Commit c31de60
authored
fix(queue): dedupe concurrent AI reviews for the same PR head (#2429)
* fix(queue): dedupe concurrent AI reviews for the same PR head
A webhook pass and an agent-regate-pr sweep pass can independently miss
the AI review cache for the same (repo, PR, head SHA, mode) and each
fire a real LLM call, potentially landing different verdicts at the
same head. Add a per-key advisory lock (mirroring the existing
per-PR maintenance lock) around the review call; a pass that loses the
race returns the existing ai_review_inconclusive shape so the gate
holds for human review instead of racing an independent verdict.
* fix(queue): close the concurrent-claim race in the transient-lock get/set fallback
claimAiReviewLock's fallback path (used when the cache adapter has no
atomic claim()) was a plain get-then-set pair: two concurrent callers
can both observe an absent key before either writes, and both believe
they claimed the lock -- defeating the dedupe guarantee for exactly
the double-LLM-call race this lock exists to prevent. The same latent
gap already existed in the sibling claimAgentMaintenanceLock fallback.
Extract a shared claimTransientLock helper used by both locks. Its
fallback now writes a token unique to the attempt, then reads the key
back -- since a correctly-behaved key-value store serializes writes to
a single key, only the caller whose token survives the final read
actually won; every other concurrent caller reads a different (later)
token and correctly backs off. Still fails open on a missing cache or
any read/write error, matching the existing defense-in-depth design.
* fix(queue): stop pretending the transient-lock fallback can serialize without atomic claim()
The prior fallback (write a unique token, then re-read to verify) does
not close the race: caller A can write its token, read it back, and
return true entirely before caller B's later write/read also returns
true -- both callers can still "win" under real concurrent
interleavings, which defeats the whole point of the lock.
There is no way to build real mutual exclusion out of separate
get/set calls without an atomic primitive. Rather than keep pretending
to serialize, claimTransientLock now requires the cache adapter's
native claim() for any exclusivity at all; without it, every caller
proceeds (fail open), matching this lock family's existing
defense-in-depth philosophy. Self-host's Redis-backed cache always
implements claim(), so this is a documented limitation for a
hypothetical future adapter, not a live production gap.1 parent 7e3e20e commit c31de60
4 files changed
Lines changed: 424 additions & 26 deletions
File tree
- src/queue
- test/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2446 | 2446 | | |
2447 | 2447 | | |
2448 | 2448 | | |
| 2449 | + | |
| 2450 | + | |
| 2451 | + | |
| 2452 | + | |
| 2453 | + | |
| 2454 | + | |
| 2455 | + | |
| 2456 | + | |
| 2457 | + | |
| 2458 | + | |
| 2459 | + | |
| 2460 | + | |
| 2461 | + | |
| 2462 | + | |
| 2463 | + | |
| 2464 | + | |
| 2465 | + | |
| 2466 | + | |
| 2467 | + | |
| 2468 | + | |
| 2469 | + | |
| 2470 | + | |
| 2471 | + | |
| 2472 | + | |
| 2473 | + | |
| 2474 | + | |
| 2475 | + | |
| 2476 | + | |
2449 | 2477 | | |
2450 | 2478 | | |
2451 | 2479 | | |
| |||
2466 | 2494 | | |
2467 | 2495 | | |
2468 | 2496 | | |
2469 | | - | |
2470 | | - | |
2471 | | - | |
2472 | | - | |
2473 | | - | |
2474 | | - | |
2475 | | - | |
2476 | | - | |
2477 | | - | |
2478 | | - | |
2479 | | - | |
2480 | | - | |
2481 | | - | |
2482 | | - | |
2483 | | - | |
2484 | | - | |
2485 | | - | |
2486 | | - | |
2487 | | - | |
| 2497 | + | |
| 2498 | + | |
| 2499 | + | |
| 2500 | + | |
| 2501 | + | |
2488 | 2502 | | |
2489 | 2503 | | |
2490 | 2504 | | |
| |||
2502 | 2516 | | |
2503 | 2517 | | |
2504 | 2518 | | |
| 2519 | + | |
| 2520 | + | |
| 2521 | + | |
| 2522 | + | |
| 2523 | + | |
| 2524 | + | |
| 2525 | + | |
| 2526 | + | |
| 2527 | + | |
| 2528 | + | |
| 2529 | + | |
| 2530 | + | |
| 2531 | + | |
| 2532 | + | |
| 2533 | + | |
| 2534 | + | |
| 2535 | + | |
| 2536 | + | |
| 2537 | + | |
| 2538 | + | |
| 2539 | + | |
| 2540 | + | |
| 2541 | + | |
| 2542 | + | |
| 2543 | + | |
| 2544 | + | |
| 2545 | + | |
| 2546 | + | |
| 2547 | + | |
| 2548 | + | |
| 2549 | + | |
| 2550 | + | |
| 2551 | + | |
| 2552 | + | |
| 2553 | + | |
| 2554 | + | |
| 2555 | + | |
| 2556 | + | |
| 2557 | + | |
| 2558 | + | |
| 2559 | + | |
| 2560 | + | |
| 2561 | + | |
| 2562 | + | |
| 2563 | + | |
| 2564 | + | |
| 2565 | + | |
| 2566 | + | |
2505 | 2567 | | |
2506 | 2568 | | |
2507 | 2569 | | |
| |||
4677 | 4739 | | |
4678 | 4740 | | |
4679 | 4741 | | |
| 4742 | + | |
| 4743 | + | |
| 4744 | + | |
| 4745 | + | |
| 4746 | + | |
| 4747 | + | |
| 4748 | + | |
| 4749 | + | |
| 4750 | + | |
| 4751 | + | |
| 4752 | + | |
| 4753 | + | |
| 4754 | + | |
| 4755 | + | |
| 4756 | + | |
| 4757 | + | |
| 4758 | + | |
| 4759 | + | |
| 4760 | + | |
| 4761 | + | |
| 4762 | + | |
| 4763 | + | |
| 4764 | + | |
| 4765 | + | |
| 4766 | + | |
| 4767 | + | |
| 4768 | + | |
| 4769 | + | |
| 4770 | + | |
| 4771 | + | |
| 4772 | + | |
| 4773 | + | |
| 4774 | + | |
4680 | 4775 | | |
4681 | 4776 | | |
4682 | 4777 | | |
| |||
4968 | 5063 | | |
4969 | 5064 | | |
4970 | 5065 | | |
| 5066 | + | |
| 5067 | + | |
| 5068 | + | |
| 5069 | + | |
| 5070 | + | |
| 5071 | + | |
| 5072 | + | |
| 5073 | + | |
4971 | 5074 | | |
4972 | 5075 | | |
4973 | 5076 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
524 | 524 | | |
525 | 525 | | |
526 | 526 | | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
527 | 559 | | |
528 | 560 | | |
529 | 561 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
134 | 174 | | |
135 | 175 | | |
136 | 176 | | |
| |||
0 commit comments