fix: prevent LiveDashboard from showing stale migration status under concurrent access - #3
Merged
Merged
Conversation
…concurrent access
Two related bugs in DataMigration.LiveDashboard.Page:
1. list_data_migrations/1 accumulated entries into a list across calls
instead of keying by {repo, folder, id}, so a stale entry cached by
one LiveView session could shadow a fresh one computed by another
concurrent session on the same node.
2. compile_file/2 used Code.require_file/2, which is idempotent
VM-wide (not per-process): once any process on the node has required
a given file path, every later call — including from an unrelated
LiveView session — returns nil instead of the compiled module list.
The caller treated nil as a compile failure and silently dropped the
migration from the list. Switched to Code.compile_file/2, which
always recompiles and returns the module list, and suppressed the
resulting "redefining module" diagnostic.
Found while investigating a production report of the dashboard
reverting a migration's status to "down" after a page reload, even
though the migration had actually run successfully.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Calling Ecto.Migrator.down/3 directly from the test process (as opposed to via the LiveView process under test) crashed under the MyXQL adapter in CI with a DBConnection.ConnectionError / CaseClauseError inside the SQL Sandbox connection state machine — a MyXQL+Sandbox interaction quirk, not a bug in the fix itself. The call was redundant anyway: the SQL Sandbox already rolls back all writes, including migration state, at the end of every test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dbernheisel
approved these changes
Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two related bugs in
DataMigration.LiveDashboard.Pagethat cause the dashboard to intermittently show a migration asdowneven after it has successfully run — this surfaced in production as a page reload reverting a migration's status right after running it.list_data_migrations/1accumulated instead of replaced. It built the migration list by concatenating each call's results (data_migrations ++ acc) rather than keying by{repo, folder, id}. The:persistent_termcache is process-independent — shared by every LiveView session on the node — so a stale entry cached by one concurrent session could sit alongside (or shadow) a fresh entry computed by another, and the rendered list wasn't guaranteed to reflect the true current state. Fixed by keying the cache as a map and always replacing on recompute.compile_file/2usedCode.require_file/2, which is idempotent VM-wide, not per-process. Once any process on the node has required a given migration file path, every later call — including from a completely unrelated LiveView session — getsnilback instead of the compiled module list. The caller treatednilas "failed to compile" and silently dropped that migration from the list. Switched toCode.compile_file/2, which always recompiles and returns the module list, and suppressed the resulting "redefining module" diagnostic viaCode.with_diagnostics/1.Both are concurrency bugs: a single LiveView session in isolation won't reproduce them, but two sessions computing the list around the same time (e.g. two engineers with the dashboard open, or a reload racing a background poll) can.
Test plan
test/data_migration/live_dashboard/page_test.exs(describe "list_data_migrations/1 (regression)") drives a migration up through the same LiveView, then navigates back to the list and re-opens it, asserting no duplicate/stale row and the correct status — reproduces the bug reliably against the pre-fix code (confirmed viagit stash: fails ~4/5 seed runs unfixed, passes 15/15 seed runs fixed).mix test— full suite passes (22 tests), all 4 adapters green in CI (sqlite, pg, myxql, tds).mix format --check-formatted— clean.mix compile --force --warnings-as-errors— no new warnings introduced (pre-existing warnings onmainare unrelated to this change, from a local Elixir version newer than CI's pinned 1.18).