Skip to content

Fix async_tree double unpin#3287

Open
lrubasze wants to merge 3 commits into
mainfrom
lrubasze/fix-async-tree-double-unpin
Open

Fix async_tree double unpin#3287
lrubasze wants to merge 3 commits into
mainfrom
lrubasze/fix-async-tree-double-unpin

Conversation

@lrubasze

Copy link
Copy Markdown
Contributor

Fixes #3286

Problem

  • light-base panics in parachain sync: panicked at runtime_service.rs:493:34: called Result::unwrap() on an Err value: ().
  • Cause: sync_service/paraheads.rs unpins a relay block twice — once when its parahead fetch finishes, once when it's pruned.
  • The prune path only re-unpins if the block's pruned_blocks async-op data is_none(). But AsyncTree::try_advance_output mapped a pruned Finished { reported: false } block to None instead of
    Some(user_data) (the old "TAsync thrown away silently / TODO" corner case).
  • So a relay block whose parahead finished but was never reported before being pruned hits both unpin paths → double-unpin → Err(()) → panic.

Fix

  • try_advance_output now returns Some(user_data) for any Finished pruned block, regardless of reported.
  • The prune path then correctly sees "already handled" and skips it → unpinned exactly once.

Why it's safe

  • Only paraheads reads this value (the unpin guard); runtime_service ignores it (|(_, b, _)|) and uses the separate InputIterItem::async_op_user_data, which is unchanged.
  • The one case that could turn this into a pin leak (a block becoming Finished via same_async_op_as_parent without going through async_op_finished) is structurally impossible in paraheads — all inserts pass same_async_op_as_parent = false.

Changes

  • Fix in try_advance_output.
  • Regression test in async_tree covering the finished-but-unreported pruned block.
  • Doc clarification distinguishing the two async-op Options ("finished" in pruned_blocks vs "reported" in InputIterItem).

lrubasze added 3 commits June 15, 2026 10:37
A relay block whose async op finished but which is pruned before being
reported comes back in pruned_blocks with None async data, the condition
behind the paraheads double-unpin (Sentry DOTLI-7P).
… blocks

try_advance_output dropped the async user data of a pruned block whose
operation had finished but which was never reported, returning None. The
paraheads sync service treats None as 'outcome not yet applied' and unpins
the relay block a second time, causing the Err(()) panic in
Subscription::unpin_block (Sentry DOTLI-7P).

Return Some whenever the operation finished, regardless of reported state.
Convert the reproduction test into a regression test.
…mantics

InputIterItem::async_op_user_data is Some only once a block is reported,
while OutputUpdate::Finalized::pruned_blocks is Some as soon as the op
finishes. Document the distinction and cross-reference both so the
look-alike Options aren't confused.
@lrubasze lrubasze marked this pull request as ready for review June 15, 2026 13:20
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.

light-base: panic in Subscription::unpin_block — parachain sync double-unpins a relay block

2 participants