Skip to content

Wind down spawned tasks when startup fails - #1086

Open
Jolah1 wants to merge 1 commit into
lightningdevkit:mainfrom
Jolah1:wind-down-tasks-on-startup-error
Open

Wind down spawned tasks when startup fails#1086
Jolah1 wants to merge 1 commit into
lightningdevkit:mainfrom
Jolah1:wind-down-tasks-on-startup-error

Conversation

@Jolah1

@Jolah1 Jolah1 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #1009.

Node::start spawns background tasks — wallet sync, RGS gossip, pathfinding scores — before it can still fail, most visibly when resolving or binding the configured listening addresses. The error arm only called chain_source.stop(), so those tasks kept running behind a node that never came up.

This extracts the wind-down sequence out of Node::stop into a private Node::shutdown and runs it on any start_inner error.

Reviewer note — relaxed assertions. Because the wind-down now also runs after a partial startup, it can no longer assume every task exists:

  • Both shutdown watch::Sender::send calls are allowed to find no receivers (previously log_error! + debug_assert!(false)).
  • Runtime::wait_on_background_tasks' debug_assert!(tasks.len() > 0) and Runtime::wait_on_background_processor_task' debug_assert!(false, "Expected a background processing task") are dropped, with doc comments in their place explaining that the empty case is now reachable by design.

I considered keeping the assertions by threading an expect_running: bool through shutdown(), but that trades a real invariant for a parameter the caller can get wrong, and the assertions only ever restated "we got here via stop". Happy to go the other way if you'd rather keep them.

Testing

  • tests/integration_tests_rust.rs::failed_start_winds_down_background_tasks — squats one of the node's listening addresses so bind fails after the wallet-sync and pathfinding-scores tasks are up, asserts via a CollectingLogWriter that the full shutdown sequence ran, then frees the address and confirms the node starts and stops cleanly.
  • src/runtime.rs::winding_down_without_spawned_tasks_is_a_noop — the runtime wind-down calls tolerate finding nothing to wait on.

This PR was written with the assistance of Claude Code.

`Node::start` spawns background tasks - wallet sync, RGS gossip,
pathfinding scores - before it can still fail, e.g. when resolving or
binding the configured listening addresses. Until now the error path only
stopped the chain source, leaving those tasks running behind a node that
never came up, and leaving the node in a state a subsequent `start` could
not cleanly recover from.

Extract the wind-down sequence from `Node::stop` into a `Node::shutdown`
helper and run it on any `start_inner` error. As the helper now also runs
after a partial startup, it can no longer assume that every task exists:
the two shutdown `watch::Sender::send` calls are allowed to find no
receivers, and the `debug_assert!`s in `Runtime::wait_on_background_tasks`
and `Runtime::wait_on_background_processor_task` that required a
fully-started node are dropped in favour of doc comments spelling out
that case.

Fixes lightningdevkit#1009.

This change was written with the assistance of Claude Code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUGgjhnpkCuFjsE3BjYAEx
@ldk-reviews-bot

ldk-reviews-bot commented Sep 3, 2026

Copy link
Copy Markdown

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

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.

Properly wind down all spawned tasks on any startup error

2 participants