From 6a3ad787d5d263d23e52a9f3bb23423266f4cbdd Mon Sep 17 00:00:00 2001 From: Fredrik Johansson Date: Thu, 2 Jul 2026 18:57:06 +0200 Subject: [PATCH] fix: register file-done channel before sending file-accept MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For small files, file-done arrives before wireFileRecv's goroutine reaches the pendingDones registration — the message was discarded and the goroutine timed out. Fix by registering doneCh in AcceptOffer (before sending file-accept), carrying it through recvState, and reading it in wireFileRecv without re-registering. Co-Authored-By: Claude Sonnet 4.6 --- cli/internal/transport/transport.go | 30 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/cli/internal/transport/transport.go b/cli/internal/transport/transport.go index b8f3c5e..aaff774 100644 --- a/cli/internal/transport/transport.go +++ b/cli/internal/transport/transport.go @@ -777,13 +777,20 @@ func (s *Session) AcceptOffer(offer FileOffer, downloadDir string) { if info, err := os.Stat(partPath); err == nil && info.Size() < offer.Size { offset = info.Size() } - s.controlSend(map[string]string{"type": "file-accept", "xid": offer.XID, "offset": fmt.Sprint(offset)}) + // Register doneCh before sending file-accept so file-done can never arrive + // before the channel exists (small files finish before wireFileRecv runs). + doneCh := make(chan string, 1) s.mu.Lock() + if s.pendingDones == nil { + s.pendingDones = make(map[string]chan string) + } + s.pendingDones[offer.XID] = doneCh if s.pendingRecvs == nil { s.pendingRecvs = make(map[string]*recvState) } - s.pendingRecvs[offer.XID] = &recvState{offer: offer, dir: downloadDir, have: offset} + s.pendingRecvs[offer.XID] = &recvState{offer: offer, dir: downloadDir, have: offset, doneCh: doneCh} s.mu.Unlock() + s.controlSend(map[string]string{"type": "file-accept", "xid": offer.XID, "offset": fmt.Sprint(offset)}) } func (s *Session) RejectOffer(xid string) { @@ -791,10 +798,11 @@ func (s *Session) RejectOffer(xid string) { } type recvState struct { - offer FileOffer - dir string - have int64 - f *os.File + offer FileOffer + dir string + have int64 + f *os.File + doneCh chan string } func (s *Session) wireFileRecv(dc *webrtc.DataChannel) { @@ -885,13 +893,9 @@ func (s *Session) wireFileRecv(dc *webrtc.DataChannel) { } // §9: wait for file-done and verify sha256 before reporting success. - doneCh := make(chan string, 1) - s.mu.Lock() - if s.pendingDones == nil { - s.pendingDones = make(map[string]chan string) - } - s.pendingDones[xid] = doneCh - s.mu.Unlock() + // doneCh was registered in AcceptOffer before file-accept was sent, + // so file-done can never arrive before the channel exists. + doneCh := rs.doneCh var expectedSHA256 string select { case expectedSHA256 = <-doneCh: