• src/sbbs3/sexyz.c

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Thu Jul 23 23:03:46 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/da546fbe1bd20ff1754e49b6
    Modified Files:
    src/sbbs3/sexyz.c
    Log Message:
    Revert "sexyz: batch ZMODEM output into the ring buffer to fix send throughput"

    This reverts commit 4e671c75640ccf8248ea9b3822df2d0a08f4f6c9.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Fri Jul 24 23:28:15 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/34af5f34b57436e1430bdf80
    Modified Files:
    src/sbbs3/sexyz.c
    Log Message:
    sexyz: buffer the ZMODEM streaming send path (#1195)

    The send bottleneck was never the two output threads or the ring -- it
    was feeding the ring one byte at a time. send_byte() took the ring mutex
    twice per byte while output_thread hot-looped on it, so a 256 MB send
    cost 44 CPU-seconds and 1.46 million voluntary context switches and
    topped out near 11 MB/s on localhost.

    Accumulate bytes into a 4 KB buffer and hand whole spans to the ring
    instead. The two-thread architecture, flow control and error handling
    are all unchanged; the producer just stops trickling. A 256 MB send now
    runs at ~115 MB/s using 0.85 CPU-seconds and 64 K context switches -- the
    same throughput a single-threaded rewrite would give, for a fraction of
    the change and none of the risk.

    Buffering only applies to full ZMODEM streaming. A transmit window or segmented mode (-w, -s) depends on the receiver's acks flowing back as
    data is sent; bursty buffered output would fill the window before any ack returns and stall the transfer, so those modes fall through to the
    original byte-at-a-time path and are byte-for-byte unchanged.

    A fast streaming sender also fills the socket's auto-tuned, possibly multi-megabyte send buffer, so a single line error would cost a ZRPOS retransmission of everything in flight and break error recovery. Bound SO_SNDBUF to the ring size to cap the in-flight backlog (streaming only --
    a window already bounds it, and a socket buffer smaller than the window
    would fight it). This is applied outside the stdio guard so it takes
    effect for a piped socket as well, and [sockopts] can override it for a
    high bandwidth-delay link.

    Verified against lrz over the bench harness: 256 MB streaming MATCH at
    ~115 MB/s; the 3x error-injection gate passes 5/5; windowed (-w8192
    through -w65536), segmented (-s) and the receive path are unchanged from before; an 8 MB transfer over a 25 ms link matches the byte-at-a-time
    sender (both link-bound). No zmodem.c change, so SyncTERM is untouched
    (and it already has a buffered send path of its own).

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

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