AkurAI Build
Menu

AkurAI-Build / Pull requests / #1

Cap concurrent Smart HTTP git subprocesses via BlockingGit

Merged · fix/git-http-unbounded-blocking → main · stdio

git_http bypassed the BlockingGit semaphore, spawning run_git_http directly via spawn_blocking with no concurrency cap. A burst of clone/fetch/push requests against /git/* could fork unbounded git upload-pack/receive-pack subprocesses, which is what OOM-killed Titan (hundreds of git/git-upload-pack processes under one session exhausted 16GB RAM + 32GB swap, then the OOM killer took out systemd itself, requiring a hard reboot).

Fix: route git_http's blocking work through state.blocking_git like every other git-invoking endpoint, capping it at MAX_BLOCKING_GIT (4).

Changes

diff --git a/src/server.rs b/src/server.rs
index ddb1d6d..fa17140 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -2710,10 +2710,10 @@ async fn git_http(
     } else {
         Vec::new()
     };
-    let response = tokio::task::spawn_blocking(move || run_git_http(root, git_path, parts, body))
-        .await
-        .map_err(ApiError::internal)?
-        .map_err(ApiError::internal)?;
+    let response = state
+        .blocking_git
+        .run(move || run_git_http(root, git_path, parts, body).map_err(ApiError::internal))
+        .await?;
     if is_push && response.status().is_success() {
         audit_http(
             &state,

Reviews

Current changes approved.

reviewer · Approved

Approved. Routes git_http's blocking work through state.blocking_git.run, matching the six existing call sites (server.rs:1345, 1702, 2033, 2431, 3660, 3687) and capping concurrency at MAX_BLOCKING_GIT = 4. Verified independently: - BlockingGit::run acquires an owned semaphore permit before spawn_blocking and holds it inside the closure (`let _permit = permit;`), so the cap covers the whole subprocess lifetime rather than just scheduling. - `state` is borrowed, not moved, so the subsequent audit_http(&state, ...), state.hosted_root and refresh_pull_request_heads calls still compile and behave unchanged. - Error mapping is preserved: run_git_http's error is mapped with ApiError::internal inside the closure and the JoinError by BlockingGit::run, matching the previous double map_err. - The non-push read path is unchanged. This closes the last /git/* endpoint that bypassed the semaphore, which is the unbounded upload-pack/receive-pack fork path described in the body. Note for the merge queue, not a blocker: base_sha aa9bcaeb is stale (main is now at ec2d2d73), so this merges as a non-fast-forward.

Current changes

Merge queue

No changes waiting to merge.