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
+15
-1
@@ -80,6 +80,12 @@ namespace core
|
||||
}
|
||||
if(event.events & EPOLLIN) {
|
||||
coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN";
|
||||
// A read just happened (or at least an opportunity for OpenSSL
|
||||
// to make progress on whatever it needed a read for) - give any
|
||||
// write that was waiting on that a fresh attempt via this same
|
||||
// call's own trailing resetSocket(), rather than leaving
|
||||
// EPOLLWRNORM permanently un-armed from a single earlier stall.
|
||||
waitingOnReadToWrite = false;
|
||||
coreutils::ZString zbuffer(buffer, length);
|
||||
// receiveData() calls all the way down through
|
||||
// onDataReceived()/HTTPRequest::doParse()/application request
|
||||
@@ -241,10 +247,18 @@ namespace core
|
||||
int sent = sendData(fifo.front().c_str(), fifo.front().length());
|
||||
if(sent > 0) {
|
||||
consecutiveWriteStalls = 0;
|
||||
waitingOnReadToWrite = false;
|
||||
if((size_t)sent >= fifo.front().length())
|
||||
fifo.pop();
|
||||
else
|
||||
fifo.front().erase(0, sent);
|
||||
} else if(sendNeedsRead()) {
|
||||
// Not a stall in the CPU-spinning sense consecutiveWriteStalls
|
||||
// exists to catch - resetSocket() below simply won't re-arm
|
||||
// EPOLLWRNORM while this is true, so there's nothing to busy-
|
||||
// retry until a real EPOLLIN event (already always watched
|
||||
// for) brings in whatever OpenSSL is actually waiting to read.
|
||||
waitingOnReadToWrite = true;
|
||||
} else if(++consecutiveWriteStalls > 50) {
|
||||
// sendData() keeps failing for a reason that has nothing to
|
||||
// do with the socket's own write-readiness (confirmed real
|
||||
@@ -307,7 +321,7 @@ namespace core
|
||||
struct epoll_event event;
|
||||
event.data.ptr = this;
|
||||
event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET;
|
||||
if(fifo.size() > 0)
|
||||
if(fifo.size() > 0 && !waitingOnReadToWrite)
|
||||
event.events |= EPOLLWRNORM;
|
||||
// Was `if(!shutDown)` - refused to re-arm at all once shutting
|
||||
// down, even with data still queued to send (e.g. a graceful
|
||||
|
||||
Reference in new issue
Block a user