Skip to content

Make julialauncher config reads lock-free - #1533

Merged
IanButterworth merged 2 commits into
mainfrom
ib/lockfree-launcher
Jun 11, 2026
Merged

Make julialauncher config reads lock-free#1533
IanButterworth merged 2 commits into
mainfrom
ib/lockfree-launcher

Conversation

@IanButterworth

@IanButterworth IanButterworth commented Jun 11, 2026

Copy link
Copy Markdown
Member

Claude:


Follow-up to #1517. That PR shrank how long the exclusive configuration lock is held; this one removes the launcher from the lock contention picture entirely: launching julia can no longer block on the configuration lock, ever.

Why this is safe

The launcher only ever took a shared lock, inside load_config_db, and released it before resolving the binary path and exec'ing Julia. The only thing that lock bought was a guarantee of not reading a half-written juliaup.json. Since all config writes go through an atomic temp-file + rename (save_config_db), a lock-free reader always sees either the old or the new file in full — no lock needed.

One hole had to be closed to make that invariant total: the initial config file was created by opening with create(true) and writing JSON in place, so a lock-free reader racing first-time setup could observe an empty or partial file. The initial config is now also created via temp-file + rename (create_initial_config_file), so an incomplete juliaup.json can never be observed on disk.

Changes

  • New load_config_db_lockfree; julialauncher uses it for both config reads (startup and post-auto-install reload). The locked load_config_db is unchanged for all juliaup commands.
  • load_mut_config_db creates the initial config atomically instead of writing in place (it still holds the exclusive lock while doing so).
  • A zero-length config file (possible leftover from an interrupted initial setup by an older juliaup) is treated like a missing file rather than a parse error.
  • devdocs/locking_and_update_flows.md documents the lock-free read path and adds the design rule that all juliaup.json writes must be atomic replacements, since that invariant is now load-bearing.
  • Unit tests, including a regression test that a lock-free read succeeds while the exclusive lock is held.

What this does and doesn't fix

With this change the launcher cannot be stalled even by the remaining long exclusive-lock holders (remove/gc deleting a large install under the lock). It does not change the situation in #400 (an in-use installation being deleted) — but it also doesn't meaningfully widen that race: the lock was already released before exec, so the unprotected window between config read and launch existed before and is unchanged. #400 remains the complementary fix, planned separately along with moving directory deletion out from under the exclusive lock (rename-to-trash).

🤖 Generated with Claude Code

Launching julia could previously stall on the configuration lock
whenever another process held the exclusive lock (e.g. a `remove`/`gc`
deleting a large installation). The launcher only ever used its shared
lock to get a consistent snapshot of juliaup.json — and since all
config writes now replace the file atomically (temp file + rename),
that snapshot consistency no longer needs a lock at all.

- Add `load_config_db_lockfree` and switch julialauncher to it, so
  launching Julia can never block on the configuration lock.
- Create the initial juliaup.json atomically (temp file + rename)
  instead of writing it in place, closing the one window where a
  lock-free reader could observe an empty/partial config file.
- Treat a zero-length config file (leftover from an interrupted
  initial setup of an older juliaup) like a missing one instead of
  failing to parse it.
- Document the lock-free read path and the "config writes must be
  atomic" invariant in devdocs/locking_and_update_flows.md.
- Add unit tests, including a regression test that a lock-free read
  succeeds while the exclusive lock is held.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@topolarity

topolarity commented Jun 11, 2026

Copy link
Copy Markdown
Member

Hooray! You may need to use FILE_RENAME_FLAG_POSIX_SEMANTICS to avoid readers blocking the rename on Windows and causing persist() to fail (open read handles on the destination file block renames on Windows by default)

@davidanthoff davidanthoff left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

tempfile's persist() renames via MoveFileExW, which fails while any
process has the destination file open. With lock-free launcher reads a
writer's commit can now overlap a reader holding juliaup.json open, so
a commit could transiently fail on Windows.

Route all config-file replacements through std::fs::rename instead,
which on Windows renames with FILE_RENAME_FLAG_POSIX_SEMANTICS |
FILE_RENAME_FLAG_REPLACE_IF_EXISTS (falling back to MoveFileExW only
on filesystems without support), so the replacement succeeds while
readers still hold the old file open. On Unix this is the same
rename(2) as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@IanButterworth

Copy link
Copy Markdown
Member Author

@topolarity look good now?

@topolarity

Copy link
Copy Markdown
Member

Yes, I think that should resolve it!

Only other thought is that despite what the OP says w.r.t. #400, it does seem like the new behavior makes that window somewhat worse since you will no longer be blocked from loading an install during the time that its files are being deleted

But that may be handled best in a follow-up tackling that specific issue?

@IanButterworth

Copy link
Copy Markdown
Member Author

Yeah. I do think we want to close #400 but the issue with vscode poking through the juliaup api to binaries directly makes it harder to solve. Need @davidanthoff's input there.

We can merge this for now at least.

@IanButterworth
IanButterworth merged commit 2879f1c into main Jun 11, 2026
31 checks passed
@IanButterworth
IanButterworth deleted the ib/lockfree-launcher branch June 11, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants