Commit 140150f
cli: fix concurrent mops invocations clobbering shared scratch dirs (#532)
## Problem
Running two `mops` processes on the same project (e.g. an editor watcher
and `caffeine check --fix`, or two back-to-back invocations from a
script) intermittently failed with misleading errors:
- `.mops/.check-stable/new.most: No such file or directory` from `mops
check-stable`
- `EEXIST: file already exists, symlink ...` from `mops
check`/`build`/`check-stable` when `[canisters.<name>.migrations]` was
configured
Worse, `check-stable` would print a "Hint: You may need a migration"
suggestion (now removed in #531) that pointed users at the wrong cause
entirely.
## Root cause
Two scratch directories were shared across all `mops` processes for the
same project:
1. **`.mops/.check-stable/`** — `runStableCheck` did `rm -rf <dir>` →
`mkdir <dir>` at the start and `rm -rf <dir>` at the end. Two concurrent
runs would: A creates `new.most`, B's startup `rm -rf` deletes it, A's
subsequent `--stable-compatible` read fails.
2. **`<parent>/.migrations-<canister>/`** — `prepareMigrationArgs` (used
when migrations need a trimmed/`next`-merged staging dir) wiped the dir
and re-symlinked. Two concurrent runs raced on `symlinkSync` (EEXIST) or
on each other's wipe.
Both directories existed mostly to give moc a stable path to read from
during the invocation; they weren't actually shared state that needed to
be coordinated.
## Fix
Switch both directories to per-invocation unique paths created with
`fs.mkdtempSync`:
- `.mops/.check-stable/` → `.mops/.check-stable-XXXXXX/`
- `<parent>/.migrations-<canister>/` →
`<parent>/.migrations-<canister>-XXXXXX/`
Each process owns its own dir for its lifetime and removes it on exit.
No shared mutable state → no race possible by construction. Existing
`.gitignore` rules already cover both patterns (`.mops/` and
`.migrations-*/`).
Caller behavior is unchanged: `prepareMigrationArgs` still returns a
`cleanup()` callback that removes the staged dir in `finally`.
## Caveats
- A crashed process leaks its scratch dir (small — symlinks + a couple
of `.most`/`.wasm` files). Both locations are gitignored, so it's
cosmetic.
- Stale `.mops/.check-stable/` or `<parent>/.migrations-<canister>/`
directories from older releases aren't auto-cleaned. They'll stay until
the user removes them; harmless but visible in `git status` for projects
that don't ignore them.
## Test plan
- [x] Added regression test: 10 parallel `mops check-stable` invocations
against a fixture with `check-limit = 3` and 4 chain migrations (forces
both the check-stable scratch path and the migration staging `mkdtemp`
branch).
- [x] Existing migrate / check / build / check-stable suites pass
(103/103, 64 snapshots).
- [x] `npm run lint` + `npm run check` clean.
- [x] Empirical reproduction of both error symptoms before the fix; both
gone after.
- [ ] Reviewer sanity: trigger the original failure with two parallel
`mops check-stable` (or `caffeine check --fix` + an editor watcher) on a
project with `[migrations]` configured; confirm clean output.
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent dc66a27 commit 140150f
12 files changed
Lines changed: 126 additions & 14 deletions
File tree
- cli
- commands
- helpers
- tests
- check-stable/migrations-chain
- migrations
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
| 1 | + | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
21 | 24 | | |
22 | 25 | | |
23 | 26 | | |
| |||
189 | 192 | | |
190 | 193 | | |
191 | 194 | | |
192 | | - | |
193 | | - | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
194 | 199 | | |
195 | 200 | | |
196 | 201 | | |
197 | 202 | | |
198 | 203 | | |
199 | 204 | | |
200 | | - | |
| 205 | + | |
201 | 206 | | |
202 | 207 | | |
203 | 208 | | |
| |||
207 | 212 | | |
208 | 213 | | |
209 | 214 | | |
210 | | - | |
| 215 | + | |
211 | 216 | | |
212 | 217 | | |
213 | 218 | | |
| |||
246 | 251 | | |
247 | 252 | | |
248 | 253 | | |
249 | | - | |
| 254 | + | |
250 | 255 | | |
251 | 256 | | |
252 | 257 | | |
| |||
259 | 264 | | |
260 | 265 | | |
261 | 266 | | |
262 | | - | |
263 | | - | |
| 267 | + | |
264 | 268 | | |
265 | 269 | | |
266 | 270 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
193 | 194 | | |
194 | 195 | | |
195 | 196 | | |
196 | | - | |
197 | | - | |
198 | | - | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
199 | 202 | | |
200 | 203 | | |
201 | 204 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
89 | 109 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
Lines changed: 11 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
0 commit comments