open
https://gitlab.synchro.net/main/sbbs/-/work_items/1304
A logged-in, idle FTP client keeps the FTP server from shutting down or recycling for up to twice `MaxInactivity` (default 300 seconds, so up to 10 minutes). If the process is killed meanwhile (e.g. by systemd's stop timeout), a QWKnet account's session lock `data/user/<user#>.ftp` is left behind, and that account is refused with `421 QWKnet accounts are limited to one concurrent FTP session` for up to an hour after the restart.
Code references are as of master 3881be3f01 (souls-11-unit, 2026-10-26). This is the FTP Server's counterpart of #1255 (Services).
## Where
`src/sbbs3/ftpsrvr.cpp`:
- `sock_recvbyte()` checks `terminate_server` only on entry (`:472-476`). After that it waits in `socket_readable(sock, startup->max_inactivity * 1000)` (`:513`, `:525`) until data arrives or the inactivity timeout expires, so a control connection that is idle at shutdown doesn't notice it.
- On shutdown the server thread waits for `active_clients` to reach zero for up to `max_inactivity * 2` seconds (`:5780-5793`), logging `Waiting for N active clients to disconnect...`.
- A QWKnet account's login takes `fmutex_open("data/user/<user#>.ftp", ..., /* max_age: */ 60 * 60)` (`:2785-2792`); it is released when the session ends (`:5293`). A killed process never releases it, and a new login is refused until the file is an hour old.
## Observed
On Linux with a QWKnet account logged in and idle, stopping the server logged `Waiting for 1 active clients to disconnect...` and nothing more until the stop script killed the process. After restart, that account's logins got `421 QWKnet accounts are limited to one concurrent FTP session` until the stale `data/user/<user#>.ftp` aged out.
## Suggested fix
Make the control-connection wait shutdown-aware, as was done for Services in #1255: poll `socket_readable()` in short slices (e.g. 1 second) and return with `421 Server downed, aborting.` once `terminate_server` is set, or have the server thread shut down the control sockets on terminate. Separately, the QWKnet session lock could record the owning process (as the QWK job locks do) so a lock left by a dead process on the same host is taken over at once instead of after an hour.
*Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.38-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)