fix(queue): bound reconcileLiveDuplicateSiblings' live per-sibling fan-out#7610
fix(queue): bound reconcileLiveDuplicateSiblings' live per-sibling fan-out#7610glorydavid03023 wants to merge 1 commit into
Conversation
…n-out (JSONbored#5835) reconcileLiveDuplicateSiblings fired one live fetchLivePullRequestState per overlapping duplicate sibling through an unbounded `Promise.all(overlapping.map(...))`. A popular linked issue can hold dozens of duplicate PRs, so a single webhook delivery could burst that many simultaneous GitHub REST calls against the ONE ~5000/hr bucket every managed repo shares through one installation -- exactly the "many live per-item GitHub reads from one delivery" shape processors.ts already bounds twice (CONTRIBUTOR_CAP_LIVE_CHECK_CONCURRENCY and GLOBAL_OPEN_ITEM_LIVE_CHECK_CONCURRENCY, both 10). The fan-out now runs through mapWithConcurrency at DUPLICATE_SIBLING_LIVE_CHECK_CONCURRENCY = 10, matching those two so the three bounded fan-outs stay consistent. Every sibling is still verified -- the cap bounds concurrency, not coverage -- so the reconcile's fail-open semantics are unchanged. mapWithConcurrency moves from processors.ts to a new src/queue/concurrency.ts rather than being imported across: duplicate-detection.ts deliberately does not import processors.ts, and its own header records why (it inlines an admission-key wrapper for the same reason -- importing back would make the two circularly dependent). A neutral module both can depend on shares the helper without reintroducing that cycle or growing a third private copy of the same worker-pool loop. processors.ts re-exports it, so its existing surface is unchanged -- the same shim shape it already uses for transient-locks.ts and signal-snapshot.ts. Tested: a regression test drives 40 overlapping siblings and asserts peak in-flight fetches is exactly 10 (it was 40) while all 40 are still verified and the result is unchanged; plus direct branch coverage for the moved helper, including order preservation when mappers finish out of order, the empty-input case, and the clamp that keeps a zero/negative concurrency from hanging on a zero-worker pool.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Caution 🛑 LoopOver review result - reject/close recommendedReview updated: 2026-07-21 01:32:58 UTC
Review summary Nits — 3 non-blocking
Why this is blocked
📋 Copy for AI agents — paste into your coding agentDecision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
LoopOver is closing this pull request on the maintainer's behalf (conflicts with the base branch — resolve and open a fresh PR; No linked issue detected). This is an automated maintenance action — to pursue this change, please open a new pull request with the issues resolved. Closed PRs may be analyzed later to improve review accuracy, but they are not automatically reopened or re-reviewed. |
Closes #5835
Summary
reconcileLiveDuplicateSiblings(src/queue/duplicate-detection.ts) live-reconciles a PR's duplicate-cluster siblings before the winner is elected. When a PR has overlapping open siblings sharing a linked issue, it fired one live GitHub REST call per sibling through an unboundedPromise.all(overlapping.map(...))— every open sibling got a simultaneousfetchLivePullRequestStatein a single pass.A popular linked issue can accumulate dozens of duplicate PRs, so one webhook delivery could burst that many concurrent REST calls against the one ~5000/hr bucket every managed repo shares through a single installation — exactly the "many live per-item GitHub reads from one delivery" pressure the module's own doc comments already worry about.
The fix
The fan-out now runs through
mapWithConcurrencyatDUPLICATE_SIBLING_LIVE_CHECK_CONCURRENCY = 10. That's the same boundsrc/queue/processors.ts— the fileduplicate-detection.tswas extracted from — already applies to the two comparable per-item live-check fan-outs:CONTRIBUTOR_CAP_LIVE_CHECK_CONCURRENCY = 10GLOBAL_OPEN_ITEM_LIVE_CHECK_CONCURRENCY = 10So all three bounded fan-outs now use one consistent number rather than each picking their own. Every sibling is still verified — the cap bounds concurrency, not coverage — so the reconcile's fail-open semantics (a sibling is dropped only on a positive "not open" confirmation) are entirely unchanged.
Why
mapWithConcurrencymoved to a new moduleThe naive fix — import
mapWithConcurrencyfromprocessors.ts— would create a circular import.duplicate-detection.tsdeliberately does not importprocessors.ts, and its own header comment records why: it inlines an admission-key wrapper rather than importing it back, precisely because "importing it back would have made this file andprocessors.tscircularly dependent on each other."So the helper moves to a neutral
src/queue/concurrency.tsthat both files depend on — sharing it without reintroducing the cycle, and without growing a third private copy of the same worker-pool loop.processors.tsre-exports it, so its existing public surface is byte-identical. This is the same shim shapeprocessors.tsalready uses fortransient-locks.tsandsignal-snapshot.ts.Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run typechecknpm run test:coveragelocally;codecov/patchrequires >=99% coverage of the lines AND branches changednpm run engine-parity:drift-checknpm run docs:drift-checknpm run selfhost:validate-observabilitynpm audit --audit-level=moderatePatch coverage: 100% (11/11 executable changed lines, zero partial branches), measured by intersecting the lcov report against this diff's added lines.
No behavior change: the 890 tests in the existing queue suites (
queue,queue-2…queue-5,queue-lifecycle-guards,reconcile-live-duplicate-siblings) pass unchanged.The regression test (
reconcile-live-duplicate-siblings.test.ts) drives 40 overlapping siblings and asserts peak simultaneous fetches is exactly 10 (it was 40 before), that all 40 are still verified, and that the result is identical to the unbounded version. Newqueue-concurrency.test.tsgives the moved helper direct branch coverage: the concurrency cap is honoured and saturated, input order is preserved when mappers finish out of order, empty input resolves without calling the mapper, a zero/negative concurrency is clamped to a single worker (so a zero-worker pool can't hang), and a mapper rejection propagates.If any required check was skipped, explain why:
Safety
UI Evidencesection. Not applicable — backend only.UI Evidence
Not applicable — this is a backend queue/rate-limit change. No UI, frontend, docs, or extension surface is touched.
Notes
processors.tsalready use, so a future change to the installation REST budget can be reasoned about in one place.