v0.2.0
First release since 0.1.130. Headline: a fix for an intermittent end-of-transfer
hang, directory in-band repair, a config file, and compression/CPU performance
wins.
Protocol
- Wire-compatible with 0.1.130 —
PROTOCOL_VERSIONis unchanged (6), so
0.1.130 and 0.2.0 peers interoperate over the QUIC/TCP direct path. No forced
upgrade.
Added
- Config file at
~/.config/mftp/config.tomlfor persistent per-link
defaults:streams,chunk_size,no_compress,transport,
tcp_below_rtt,fec,port. Precedence is explicit CLI flag > config filebuilt-in default; an absent file is not an error.
MFTP_CCenvironment variable to override the QUIC congestion controller
(bbr/cubic/reno); the default remains BBR, which suits mftp's
high-BDP target.
Performance
- Adaptive compression now caps at zstd level 3 instead of escalating to
level 6 on highly-compressible data. Level 6 cost roughly 2× the CPU for only
~7% better ratio, so whenever compression rather than the network was the
bottleneck it reduced throughput. On a 50 ms-RTT transfer of compressible
(JSON-log) data, throughput roughly doubled (~67 → ~135 MiB/s) after the
change. Incompressible data is unaffected (still drops to level 1 / skips). - Sender skips zero-filling chunk buffers. The file feeders read into
uninitialised buffers (immediately and fully overwritten by the read) instead
of allocatingvec![0u8; chunk_size]per chunk. Profiling the LAN/TCP path
showed the redundant zero-fill was ~10% of sender CPU; removed with no
behaviour change.
Reliability
- In-band incremental repair now covers directory transfers (previously
single-file only). A missing/corrupt chunk or unreconstructable FEC stripe in
a directory transfer is repaired over the existing connection — the sender
reverse-maps the global chunk index across the concatenated file set and the
receiver scatter-writes it — instead of aborting and resuming. No wire-format
change. - Resume files now carry an integrity tag (format v2): a corrupt or
bit-rotted resume file is detected on load and discarded (clean restart)
rather than trusted.
Fixed
- Stream-scaling deadlock near end-of-transfer. When adaptive stream scaling
added streams just as the transfer was finishing, the sender could hang
indefinitely: if the final worker exited before the scale-up acknowledgement
arrived, the completion check (which only ran on a worker-join) was never
re-evaluated, so the loop waited forever on the scale channel and the receiver
waited forever for aCompletethat never came. This hit roughly 1 in 3
transfers at ~50 ms RTT under the default (adaptive-streams) configuration;
pinning-n Nwas a workaround. The dispatch loop now re-checks completion
after every event, and the case has a regression test.