• FTP Server shutdown waits up to 2x MaxInactivity on an idle client; a

    From Rob Swindell@1:103/705 to GitLab issue in main/sbbs on Mon Oct 26 01:02:59 2026
    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)