Skip to content

Modernize gfile: Go 1.26, pion/webrtc v4, framed protocol#12

Merged
Antonito merged 8 commits into
masterfrom
modernize-gfile
Apr 20, 2026
Merged

Modernize gfile: Go 1.26, pion/webrtc v4, framed protocol#12
Antonito merged 8 commits into
masterfrom
modernize-gfile

Conversation

@Antonito

Copy link
Copy Markdown
Owner

Summary

This PR is a large overhaul of gfile after a long maintenance gap. The goal is to bring the project back to life on a modern Go / pion stack, give it a real wire protocol, and make same-host / cross-host transfers meaningfully faster.

  • Go & deps: bump Go 1.12 → 1.26.2, migrate pion/webrtc v2 → v4, swap logrus → zerolog, urfave/cli → cobra.
  • New wire protocol (PROTOCOL.md, internal/protocol): framed control/data channels with versioning, proper dispatch, and streaming zstd compression (off by default for already-compressed data).
  • Multi-PC mode: --connections N (1..16) stripes the transfer across N parallel peer connections, working around the per-datachannel throughput ceiling on high-bandwidth / high-latency links. See PROTOCOL.md — Multi-PC mode.
  • Restructure: code moved into internal/{session,stream,stats,output,protocol,utils} and pkg/transfer/{sender,receiver}. The old _client/web wasm experiment and the legacy pkg/session, pkg/stats, pkg/utils trees are removed.
  • CI & tooling: Travis + Makefile → GitHub Actions (ci.yml, release.yml) + Justfile + golangci-lint. Expanded Go test coverage plus Python scripts/e2e.py and scripts/bench.py harnesses.

Benchmarks

Methodology: same host (darwin 24.6, arm64, Apple Silicon), pion/webrtc, incompressible random payload. Master's gfile bench is a 20-second synthetic throughput test over a single data channel with a 64 kB buffer-low threshold and 4 kB writes. This PR's gfile bench is a real 500 MB file transfer through the full pkg/transfer pipeline. 3–5 runs per config, median reported.

Config Throughput (MB/s) vs master
master (v2 webrtc, 20s synthetic) 1.1 (0.95–1.17, n=5)
This PR, single-PC, --loopback 102.6 (101.0–104.3, n=3) ~93×
This PR, multi-PC --connections 2, --loopback 179.7 (167.3–180.3, n=3) ~163×
This PR, multi-PC --connections 4, --loopback ~241 (240.7–242.5, n=3)¹ ~219×

¹ --connections 4 over loopback on this macOS version (Darwin 24.6) intermittently fails peer-open with abort: peer N open timeout — roughly 3/5 attempts succeed. The failure is environmental (ICE/DC open racing on loopback), not a protocol regression; successful runs are stable at the number above. Cross-host real-network runs don't exhibit this.

Note

This PR was drafted with the help of Claude Code. The design decisions, review, and merge call are mine; flagging it here given this project has a few stars and has been mostly unmaintained for the last few years — want to be upfront about the assist.

@Antonito Antonito mentioned this pull request Apr 19, 2026
- Bump Go 1.12 → 1.26.2; migrate pion/webrtc v2 → v4
- Replace logrus with zerolog, urfave/cli with cobra/v1.10
- Introduce framed protocol (PROTOCOL.md) under internal/protocol with control/data frames and versioning
- Restructure into internal/ (session, stream, stats, output, protocol, utils) and pkg/transfer/{sender,receiver}
- Add klauspost/compress streaming compression; bandwidth tracking and bench harness
- Swap Travis+Makefile for GitHub Actions (ci, release) + Justfile + golangci-lint config
- Drop unused _client/web wasm experiment and legacy pkg/session, pkg/stats, pkg/utils trees
- Expand test coverage (protocol, session, transfer, stats, base45, sdp) and add Python e2e/bench scripts
The three sites launching EmitProgressSamples only canceled its context
on exit without waiting for the goroutine to stop, so it could still be
reading output.Default after the caller returned. Tests swapping the
emitter via output.SetDefault had no happens-before edge with that
lingering read, triggering the race detector on Windows CI.

Add StartProgressEmitter which returns a stop that cancels and blocks
on goroutine exit, and migrate all three call sites.
The primary data channel's onFramesErr unconditionally called core.fail
on any OnFrames exit error. When the sender closed first, pion's SCTP
layer could surface teardown with "sending reset packet in
non-established state" from rwc.Read instead of io.EOF. core.fail then
raced verifyAndClose's doneOnce — if fail won, the receiver returned a
spurious "protocol error" despite the transfer having succeeded, which
flaked TestSendReceiveCompressed on CI.

The multi-connection control channel had the same class of bug: after
TRANSFER_COMPLETE, the sender's deferred PeerConnection.Close surfaces
on the receiver as an SCTP "User Initiated Abort", and the control
channel's onFramesErr logged it as ERR even though the transfer had
succeeded.

Gate both callbacks on a post-completion flag (eofSeen on single,
completeSeen on multi) so post-completion transport teardown is
silently ignored and verifyAndClose finalises the transfer.
@Antonito Antonito mentioned this pull request Apr 20, 2026
--stun is now a comma-separated list with the Google STUN default applied
at the CLI layer. Empty ("") disables STUN so transfers fall back to
host/mDNS candidates, which is enough on a LAN and avoids a public reflexive
round-trip when none is needed.
Mirror browser behavior: ICE host candidates are emitted as `.local`
hostnames instead of raw LAN IPs, so an SDP pasted between peers doesn't
leak internal network topology and a LAN transfer works without STUN.

Toggle with --mdns=false when the other peer can't resolve mDNS. The
`bench` command and the in-process sendrecv test keep mDNS off for
determinism.
Plumb a test-only ICELite flag through IOConfig → Session → multiRouter so
loopback tests can short-circuit ICE connectivity checks. The CLI never
sets it; production transfers leave it false.

Also fix a race in receiver negotiateReceivePeer: the peer was published
into r.peers before sess.CreateConnection had finished writing sess's
internal state, so a concurrent cleanup() Close could race with that
write. Now we build sess fully first, then publish, and dedupe sess.Close
via sync.Once so either cleanup() or the failure-path defer may run.
@Antonito Antonito marked this pull request as ready for review April 20, 2026 11:42
@Antonito Antonito merged commit 434e178 into master Apr 20, 2026
3 checks passed
@Antonito Antonito deleted the modernize-gfile branch April 20, 2026 11:47
This was referenced Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant