mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-04 03:35:19 +02:00
WebSocket: teach the input sink about EOF, and stop losing why a read failed
Follow-up to the previous commit, from Nemoumbra's questions - which found a worse spin than the one that fix addressed. InputSink couldn't tell "nothing right now" from "peer is gone": Fill() treats recv() == 0 as no data and only sets hasError_ on a real error. Block() then waits with WaitUntilReady(), which reports a closed socket as ready immediately and forever, so TakeExact() looped on it without ever returning. A client that disconnects with half a frame buffered - easy to do while blasting messages - put the server in an infinite loop inside TakeExact, never even returning to Process(). Measured 7.95 CPU-seconds over 8 seconds; 0.08 after. So: track EOF explicitly (sticky atEnd_, exposed as AtEnd()), and have Block() give up when nothing more can arrive. That information was being thrown away in three more places: * Process() only tried to fill when the sink was already empty, so a disconnect went unnoticed for as long as there were leftovers - and if those leftovers were a partial frame, the read above never completed. Always fill, and close once the peer is gone and we've consumed what it sent. * ReadPending() uses TakeAtMost(), which returns 0 both for "nothing right now" and "nothing ever again", and then reported success having consumed nothing. Ask the sink which it was. * Both TakeExact() call sites answered a failed read with POLICY_VIOLATION, blaming the client for a protocol error when it had simply disconnected. Check the sink and report ABNORMAL when that's what happened. Also stop queueing data once our own close frame is queued. RFC 6455 5.5.1 forbids data frames after a close, and beyond the protocol, anything appended afterwards keeps the buffers non-empty and starves the "everything is flushed" check that ends the connection. Observed the server pumping 167MB of log broadcasts after being asked to close. The repeated close-and-discard is now one helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
This commit is contained in:
co-authored by
Claude Opus 5
parent
03b313e22b
commit
abd91da5eb
+13
-1
@@ -213,6 +213,13 @@ void InputSink::Fill() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytes == 0 && avail > 0) {
|
||||
// recv() returning 0 on a non-empty request means the peer closed. Sticky: nothing more
|
||||
// will ever arrive, and anything waiting for more has to stop rather than poll forever.
|
||||
atEnd_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, move forward (might be by zero.)
|
||||
valid_ += bytes;
|
||||
write_ += bytes;
|
||||
@@ -223,12 +230,17 @@ void InputSink::Fill() {
|
||||
}
|
||||
|
||||
bool InputSink::Block() {
|
||||
if (atEnd_ || hasError_) {
|
||||
// Nothing can arrive any more. Without this we'd spin: a closed socket is always "ready",
|
||||
// so WaitUntilReady returns immediately and Fill() reads nothing, forever.
|
||||
return false;
|
||||
}
|
||||
if (!fd_util::WaitUntilReady((int)fd_, 5.0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Fill();
|
||||
return true;
|
||||
return !atEnd_;
|
||||
}
|
||||
|
||||
void InputSink::AccountDrain(size_t bytes) {
|
||||
|
||||
Reference in New Issue
Block a user