Version
6.10.1.202505221210-r
Operating System
Linux/Unix
Bug description
ReceivePack.receive() hangs indefinitely on packs above roughly 55-100 KiB — no exception, no timeout from JGit itself, no forward progress. Below that size, pushes of equivalent shape (similar object/delta count) complete in under a second.
Found via GitBucket (embeds JGit 6.10.1.202505221210-r): gitbucket/gitbucket#4117. Filing here as well because the investigation isolated this to ReceivePack/PackParser itself — reproducible independent of transport (SSH vs. HTTPS), independent of the embedding SSH server library version, independent of thin-pack vs. full-pack, and independent of new-ref vs. existing-ref — so it does not look specific to GitBucket's embedding code.
Environment
- JGit
6.10.1.202505221210-r
- Observed via GitBucket 4.44.0 and 4.47.0 (same JGit version in both; only the embedded Apache SSHD version differed, 2.16.0 vs 2.19.0, with no effect)
- JVM: OpenJDK 17.0.14
- Git client: 2.43.0
Reproduction
- Push any commit whose full pack totals roughly >60 KiB with more than a trivial single-file change, to a server embedding this JGit version (SSH or HTTPS)
- Client finishes
Writing objects: 100%; server sends nothing back
- The connection eventually dies via whatever timeout exists outside JGit on that transport (see Actual behavior) — JGit itself never resolves it either way
Actual behavior
The client finishes writing the pack normally (Writing objects: 100% ... done) and then receives zero further protocol response. Server-side, the request thread is permanently parked in a blocking InputStream.read() inside PackParser.whole() — confirmed via repeated thread dumps (kill -QUIT, ~60s apart) showing the identical stack with zero progress between samples. ReceivePack never throws, never times out on its own, and never completes. The hang only ends when something external to JGit intervenes — in our testing, either the embedding SSH server's own idle timer (10 minutes) or a reverse proxy's read timeout — at which point JGit merely discovers, when it finally tries to write its status report, that the connection is gone (channel already closed / equivalent).
Expected behavior
ReceivePack.receive() should complete successfully for a well-formed pack of this size and shape (it does for smaller/equivalent packs), or fail fast with an exception if something about the input is actually invalid. It should never block indefinitely with no internal timeout and no forward progress, regardless of pack size.
Relevant log output
Thread dump, identical across multiple ~60s-apart samples during the hang:
java.io.InputStream.read(java.base@17.0.14/InputStream.java:218)
org.eclipse.jgit.transport.PackParser.whole(PackParser.java:1061)
org.eclipse.jgit.transport.PackParser.indexOneObject(PackParser.java:983)
org.eclipse.jgit.transport.PackParser.parse(PackParser.java:534)
org.eclipse.jgit.internal.storage.file.ObjectDirectoryPackParser.parse(ObjectDirectoryPackParser.java:168)
org.eclipse.jgit.transport.ReceivePack.receivePack(ReceivePack.java:1559)
org.eclipse.jgit.transport.ReceivePack.receivePackAndCheckConnectivity(ReceivePack.java:1250)
org.eclipse.jgit.transport.ReceivePack.service(ReceivePack.java:2270)
org.eclipse.jgit.transport.ReceivePack.receive(ReceivePack.java:2200)
What the embedding server (GitBucket) eventually logs once its own unrelated idle timer fires, ~10 minutes later, on the SSH transport:
Detected IdleTimeout after 600218/600000 ms
org.apache.sshd.common.channel.exception.SshChannelClosedException: write(...) - channel already closed
at org.eclipse.jgit.transport.ReceivePack.close(ReceivePack.java:1963)
at org.eclipse.jgit.transport.ReceivePack.receive(ReceivePack.java:2210)
Other information
- GC/heap pressure —
jstat -gcutil flat throughout
- CPU-bound work — no busy thread, idle many-core host
- A stalled outbound call from the server — none exists during the hang
- Embedded SSH server library version — upgrading it (2.16.0 → 2.19.0) had no effect
- Thin-pack external delta-base resolution —
git push --no-thin reproduces identically
- New-ref vs. existing-ref — reproduces identically either way
- Transport — reproduces identically over SSH and HTTPS
Bisection, splitting one failing push's exact content in half:
| Push |
Objects |
Size |
Result |
| Full commit (44 files) |
55 |
103.75 KiB |
Hangs |
| Same commit minus one file |
56 |
49.11 KiB |
Succeeds instantly |
| That one file alone |
3 |
55.46 KiB |
Succeeds instantly |
Neither half is malformed or unusual on its own. Only the combined ~104 KiB pack hangs — pointing at a size-dependent condition in PackParser/ObjectDirectoryPackParser (buffer sizing or a length-accounting boundary), not a specific bad object or encoding.
Not yet done: a minimal, JGit-only reproduction (e.g. a unit test driving ReceivePack directly over a PipedInputStream/PipedOutputStream pair with a synthetic pack, no real server involved) — everything above comes from a real GitBucket deployment. Happy to attempt that, or to share the actual .pack file that reproduces it, if either would help.
Version
6.10.1.202505221210-r
Operating System
Linux/Unix
Bug description
ReceivePack.receive()hangs indefinitely on packs above roughly 55-100 KiB — no exception, no timeout from JGit itself, no forward progress. Below that size, pushes of equivalent shape (similar object/delta count) complete in under a second.Found via GitBucket (embeds JGit
6.10.1.202505221210-r): gitbucket/gitbucket#4117. Filing here as well because the investigation isolated this toReceivePack/PackParseritself — reproducible independent of transport (SSH vs. HTTPS), independent of the embedding SSH server library version, independent of thin-pack vs. full-pack, and independent of new-ref vs. existing-ref — so it does not look specific to GitBucket's embedding code.Environment
6.10.1.202505221210-rReproduction
Writing objects: 100%; server sends nothing backActual behavior
The client finishes writing the pack normally (
Writing objects: 100% ... done) and then receives zero further protocol response. Server-side, the request thread is permanently parked in a blockingInputStream.read()insidePackParser.whole()— confirmed via repeated thread dumps (kill -QUIT, ~60s apart) showing the identical stack with zero progress between samples.ReceivePacknever throws, never times out on its own, and never completes. The hang only ends when something external to JGit intervenes — in our testing, either the embedding SSH server's own idle timer (10 minutes) or a reverse proxy's read timeout — at which point JGit merely discovers, when it finally tries to write its status report, that the connection is gone (channel already closed/ equivalent).Expected behavior
ReceivePack.receive()should complete successfully for a well-formed pack of this size and shape (it does for smaller/equivalent packs), or fail fast with an exception if something about the input is actually invalid. It should never block indefinitely with no internal timeout and no forward progress, regardless of pack size.Relevant log output
Other information
jstat -gcutilflat throughoutgit push --no-thinreproduces identicallyBisection, splitting one failing push's exact content in half:
Neither half is malformed or unusual on its own. Only the combined ~104 KiB pack hangs — pointing at a size-dependent condition in
PackParser/ObjectDirectoryPackParser(buffer sizing or a length-accounting boundary), not a specific bad object or encoding.Not yet done: a minimal, JGit-only reproduction (e.g. a unit test driving
ReceivePackdirectly over aPipedInputStream/PipedOutputStreampair with a synthetic pack, no real server involved) — everything above comes from a real GitBucket deployment. Happy to attempt that, or to share the actual.packfile that reproduces it, if either would help.