Skip to content

Add an MVS test that detects when select() silently drops sockets from its set #144

Description

@mgrossmann

What

A test program under test/mvs that determines, from inside the guest, whether the emulator reports every socket that actually has data when select() is called on more than one.

The defect it looks for

tcpip.c in Hercules derives the nfds it passes to the host select() from Ccom_han[maxsock-1] -- the host handle of the highest socket number the guest passed -- rather than from the highest handle in the set. Socket numbers are handed out by the emulator, host handles by the host, and the two orderings drift apart as soon as anything else opens or closes a descriptor. A socket whose handle sits at or above that limit is not examined by select() at all: it never comes back ready, and whoever waits on it waits.

The defect is only reachable from a program that selects on more than one socket. Measured on MVS 3.8j: over 40000 select runs, not one had more than a single descriptor in its set. HTTPD selects on its listener alone, because accepted connections go to its worker queue rather than into the array its fd_set is built from; FTPD selects on one control or data socket at a time; ufsd, mvsmf, httplua and httprexx do not select at all. Nothing in the ecosystem currently exercises it, which is exactly why it needs a program written for the purpose.

Why a guest test rather than emulator instrumentation

It needs no Hercules change: no diagnostic build, no install, no IPL. It runs against any emulator version, including ones already deployed elsewhere, and it keeps working as a regression test after the defect is fixed.

How it detects the defect without emulator support

  1. Open a listener on 127.0.0.1 and connect to it N times, so the program owns both ends of N pairs and knows every socket number it was given.
  2. Make every socket in the set readable -- one byte on each peer.
  3. Call select() over the whole set with a short timeout.
  4. Cross-check each socket independently with ioctlsocket(FIONREAD). That is a different X'75' function code and does not go through the select path, so it is unaffected by the defect.
  5. Any socket with FIONREAD > 0 that select() did not report is a miss.

The invariant is simple enough to be worth stating outright: if every socket in the set is readable, select() must report every one of them. The number missing also says how far the limit was off.

Shuffling, and what the test cannot do alone

Slot numbers and host handles only come apart when the allocation order is disturbed. Both allocators hand out the lowest free entry -- the emulator for socket numbers, the kernel for descriptors -- so as long as the guest is the only actor, closing and reopening keeps the two orderings in step. The test should therefore close a varying subset and reopen it over several rounds, but it should not be expected to force divergence on its own.

What produces the divergence is descriptor churn that does not come from the guest: connections opened, held and closed against the emulator's own listeners (its HTTP console port, its TN3270 port) while the guest allocates. That part is driven from outside and needs no guest code, so it does not belong in this program -- but the program should be usable while it happens, i.e. it should run long enough and in enough rounds to be a target.

Output

  • per round: how many sockets were in the set, how many misses
  • per miss: the socket numbers in the set, which one was missed, and what FIONREAD reported for it
  • final summary: rounds, sockets tested, total misses
  • the socket numbers the program was handed, so a run can be correlated with an emulator-side trace if one is ever taken

Suggested shape

test/mvs/tst75sel.c, following the existing tst*.c naming. Start with the minimal case -- two sockets, both readable -- and scale to eight; two is already enough to demonstrate the defect and is far easier to reason about in the output.

Batch-runnable, so the summary lands in SYSOUT and can be read back without a terminal session.

Value either way

A miss proves the defect is reachable on the emulator under test, which is the evidence its upstream report currently lacks -- deliberately, because nothing has been changed there on speculation. Zero misses across forced divergence is worth having too: it bounds how hard the condition is to reach, and after a fix it is the regression guarantee.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions