From a9e356810aa0880bfd096ad5a3ab3a142a64c2f3 Mon Sep 17 00:00:00 2001 From: Andrey Bychkov Date: Mon, 24 Aug 2026 15:14:30 +0300 Subject: [PATCH] fix(piethernet): bound the EAGAIN retry loop in writeDevice by writeTimeout The loop may run in a thread that belongs to another device (the picloud dispatcher forwards data from one connection's read thread to another connection's socket), so isThreadedReadStopping() alone cannot stop it: it sees the wrong device's flag. Stalling for at most writeTimeout() without progress makes every write finite; the receiver resynchronizes on the next packet signature. --- libs/main/io_devices/piethernet.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index fd51b807..0dd3c866 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -388,7 +388,7 @@ void PIEthernet::applyBuffers() { void PIEthernet::applyTimeout(int fd, int opt, PISystemTime tm) { if (fd == 0) return; - // piCoutObj << "setReadIsBlocking" << yes; + // piCoutObj << "setReadIsBlocking" << yes; #ifdef WINDOWS DWORD _tm = tm.toMilliseconds(); #else @@ -851,13 +851,17 @@ ssize_t PIEthernet::writeDevice(const void * data, ssize_t max_size) { } else { ssize_t remain_size = max_size; const char * remain_data = (const char *)data; + // the socket is non-blocking, so EAGAIN means the send buffer is full + // and the peer is not draining it. The write may be issued from a thread + // that belongs to another device (forwarding in the picloud dispatcher), + // so this loop must be bounded by itself: stall for at most + // writeTimeout() without progress, then give up. The receiver + // resynchronizes on the next packet signature, and any concurrent + // stopAndWait() on the writing thread's device becomes finite. + PITimeMeasurer stalled_tm; while (remain_size > 0) { - if (isThreadedReadStopping()) { - // socket is non-blocking and peer may not drain the send buffer, - // without this check the loop below would spin forever and the - // thread could never be stopped by stop()/stopAndWait() - break; - } + if (isThreadedReadStopping()) break; + if (stalled_tm.elapsed_s() > writeTimeout().toSeconds()) break; int sr = ::send(sock, remain_data, remain_size, 0); if (sr < 0) { int err = ethErrorCore(); @@ -877,8 +881,12 @@ ssize_t PIEthernet::writeDevice(const void * data, ssize_t max_size) { ret += sr; remain_data += sr; remain_size -= sr; + stalled_tm.reset(); + } + if (remain_size > 0) { + piCoutObj << "Error: write stalled," << remain_size << "bytes not sent"; + return -1; } - if (remain_size > 0) return -1; } return ret; }