https://gitlab.synchro.net/main/sbbs/-/commit/4577d214b41a92936945da1d
Modified Files:
src/sbbs3/main.cpp websrvr.cpp
Log Message:
Web/terminal servers: bound teardown output-drain SLEEP loops
close_session_socket() (web) waited unboundedly for the output ringbuffer
to drain and for the output thread to release outbuf_write. Against an abandoned client -- e.g. a scraper connection dropped mid-session, leaving recv()==0 -- the send never completes, so both `while (...) SLEEP(1)` loops spun forever, stranding the session thread and, at recycle/terminate,
hanging the whole web server on "Waiting for N child threads to terminate".
The unbounded-wait loops date to dbbfabf1b1 (funky-27-foam, "Update to
3.17a").
Bound both loops on terminate_server and a time(NULL) + max_inactivity
deadline (mirroring the drain_outbuf() hardening beside it), and, on
give-up, shutdown(SHUT_RDWR) the socket to wake the output thread's blocked send so it releases outbuf_write before the TLS session is destroyed -- the same teardown remedy as 8101584de (symbol-19-seek, "Terminal server: shutdown(), don't close(), node sockets at terminate/recycle"). The output thread only destroys outbuf_write after the socket goes INVALID_SOCKET,
which close_session_socket() sets last, so the shutdown()/trylock path
cannot race the destroy.
main.cpp's SSH-mode-switch drain -- `while (output_thread_running && RingBufFull(&outbuf)) SLEEP(1)` -- has the same shape: a dead SSH peer that never drains outbuf could wedge the node thread. d9af0e5314 (fixes-19-over) added the output_thread_running guard for a witnessed infinite loop but left the peer-dead case; bound it on terminate_server and a 60s deadline too.
Verified in production on vert over several days / 25K+ HTTP(S) requests:
clean session teardown, no thread pile-up, recycles complete.
Co-Authored-By: Claude Opus 4.8 (1M context) <
noreply@anthropic.com>
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net