From 49625ab5e510c62aff0186d644f60233756916b0 Mon Sep 17 00:00:00 2001 From: Xirvik Support <31997142+xirvik@users.noreply.github.com> Date: Thu, 4 Jun 2026 02:27:45 +0000 Subject: [PATCH] rpc: close SCGI task on EPIPE to stop event_write() busy-loop When an SCGI client closes the connection before rtorrent finishes sending the response, send() in SCgiTask::event_write() returns -1 with errno EPIPE. EPIPE was grouped with EAGAIN/EINTR as a non-fatal retry-later condition, so the task was not closed and its descriptor stayed registered for EPOLLOUT. A broken socket is reported writable immediately, so epoll_wait() returns it on every iteration and the SCGI thread spins at 100% CPU on one core indefinitely. The dead connection fd is also leaked (stays ESTAB). EPIPE is terminal here, not retryable: the peer is gone and the response can never be delivered. Close the task on EPIPE, matching event_read(), which already closes on any recv() error other than EAGAIN/EINTR. Reproduction: open the SCGI socket, send a complete RPC request, then shutdown(SHUT_RDWR)/close before reading the reply. Stock: the rtorrent-scgi thread goes to 100% CPU and the connection leaks. With this change: CPU stays at 0% and the descriptor is closed. --- src/rpc/scgi_task.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/scgi_task.cc b/src/rpc/scgi_task.cc index e759a2c7..f940f95c 100644 --- a/src/rpc/scgi_task.cc +++ b/src/rpc/scgi_task.cc @@ -184,7 +184,7 @@ SCgiTask::event_write() { int bytes = ::send(m_fileDesc, m_buffer.data() + m_position, m_buffer.size() - m_position, 0); if (bytes == -1) { - if (!(errno == EAGAIN || errno == EINTR || errno == EPIPE)) + if (!(errno == EAGAIN || errno == EINTR)) close(); return;