Skip to content

Await asyncs of components deleted before graceful shutdown - #4401

Open
rdlu wants to merge 2 commits into
phoenixframework:mainfrom
rdlu:await-asyncs-of-deleted-components
Open

Await asyncs of components deleted before graceful shutdown#4401
rdlu wants to merge 2 commits into
phoenixframework:mainfrom
rdlu:await-asyncs-of-deleted-components

Conversation

@rdlu

@rdlu rdlu commented Aug 21, 2026

Copy link
Copy Markdown

Fixes #4400.

When components are deleted (delete_components/2 in channel.ex, i.e. on the client's cids_destroyed ack), their in-flight async pids are collected via the existing socket_asyncs/2 into a new orphaned_asyncs channel-state map and monitored; a dedicated handle_info :DOWN clause prunes entries (and keeps these monitor messages out of user handle_info/2). all_asyncs/1 merges the orphans, so graceful shutdown (await_asyncs/1) awaits them and LiveViewTest.render_async/2 waits for them too.

Notes for review:

  • Deliberately no cancel-at-delete: killing the task at deletion just moves the mid-query tear-down earlier — Ensure async tasks can exit gracefully in LiveViewTest #4348's purpose is to let the work finish. Orphaned results are still discarded as today (write_component:errorpush_noop); only the shutdown/await bookkeeping changes.
  • render_async/2 now also waits for asyncs of removed components (they share async_pids). We think that's a fix — previously a test could return while such a task was still writing to the DB — but it is an observable change; happy to narrow it to terminate/2 only if preferred.
  • A separate Process.monitor/1 is needed for pruning because the monitor set up by Async.run_async_task/5 uses alias: :reply_demonitor and emits no :DOWN on successful completion.
  • Regression tests live next to the existing "test supervisor shutdown awaits async tasks" test in test/phoenix_live_view/integrations/start_async_test.exs, using the [:phoenix, :live_component, :destroyed] telemetry event to deterministically wait for the actual delete_component (the client acks cids_destroyed asynchronously). Verified: new tests red on unpatched main, full suite green after the fix, and the single-file repro from the issue flips exactly as expected against the patched tree.

Disclosure: this fix was developed with AI assistance — Claude Fable 5 (analysis/orchestration) with Claude Opus 5 subagents (implementation), directed and reviewed by a human.

rdlu added 2 commits August 21, 2026 14:49
A LiveComponent that leaves the tree while one of its start_async tasks is
still running has its task killed mid-flight on a graceful shutdown, instead
of being awaited like the asyncs of the LiveView and of live components.
Diff.delete_component/2 drops a component from the state without touching the
async tasks it started, which made those tasks invisible to await_asyncs/1 on
a graceful shutdown: instead of being awaited, they were killed mid-flight by
their link to the channel process.

Keep tracking the pids of a removed component's asyncs in the channel state
and monitor them, so all_asyncs/1 still sees them and the entries are dropped
once the tasks are done.
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.

Graceful shutdown does not await start_async tasks of a component deleted from the tree

1 participant