Cap git upload-pack child lifetime to bound permit starvation
A client that holds the connection open without reading makes git
upload-pack block on a full stdout pipe and never exit, pinning its
concurrency permit indefinitely; enough slow clients starve all clones at
the handshake. Kill the child past GIT_SMART_HTTP_TIMEOUT_SECS (300s,
generous for a large repo on a slow link) so the permit is always freed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed,
+16 insertions,
-2 deletions
| 250 |
250 |
|
// upload-pack` child and streams a packfile; this bounds the process fan-out so
|
| 251 |
251 |
|
// a burst of clones on a large repo can't exhaust processes/memory.
|
| 252 |
252 |
|
pub const GIT_SMART_HTTP_MAX_CONCURRENT: usize = 8;
|
|
253 |
+ |
// Hard ceiling on how long a single clone's `git upload-pack` child may run
|
|
254 |
+ |
// while holding its concurrency permit. A client that stops reading makes git
|
|
255 |
+ |
// block on a full stdout pipe and never exit, pinning the permit; killing it
|
|
256 |
+ |
// past this deadline keeps slow/stuck clones from starving the budget. Generous
|
|
257 |
+ |
// enough for a large repo over a slow link.
|
|
258 |
+ |
pub const GIT_SMART_HTTP_TIMEOUT_SECS: u64 = 300;
|
| 253 |
259 |
|
|
| 254 |
260 |
|
// -- Webhook security --
|
| 255 |
261 |
|
pub const WEBHOOK_TIMESTAMP_TOLERANCE_SECS: u64 = 300; // 5 minutes
|
| 209 |
209 |
|
|
| 210 |
210 |
|
// Reap the child and release the permit once it exits. If the client
|
| 211 |
211 |
|
// disconnects, the response body (and `stdout`) drops, git gets SIGPIPE and
|
| 212 |
|
- |
// exits, so `wait()` returns and the permit is freed — no leak either way.
|
|
212 |
+ |
// exits, so `wait()` returns and the permit is freed. A client that holds
|
|
213 |
+ |
// the connection open without reading would otherwise make git block on a
|
|
214 |
+ |
// full stdout pipe and never exit, pinning the permit indefinitely — so cap
|
|
215 |
+ |
// the wait and kill the child past a generous deadline, bounding the
|
|
216 |
+ |
// slow-client permit-starvation window (Run #21 Performance).
|
| 213 |
217 |
|
tokio::spawn(async move {
|
| 214 |
218 |
|
let _permit = permit;
|
| 215 |
|
- |
let _ = child.wait().await;
|
|
219 |
+ |
let deadline = std::time::Duration::from_secs(constants::GIT_SMART_HTTP_TIMEOUT_SECS);
|
|
220 |
+ |
if tokio::time::timeout(deadline, child.wait()).await.is_err() {
|
|
221 |
+ |
let _ = child.start_kill();
|
|
222 |
+ |
let _ = child.wait().await;
|
|
223 |
+ |
}
|
| 216 |
224 |
|
});
|
| 217 |
225 |
|
|
| 218 |
226 |
|
Response::builder()
|