Fix TLS write busy-spin on SSL_ERROR_WANT_READ
A TLS sendData() failing with SSL_ERROR_WANT_READ (mid-handshake application data, or a post-handshake session ticket exchange) has nothing to do with write-readiness, but resetSocket() kept re-arming EPOLLWRNORM anyway - an already-writable TCP socket fires that immediately, so the retry loop spun the CPU as fast as it could until the connection was eventually dropped, without ever reading the peer's first command. Confirmed via a 15GB+ debug log from one stuck connection. sendNeedsRead() lets Socket now tell the difference and wait for a real EPOLLIN instead of busy-retrying.
This commit is contained in:
1 parent
c3e8d70774
commit
2676aaf84c
6 files changed
+68
-1
No files matched your search
@@ -182,6 +182,17 @@ namespace core {
|
||||
|
||||
virtual int sendData(const char *data, int length);
|
||||
|
||||
///
|
||||
/// Overridden by TCPSocket to report whether the last sendData()
|
||||
/// call failed specifically because the TLS layer needs a READ to
|
||||
/// make progress (SSL_ERROR_WANT_READ), as opposed to any other
|
||||
/// reason - see waitingOnReadToWrite's own doc comment for why
|
||||
/// writeSocket()/resetSocket() need to know this. Always false for
|
||||
/// a plain (non-TLS) socket, which has no such distinction.
|
||||
///
|
||||
|
||||
virtual bool sendNeedsRead() { return false; }
|
||||
|
||||
private:
|
||||
|
||||
std::string text;
|
||||
@@ -226,6 +237,19 @@ namespace core {
|
||||
// clearly never going to un-stick itself and gets shut down.
|
||||
int consecutiveWriteStalls = 0;
|
||||
|
||||
// Set by writeSocket() when the last failed send specifically
|
||||
// needed a read to proceed (sendNeedsRead() true) rather than
|
||||
// being genuinely stalled on write-readiness. resetSocket() skips
|
||||
// adding EPOLLWRNORM to the re-armed event mask while this is
|
||||
// true - re-arming it anyway just spins the CPU as fast as it can
|
||||
// loop, since an already-writable TCP socket keeps firing
|
||||
// EPOLLWRNORM immediately while the actual blocker (more bytes
|
||||
// from the peer) has nothing to do with write-readiness at all.
|
||||
// Cleared on any EPOLLIN event (see eventReceived()) so the write
|
||||
// gets a fresh attempt via that same event's own trailing
|
||||
// resetSocket() call, once there's real reason to expect progress.
|
||||
bool waitingOnReadToWrite = false;
|
||||
|
||||
void resetSocket();
|
||||
|
||||
std::mutex lock;
|
||||
|
||||
Reference in new issue
Block a user