Skip to content

Commit 2c0540f

Browse files
authored
fix(backend): Limit concurrent git operations to prevent resource exhaustion (#590) (#593)
When syncing generic-git-host connections with thousands of repositories, unbounded Promise.all caused resource exhaustion (EAGAIN errors) by spawning too many concurrent git processes. This resulted in valid repositories being incorrectly skipped during sync. - Add p-limit to control concurrent git operations (max 100) - Follow existing pattern from github.ts for consistency - Prevents file descriptor and process limit exhaustion - Uses rolling concurrency to avoid head-of-line blocking Fixes #590
1 parent d1655d4 commit 2c0540f

File tree

2 files changed

+9
-2
lines changed

2 files changed

+9
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Fixed "The account is already associated with another user" errors with GitLab oauth provider. [#584](https://github.com/sourcebot-dev/sourcebot/pull/584)
1717
- Fixed error when viewing a generic git connection in `/settings/connections`. [#588](https://github.com/sourcebot-dev/sourcebot/pull/588)
1818
- Fixed issue with an unbounded `Promise.allSettled(...)` when retrieving details from the GitHub API about a large number of repositories (or orgs or users). [#591](https://github.com/sourcebot-dev/sourcebot/pull/591)
19+
- Fixed resource exhaustion (EAGAIN errors) when syncing generic-git-host connections with thousands of repositories. [#593](https://github.com/sourcebot-dev/sourcebot/pull/593)
1920

2021
## Removed
2122
- Removed built-in secret manager. [#592](https://github.com/sourcebot-dev/sourcebot/pull/592)

packages/backend/src/repoCompileUtils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ import assert from 'assert';
2020
import GitUrlParse from 'git-url-parse';
2121
import { RepoMetadata } from '@sourcebot/shared';
2222
import { SINGLE_TENANT_ORG_ID } from './constants.js';
23+
import pLimit from 'p-limit';
2324

2425
export type RepoData = WithRequired<Prisma.RepoCreateInput, 'connections'>;
2526

2627
const logger = createLogger('repo-compile-utils');
2728

29+
// Limit concurrent git operations to prevent resource exhaustion (EAGAIN errors)
30+
// when processing thousands of repositories simultaneously
31+
const MAX_CONCURRENT_GIT_OPERATIONS = 100;
32+
const gitOperationLimit = pLimit(MAX_CONCURRENT_GIT_OPERATIONS);
33+
2834
type CompileResult = {
2935
repoData: RepoData[],
3036
warnings: string[],
@@ -472,7 +478,7 @@ export const compileGenericGitHostConfig_file = async (
472478
const repos: RepoData[] = [];
473479
const warnings: string[] = [];
474480

475-
await Promise.all(repoPaths.map(async (repoPath) => {
481+
await Promise.all(repoPaths.map((repoPath) => gitOperationLimit(async () => {
476482
const isGitRepo = await isPathAValidGitRepoRoot({
477483
path: repoPath,
478484
});
@@ -526,7 +532,7 @@ export const compileGenericGitHostConfig_file = async (
526532
}
527533

528534
repos.push(repo);
529-
}));
535+
})));
530536

531537
return {
532538
repoData: repos,

0 commit comments

Comments
 (0)