Summary
ftpd_session_getline() (src/ftpd#ses.c:177-204) polls the control socket with
a 5 s select() timeout and re-checks FTPD_QUIESCE on every timeout. That is
deliberate and it works — it is the fix that closed #2. What remains is the
residual boundary: an idle session only notices a shutdown at the next poll
expiry, so terminate() can wait up to ~5 s in cthread_manager_term() before
the last worker exits.
The delay is bounded and it is ~5 s in total, not 5 s per session — workers poll
concurrently. So this is a latency wart on /P FTPD and /F FTPD,SHUTDOWN, not
a hang. Recording it so it is tracked rather than rediscovered.
Current behaviour
tv.tv_sec = 5;
tv.tv_usec = 0;
rc = select(sess->ctrl_sock + 1, &rfds, NULL, NULL, &tv);
if (rc < 0)
return -1;
if (rc == 0) {
/* select() timed out -- check shutdown and idle */
if (sess->server->flags & FTPD_QUIESCE)
return -1;
...
The real idle timeout is measured cumulatively across 5 s rounds via
sess->idle_start, so the poll interval is independent of idle_timeout.
Why the mvsMF fix does not transfer directly
mvsMF solved the equivalent problem by checking the httpd quiesce flag on each
poll iteration and adding a 0.10 s delay. The first half is already present
here — the flag is checked on every poll.
The second half inverts. In the mvsMF case the 0.10 s delay made a busy poll
cheaper. In ftpd_session_getline() the select() is the blocking wait, so
lowering tv_sec 5 → 0.10 s does not add a delay, it removes one: it means
10 × SVC 75 per second per idle session, permanently, to save at most ~4.9 s
once at shutdown. On MVS 3.8j that trade is not worth making. Shortening the
poll is the wrong lever.
What the real lever is
Waking the blocked select() on demand, rather than shortening its timeout.
Both known mechanisms already have hazards documented in this repo:
- Post the wakeup ECB —
ftpd.c:404-408 records that ecb_post alone does
not reliably wake DYN75 selectex; SVC 75 uses its own internal wait rather
than an MVS WAIT on the ECB.
- Close the socket under the worker —
ftpd.c:391-396 records that closing
a socket while another TCB holds it in select() can leave the TCP/IP control
block undefined and hang select() indefinitely on 3.8j.
A third option is a self-pipe / loopback wakeup socket per session, added to the
fd_set, which shutdown writes one byte to. That costs one socket per session —
needs measuring against the memory budget before it is worth anything.
So this is research, not a one-liner.
Acceptance
- Shutdown latency attributable to idle worker sessions is materially below the
current ~5 s, or
- the investigation concludes the current bounded 5 s is the right trade for
3.8j and this issue is closed with that reasoning recorded.
Either outcome is fine. What is not fine is silently lowering the poll interval.
References
Summary
ftpd_session_getline()(src/ftpd#ses.c:177-204) polls the control socket witha 5 s
select()timeout and re-checksFTPD_QUIESCEon every timeout. That isdeliberate and it works — it is the fix that closed #2. What remains is the
residual boundary: an idle session only notices a shutdown at the next poll
expiry, so
terminate()can wait up to ~5 s incthread_manager_term()beforethe last worker exits.
The delay is bounded and it is ~5 s in total, not 5 s per session — workers poll
concurrently. So this is a latency wart on
/P FTPDand/F FTPD,SHUTDOWN, nota hang. Recording it so it is tracked rather than rediscovered.
Current behaviour
The real idle timeout is measured cumulatively across 5 s rounds via
sess->idle_start, so the poll interval is independent ofidle_timeout.Why the mvsMF fix does not transfer directly
mvsMF solved the equivalent problem by checking the httpd quiesce flag on each
poll iteration and adding a 0.10 s delay. The first half is already present
here — the flag is checked on every poll.
The second half inverts. In the mvsMF case the 0.10 s delay made a busy poll
cheaper. In
ftpd_session_getline()theselect()is the blocking wait, solowering
tv_sec5 → 0.10 s does not add a delay, it removes one: it means10 × SVC 75 per second per idle session, permanently, to save at most ~4.9 s
once at shutdown. On MVS 3.8j that trade is not worth making. Shortening the
poll is the wrong lever.
What the real lever is
Waking the blocked
select()on demand, rather than shortening its timeout.Both known mechanisms already have hazards documented in this repo:
ftpd.c:404-408records thatecb_postalone doesnot reliably wake DYN75
selectex; SVC 75 uses its own internal wait ratherthan an MVS WAIT on the ECB.
ftpd.c:391-396records that closinga socket while another TCB holds it in
select()can leave the TCP/IP controlblock undefined and hang
select()indefinitely on 3.8j.A third option is a self-pipe / loopback wakeup socket per session, added to the
fd_set, which shutdown writes one byte to. That costs one socket per session —needs measuring against the memory budget before it is worth anything.
So this is research, not a one-liner.
Acceptance
current ~5 s, or
3.8j and this issue is closed with that reasoning recorded.
Either outcome is fine. What is not fine is silently lowering the poll interval.
References
src/ftpd#ses.c:144-228—ftpd_session_getline()src/ftpd.c:381-438—terminate()