main
tag: launch-2026-06-01
tag: magicmirror-v0.1.1
tag: magicmirror-v0.3.0
tag: mnw-cli-v0.1.2
tag: mnw-cli-v0.1.3
tag: mnw-cli-v0.1.4
tag: pom-v0.4.1
tag: pom-v0.4.2
tag: pom-v0.4.3
tag: pom-v0.4.4
tag: pom-v0.4.5
tag: wam-v0.3.0
tag: wam-v0.3.1
Files
Commits
Tags
Notes
Issues
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()