Skip to content

fix: prevent LiveDashboard from showing stale migration status under concurrent access - #3

Merged
dbernheisel merged 3 commits into
mainfrom
tw/fix-live-dashboard-concurrent-mounts
Jul 17, 2026
Merged

fix: prevent LiveDashboard from showing stale migration status under concurrent access#3
dbernheisel merged 3 commits into
mainfrom
tw/fix-live-dashboard-concurrent-mounts

Conversation

@tw00

@tw00 tw00 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes two related bugs in DataMigration.LiveDashboard.Page that cause the dashboard to intermittently show a migration as down even 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/1 accumulated 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_term cache 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/2 used Code.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 — gets nil back instead of the compiled module list. The caller treated nil as "failed to compile" and silently dropped that 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 via Code.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

  • New regression test in 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 via git 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 on main are unrelated to this change, from a local Elixir version newer than CI's pinned 1.18).

…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>
@tw00
tw00 requested a review from dbernheisel July 17, 2026 02:54
tw00 and others added 2 commits July 16, 2026 22:56
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
dbernheisel merged commit 2ca816f into main Jul 17, 2026
1 check passed
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.

2 participants