Skip to content

file: use buffered pipes to prevent deadlock during pack negotiation#1826

Open
lukemarsden wants to merge 1 commit intogo-git:mainfrom
lukemarsden:fix-file-transport-deadlock
Open

file: use buffered pipes to prevent deadlock during pack negotiation#1826
lukemarsden wants to merge 1 commit intogo-git:mainfrom
lukemarsden:fix-file-transport-deadlock

Conversation

@lukemarsden
Copy link

Summary

The file transport uses in-process pipes to communicate between client and server. With unbuffered io.Pipe, both sides can deadlock when cloning repositories with many refs (e.g., 300+ branches).

The deadlock occurs because:

  1. Client writes "have" lines to stdin pipe
  2. Server writes advertised refs and ACKs to stdout pipe
  3. Both pipes fill up (io.Pipe has zero buffer)
  4. Both sides block waiting for the other to read

Solution

This PR introduces a buffered pipe implementation that allows writes to proceed up to a configurable limit (10MB) before returning an error. If the buffer is exceeded, ErrBufferFull is returned rather than deadlocking indefinitely.

The 10MB limit is far more than needed for pack negotiation - even with 1000 refs, the data is typically under 100KB.

Changes

  • buffered_pipe.go: New buffered pipe implementation with configurable max size
  • client.go: Use buffered pipes for stdin and stdout instead of io.Pipe
  • buffered_pipe_test.go: Tests for the buffered pipe implementation

Testing

All existing file transport tests pass:

ok  	github.com/go-git/go-git/v6/plumbing/transport/file	0.270s

New tests added for the buffered pipe:

  • Basic read/write
  • Multiple writes before read (would deadlock with io.Pipe)
  • Close behavior
  • Buffer overflow error

@lukemarsden lukemarsden force-pushed the fix-file-transport-deadlock branch from f1fdd57 to a9b5c72 Compare January 27, 2026 03:45
The file transport uses in-process pipes to communicate between client
and server. With unbuffered io.Pipe, both sides can deadlock when
cloning repositories with many refs (e.g., 300+ branches).

The deadlock occurs because:
1. Client writes "have" lines to stdin pipe
2. Server writes advertised refs and ACKs to stdout pipe
3. Both pipes fill up (io.Pipe has zero buffer)
4. Both sides block waiting for the other to read

This commit introduces a buffered pipe implementation that allows writes
to proceed up to a configurable limit (10MB) before blocking. If the
buffer is exceeded, an error is returned rather than deadlocking.

The 10MB limit is far more than needed for pack negotiation - even with
1000 refs, the data is typically under 100KB.
@lukemarsden lukemarsden force-pushed the fix-file-transport-deadlock branch from a9b5c72 to b982028 Compare January 27, 2026 03:55
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