• src/bench/zmodem/zdecode.pydocs/zmodem_comparison.md src/bench/zmodem/

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Friday, July 24, 2026 11:06:27
    https://gitlab.synchro.net/main/sbbs/-/commit/b5271e17b593f358d442945a
    Added Files:
    src/bench/zmodem/zdecode.py
    Modified Files:
    docs/zmodem_comparison.md src/bench/zmodem/README.md zbench_sock.py
    Log Message:
    bench/zmodem: find the batched-sender failure -- queue depth, not batching

    Eight prototypes failed the error-recovery gate without the cause ever
    being identified, so this stops guessing and captures the wire instead. zbench_sock.py gains --tap (dump the post-corruption byte stream in both directions) and --sockbuf (bound SO_SNDBUF/SO_RCVBUF, i.e. the sender's in-flight backlog), and zdecode.py turns a capture into a frame trace, resynchronising on ZPAD/ZDLE the way a receiver does.

    Decoding the receiver-to-sender channel of a passing and a failing run
    answers it in one look. The shipped per-byte sender needs one ZRPOS per
    error. The batched sender makes the receiver repeat the same ZRPOS two
    to eight times before it can resynchronise, because the sender keeps
    feeding it queued stale data from beyond the reposition point. Wire
    overhead on the same 2 MB run: +7.3% shipped against +216% batched.

    So the variable is how many bytes are queued ahead of the sender when the
    ZRPOS is generated -- ring plus socket buffers -- and not batching at
    all. A per-byte producer at 11 MB/s cannot outrun the drain and its
    queue stays nearly empty; a batched producer at 115 MB/s keeps it full. Bounding the queue proves it, with no code change beyond the batching:
    the default 16 KB ring gives 0/3, OutbufSize=1024 alone gives 2/3, and OutbufSize=1024 with 8 KB socket buffers gives 3/3 at +10.2 to +10.7%
    overhead, against a shipped-sender control that also passes 3/3. The
    bound is free -- a 1 KB ring costs no throughput, 115.80 against 115.79
    MB/s -- because batching already removed the per-byte cost.

    That arrives at Deuce's socket-buffer/BDP argument from the other side:
    on a real link the correct bound on in-flight bytes is the
    bandwidth-delay product, and error-recovery cost scales with how far past
    the reposition point the sender has already run.

    Two corrections fall out. The earlier "not a speed artifact" control was
    too strong: --rate-bps throttles the relay, not the sender's backlog, so
    it never tested this hypothesis. And ZMODEM's own transmit window does
    not substitute for the bound -- batched with -w32768 timed out 3/3 and
    -w8192 moved essentially nothing, so the batched prototype deadlocks
    under windowed mode, which is a defect in it rather than in -w.

    The recommendation becomes: buffer the producer, prefer the
    single-threaded form (same 115.8 MB/s at 0.83 CPU-seconds and 1,585
    context switches), and have sexyz bound its own in-flight bytes rather
    than relying on the environment.

    No sexyz/zmodem.c changes. Refs #1195.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net