Commit b4a55a2
committed
fix(folders): lock the full ancestor closure in one ordered statement
Two more findings this round:
1. Greptile (correctly pushing back on the previous round's fix): sorting
the *starting* ids before calling assertFolderMutable on each doesn't
actually prevent deadlocks in a reorder batch. Each call still walks and
locks its own ancestor chain one row at a time, and two concurrent
batches whose chains interleave differently (e.g. one locks folder A
then their shared ancestor C, the other locks folder B then also wants
C) can still each end up holding a row the other needs. Sorting the
starting ids only orders the entry points, not the actual sequence of
rows locked.
The only way to make batch-vs-batch lock acquisition deadlock-safe is to
compute the full set of rows that need locking up front and lock all of
them in a single statement, ordered by id. Replaced the per-id
assertFolderMutable loop in performReorderFolders with: a read-only
breadth-first walk (no locks) to collect the full ancestor closure of
every folder being reordered plus every target parent, then one
`WHERE id IN (closure) ORDER BY id FOR UPDATE` statement that locks the
entire closure atomically. Concurrent batches that do the same always
converge on the same acquisition order and cannot form a cycle.
2. Cursor: the folder-duplicate route's assertTargetParentFolderMutable
walks the target parent's ancestor chain with the same
pattern the shared engine had before the previous round's fix -- a plain
SELECT, not SELECT ... FOR UPDATE, so a parent could still be locked
between this read and the new folder row being inserted. This one is a
single linear ancestor walk (not a multi-root batch), so it doesn't have
the deadlock risk above -- just needed the same FOR UPDATE fix already
applied to the shared engine.
Updated the reorder route's tests for the new locking shape (a closure walk
plus one ordered lock read instead of N assertFolderMutable calls), added a
regression test for locking an ancestor beyond the batch's explicit ids,
and fixed the duplicate route's test mock to support the FOR UPDATE chain.
Full local verification passed (typecheck, full vitest suite 11431 tests,
check:api-validation, lint all clean).1 parent 056e33c commit b4a55a2
4 files changed
Lines changed: 125 additions & 27 deletions
File tree
- apps/sim
- app/api/folders
- [id]/duplicate
- reorder
- lib/folders
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
25 | 30 | | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
| 31 | + | |
| 32 | + | |
31 | 33 | | |
32 | 34 | | |
33 | 35 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
269 | 269 | | |
270 | 270 | | |
271 | 271 | | |
| 272 | + | |
| 273 | + | |
272 | 274 | | |
273 | 275 | | |
274 | 276 | | |
| |||
280 | 282 | | |
281 | 283 | | |
282 | 284 | | |
| 285 | + | |
283 | 286 | | |
284 | 287 | | |
285 | 288 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
| |||
49 | 48 | | |
50 | 49 | | |
51 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
52 | 75 | | |
53 | 76 | | |
54 | 77 | | |
| |||
67 | 90 | | |
68 | 91 | | |
69 | 92 | | |
70 | | - | |
71 | | - | |
72 | | - | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
73 | 96 | | |
74 | 97 | | |
75 | 98 | | |
| |||
302 | 325 | | |
303 | 326 | | |
304 | 327 | | |
305 | | - | |
306 | | - | |
| 328 | + | |
| 329 | + | |
307 | 330 | | |
308 | 331 | | |
309 | 332 | | |
310 | 333 | | |
311 | 334 | | |
312 | 335 | | |
313 | 336 | | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
| 337 | + | |
318 | 338 | | |
319 | 339 | | |
320 | 340 | | |
| |||
324 | 344 | | |
325 | 345 | | |
326 | 346 | | |
327 | | - | |
328 | 347 | | |
329 | 348 | | |
330 | 349 | | |
331 | 350 | | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
332 | 386 | | |
333 | 387 | | |
334 | 388 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
872 | 872 | | |
873 | 873 | | |
874 | 874 | | |
875 | | - | |
876 | | - | |
| 875 | + | |
| 876 | + | |
877 | 877 | | |
878 | | - | |
879 | | - | |
880 | | - | |
881 | | - | |
882 | | - | |
883 | | - | |
884 | | - | |
885 | | - | |
886 | | - | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
887 | 926 | | |
888 | 927 | | |
889 | 928 | | |
| |||
0 commit comments