Skip to content

ReceivePack/PackParser hangs indefinitely (blocked in InputStream.read) on packs above ~55KB — no exception, no timeout, transport-independent #286

Description

@brmiller

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

  1. 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)
  2. Client finishes Writing objects: 100%; server sends nothing back
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions