open
https://gitlab.synchro.net/main/sbbs/-/work_items/1334
## Summary
After a server is told to terminate or recycle, its accept loop doesn't notice until the current wait for a new connection ends. That wait is `SemFileCheckFrequency` seconds (default 2, often set higher; 10 on one production host). Until then:
- the listening sockets stay open, so new connections queue in the kernel's listen backlog unanswered, and get a reset when the listeners finally close;
- the first connection to arrive wakes the loop and is **accepted after terminate**. The Web Server then serves it normally; the FTP Server hands it to a session thread that replies `421 Server downed, aborting`;
- shutdown and recycle take up to `SemFileCheckFrequency` seconds longer than necessary.
This is low impact: the late connection is handled cleanly, and a refused connection would also have to retry. The bigger window, listeners held open through the whole client and child-thread drain (often 60+ seconds), was closed by 3592004a31 (screen-3-trend, 2026-11-10).
Code references are as of master 3592004a31.
## Observed (2026-11-10, Linux, `SemFileCheckFrequency = 10s`)
```
22:28:36 ftp FTP Server terminate
22:28:42 ftp 0004 [24.166.43.233] Connection accepted on 71.95.196.36 port 21 from port 32757
22:28:42 ftp 0013 FTP Server closing socket 71.95.196.36 port 21
22:28:42 ftp 0004 Server downed, aborting
```
Across three restarts that evening, the gap between each server's `... terminate` line and its `closing socket` lines ranged from 0 to 10 seconds.
## Where
- `xpms_accept()` (`src/xpdev/multisock.c:352`) runs a single `poll()` for the caller's full `timeout` (`:388-395`) and never looks at a termination flag. `xpms_add()`/`xpms_add_list()` already take a `bool* terminated`, but it is used only to cut bind retries short (`:179`) and isn't stored in the `struct xpms_set`.
- Callers pass `startup->sem_chk_freq * 1000` as the timeout: `websrvr.cpp:8060`, `ftpsrvr.cpp:5727`, `mailsrvr.cpp:6863`, `main.cpp:5795`.
- The Terminal Server already closes and drops a connection accepted after terminate (`main.cpp:5798-5802`). The Web, FTP and Mail servers don't, and process it.
- The Services server doesn't use `xpms_accept()`; it has its own `poll()`/`select()` over the service sockets with the same `sem_chk_freq` timeout (`services.cpp:2366`, `:2407`), so it has the same lag.
All `xpms_*` callers are in `src/sbbs3` (the four servers above plus `js_socket.cpp`'s ListeningSocket, which passes `XPMS_FOREVER`), so changing `xpms_accept()` doesn't affect SyncTERM or other xpdev users.
## Suggested fix
1. Store the `terminated` pointer in `struct xpms_set` (set via `xpms_add*()` or a new setter), and have `xpms_accept()` poll in 1-second slices up to the caller's timeout, returning `INVALID_SOCKET` as soon as `*terminated` is set. Each server's semaphore-file checks still run once per `SemFileCheckFrequency`, as now, so this adds no filesystem traffic. Only the wakeup to notice terminate becomes faster.
2. In the Web, FTP and Mail accept loops, add the guard the Terminal Server already has: if `terminate_server` is set when `xpms_accept()` returns a socket, close it and break.
3. Give the Services poll loop the same 1-second slicing.
With (1) and (2), a server stops accepting within about a second of being told to terminate and never processes a connection accepted afterwards.
Related: #1333 (shutdown waits on stuck child threads), #1295 (busy servers never act on recycle semaphores).
*Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.38-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)