Commit 93c6995
committed
fix(folders): fail safe when the reorder closure goes stale under lock
Greptile P1: the cycle-check fix's closure walk discovers ancestors with
plain reads before the FOR UPDATE lock is acquired. If a concurrent
transaction reparents one of those discovered members to point outside the
closure in that gap, the locked read reveals a parentId the cycle walk has
no data for. The walk's `?? null` fallback treated "not in closure" the
same as "reached root", so it could silently stop short of a real cycle
instead of detecting it -- e.g. A moves under B, the locked read shows a
concurrently-committed B -> C, and C already points back to A; since C was
never discovered by the pre-lock walk, the walk incorrectly terminates at B
instead of continuing to C and finding the cycle back to A.
A fully general fix would need to lock-and-expand the closure in rounds
until it's provably closed under FOR UPDATE, which reintroduces real
deadlock-ordering complexity. Took the simpler, still-correct path instead:
after acquiring the lock, verify every locked row's parentId is either null
or already a member of the locked closure. If not, the closure went stale
between discovery and locking -- fail the whole batch (errorCode: conflict,
409) rather than risk running the cycle walk on data it can't fully vouch
for. The client can retry, and a second attempt's closure walk will see the
now-settled state.
Added a regression test reproducing the exact scenario Greptile described.
Full local verification passed (typecheck, full vitest suite 11438 tests,
check:api-validation, lint all clean, 303 tests across the folders/locking
surface).1 parent 9f28f57 commit 93c6995
2 files changed
Lines changed: 62 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
426 | 426 | | |
427 | 427 | | |
428 | 428 | | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
429 | 474 | | |
430 | 475 | | |
431 | 476 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
936 | 936 | | |
937 | 937 | | |
938 | 938 | | |
| 939 | + | |
| 940 | + | |
939 | 941 | | |
940 | 942 | | |
941 | 943 | | |
| |||
1078 | 1080 | | |
1079 | 1081 | | |
1080 | 1082 | | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
1081 | 1095 | | |
1082 | 1096 | | |
1083 | 1097 | | |
| |||
1137 | 1151 | | |
1138 | 1152 | | |
1139 | 1153 | | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
1140 | 1157 | | |
1141 | 1158 | | |
1142 | 1159 | | |
| |||
0 commit comments