Files
ppsspp/Common/Net/Sinks.h
T
Henrik RydgårdandClaude Opus 5 abd91da5eb 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
2026-08-31 00:57:17 +02:00

102 lines
2.4 KiB
C++

#pragma once
#include <string>
#include "Common/Common.h"
namespace net {
class InputSink {
public:
InputSink(size_t fd);
bool ReadLine(std::string &s);
std::string ReadLine();
bool ReadLineWithEnding(std::string &s);
std::string ReadLineWithEnding();
// Read exactly this number of bytes, or fail.
bool TakeExact(char *buf, size_t bytes);
// Read whatever is convenient (may even return 0 bytes when there's more coming eventually.)
size_t TakeAtMost(char *buf, size_t bytes);
// Skip exactly this number of bytes, or fail.
bool Skip(size_t bytes);
void Discard();
size_t ReadBinaryUntilTerminator(char *dest, size_t bufSize, std::string_view terminator, bool *didReadTerminator);
bool Empty() const;
bool TryFill();
bool HasError() const { return hasError_; }
// True once the peer has closed its end. Sticky - no more data can ever arrive, which is not
// the same as "nothing right now", and callers that wait for more need to tell them apart.
bool AtEnd() const { return atEnd_; }
size_t ValidAmount() const {
return valid_;
}
// Size of the internal buffer. Useful for some clients.
enum {
BUFFER_SIZE = 32 * 1024,
};
private:
std::pair<std::string_view, std::string_view> BufferParts() const;
void Fill();
bool Block();
void AccountDrain(size_t bytes);
size_t FindNewline() const;
static const size_t PRESSURE = 8 * 1024;
size_t fd_;
// Circular buffer. read_ is the position to read from, write_ is the position to write to,
// valid_ is the number of valid bytes. valid_ can wrap around, so you might need to take
// two segments when reading and split when writing.
char buf_[BUFFER_SIZE];
size_t read_;
size_t write_;
size_t valid_;
bool hasError_ = false;
bool atEnd_ = false;
};
class OutputSink {
public:
OutputSink(size_t fd);
bool Push(const std::string &s);
bool Push(const char *buf, size_t bytes);
size_t PushAtMost(const char *buf, size_t bytes);
ATTR_FORMAT_PRINTF(2, 3)
bool Printf(MSVC_FORMAT_PRINTF const char *fmt, ...);
bool Flush(bool allowBlock = true);
void Discard();
bool Empty() const;
size_t BytesRemaining() const;
bool HasError() const { return hasError_; }
private:
void Drain();
bool Block();
void AccountPush(size_t bytes);
void AccountDrain(int bytes);
static const size_t BUFFER_SIZE = 32 * 1024;
static const size_t PRESSURE = 8 * 1024;
size_t fd_;
char buf_[BUFFER_SIZE];
size_t read_;
size_t write_;
size_t valid_;
bool hasError_ = false;
};
} // namespace net