Subj : sexyz: SIGFPE (divide by zero) when the ZMODEM transmit window is smal To : GitLab note in main/sbbs From : Rob Swindell Date : Fri Jul 24 2026 12:04:34 https://gitlab.synchro.net/main/sbbs/-/issues/1197#note_9690 ### Root cause of the `-w8192` slowness (the "related, not fixed here" item) Diagnosed. It is a real stall, not a benchmark artifact — it reproduces over a direct socketpair with no relay in the middle. **Symptom:** with the transmit window equal to the block size (`-8 -w8192`, and the same whenever the block-size ramp reaches the window), the transfer advances **exactly one window per second**. Data flows in bursts separated by 1-second silences. **Mechanism (confirmed by strace of both ends + single-clock wire capture):** 1. With a one-block window, every data subpacket fills the window, so after each block the sender must wait for that block's ZACK before sending the next. 2. `sexyz` sends through a producer/consumer ring: the protocol thread queues the subpacket, a separate `output_thread` drains it to the socket. When the protocol thread finishes a subpacket it calls `zmodem_flush()`, but `sexyz`'s flush callback is a no-op (it only `fflush(stdout)`s, and only in stdio mode). 3. So the protocol thread starts *waiting for the ZACK* while the subpacket's **terminator** (`ZDLE ZCRCQ CRC`, ~6 bytes) is still sitting in the output thread's linear buffer, not yet written. The receiver has the 8192 data bytes but not the terminator, so it cannot see the subpacket boundary and does not ACK. Its `read()` blocks — measured at `<1.001374>`. 4. Both sides now wait on each other. The sender's back-channel poll times out after **1 second**, it reads the next file block and queues it, and *that* write finally pushes the stuck terminator onto the wire. The receiver then completes the previous subpacket and ACKs it — one second late. Repeat. `-w32768` (and larger) hides this because the window holds 2–4 blocks, so the asynchronous ACKs for earlier blocks keep the window open and the sender never actually blocks on the not-yet-flushed terminator. **Confirmation:** replacing the no-op `flush()` with one that waits for the output thread to be genuinely idle (ring empty **and** its linear buffer written) takes `-8 -w8192` from a crawl to **1.72 MB/s**. That is proof of the cause, but not a shippable fix on its own — waiting on every subpacket serialises the producer and consumer and cuts streaming throughput roughly in half (11.6 → 4.5 MB/s). A real fix wants an event the output thread signals when fully drained, waited on only where the protocol is about to block for a response — which is the same "flush actually reaches the wire" work the sender redesign in #1195 needs. Tracking the fix there. *Note by Claude (Anthropic).* --- SBBSecho 3.37-Linux * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705) .