Skip to content

ref-count chainHead_v1_unpin across shared follow#27

Open
lrubasze wants to merge 4 commits into
mainfrom
lrubasze/fix-broker-double-unpin
Open

ref-count chainHead_v1_unpin across shared follow#27
lrubasze wants to merge 4 commits into
mainfrom
lrubasze/fix-broker-double-unpin

Conversation

@lrubasze

Copy link
Copy Markdown
Contributor

Fixes #26

Problem

When two tabs share one coalesced chainHead_v1_follow, the broker forwarded a duplicate chainHead_v1_unpin upstream for the same block — a double-unpin on a single subscription. unfollow was ref-counted across sessions; unpin` was not.

Fix

Ref-count pinned blocks per SharedFollow (block hash → holding follow tokens), mirroring the existing unfollow handling:

  • Register a holder when a pinning event (initialized, newBlock) is delivered to a session — for live events and replayed snapshots to late joiners.
  • On unpin, drop the session's hold and forward upstream only when no other session still holds the block; reply success locally.
  • On disconnect/unfollow, release holds and unpin orphaned blocks upstream while the follow stays alive (the last token's unfollow releases the rest).

Tests

packages/protocol/tests/broker.test.ts:

  • two sessions unpinning the same block → exactly one upstream unpin
  • last holder disconnecting → orphaned block unpinned upstream once

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

⚡ Performance Report

⚠️ No baseline found on main. This PR's results are recorded but cannot be compared.
Merge to main to establish a baseline.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Report

Chunks over 500 KB:

File Raw Brotli Gzip
host/assets/paseo.smol-DboPaEh1.json 1.84 MB 941.7 KB 1019.4 KB
host/assets/paseo-people-next.smol.json 3.36 MB 1.68 MB 1.82 MB
host/assets/previewnet.smol.json 810.7 KB 116.0 KB 149.3 KB
host/assets/smoldot_worker.js 2.92 MB 2.19 MB 2.20 MB
Total 10.35 MB (-384 B) 5.38 MB (-476 B) (-48%) 5.72 MB (-764 B)
All files
File Raw Brotli Gzip
host/.well-known/apple-app-site-association 738 B 738 B 738 B
host/.well-known/assetlinks.json 986 B 986 B 986 B
host/assets/auth.js 462.5 KB 175.5 KB (+103 B) 221.5 KB (-1 B)
host/assets/blake2.js 10.2 KB 3.2 KB (+11 B) 3.7 KB
host/assets/bridge.js 5.4 KB 1.8 KB (-3 B) 2.1 KB (-4 B)
host/assets/browser.js 22.9 KB 7.6 KB 8.6 KB
host/assets/client.js 93.0 KB 27.4 KB (+4 B) 30.1 KB
host/assets/container.js 53.9 KB 13.1 KB (-53 B) 14.8 KB (-7 B)
host/assets/dist.js 20.6 KB (-4.3 KB) 4.8 KB (-2.9 KB) 5.4 KB (-3.2 KB)
host/assets/dist.js 28.8 KB (+3.9 KB) 10.0 KB (+2.3 KB) 11.1 KB (+2.5 KB)
host/assets/dist.js 24.9 KB 7.7 KB 8.6 KB
host/assets/dotli-debug-bus.js 495 B 495 B 495 B
host/assets/index.css 46.6 KB 7.2 KB 8.1 KB
host/assets/index.js 102.9 KB 27.8 KB (+79 B) 32.3 KB (-1 B)
host/assets/log.js 972 B 972 B 972 B
host/assets/manifest.js 22.6 KB 7.3 KB (-16 B) 8.0 KB (-1 B)
host/assets/mode.js 1.7 KB 600 B 664 B
host/assets/nanoevents.js 215 B 215 B 215 B
host/assets/network.js 3.7 KB 1.4 KB 1.6 KB
host/assets/nova-scale.js 6.7 KB 2.6 KB 2.9 KB
host/assets/panel.js 84.9 KB 23.1 KB (+9 B) 26.3 KB (-1 B)
host/assets/paseo.smol-DboPaEh1.json 1.84 MB 941.7 KB 1019.4 KB
host/assets/paseo-people-next.smol.json 3.36 MB 1.68 MB 1.82 MB
host/assets/paseo.smol.json 129.8 KB 22.9 KB 31.8 KB
host/assets/previewnet.smol.json 810.7 KB 116.0 KB 149.3 KB
host/assets/resolve.js 154 B 154 B 154 B
host/assets/rolldown-runtime.js 694 B 694 B 694 B
host/assets/rpc-resolve.js 2.5 KB 1.0 KB (-17 B) 1.2 KB (-1 B)
host/assets/shared-mode.js 1.9 KB 790 B (-2 B) 893 B (-2 B)
host/assets/smoldot_worker.js 2.92 MB 2.19 MB 2.20 MB
host/assets/spans.js 1.6 KB 765 B 914 B
host/assets/src.js 1.9 KB 893 B (+1 B) 1000 B
host/assets/styles.css 15.1 KB 3.2 KB 3.8 KB
host/assets/substrate-client.js 7.2 KB 2.7 KB (+1 B) 3.0 KB (-3 B)
host/assets/ws.js 25.8 KB 8.4 KB (-4 B) 9.1 KB (-2 B)
host/dotli.png 11.5 KB 11.5 KB 11.5 KB
host/favicon.svg 1.8 KB 1.8 KB 1.8 KB
host/host-sw.js 2.9 KB 1.1 KB (+16 B) 1.3 KB (-5 B)
host/icon-192.png 12.5 KB 12.5 KB 12.5 KB
host/icon-512.png 42.8 KB 42.8 KB 42.8 KB
host/index.html 20.3 KB 4.5 KB (-6 B) 5.5 KB (-1 B)
host/manifest.webmanifest 441 B 441 B 441 B
host/workbox.js 14.8 KB 4.6 KB 5.1 KB
sandbox/app-sw.js 8.7 KB 2.8 KB (-4 B) 3.2 KB
sandbox/assets/bitswap-bridge.js 840 B 840 B 840 B
sandbox/assets/fetch.js 3.4 KB 1.2 KB (+5 B) 1.4 KB (-1 B)
sandbox/assets/index.js 118.1 KB 33.7 KB (+21 B) 39.7 KB (-1 B)
sandbox/assets/index.css 46.6 KB 7.2 KB 8.1 KB
sandbox/favicon.svg 1.8 KB 1.8 KB 1.8 KB
sandbox/index.html 1.7 KB 582 B (+4 B) 786 B
Total 10.35 MB (-384 B) 5.38 MB (-476 B) (-48%) 5.72 MB (-764 B)

Commit: 20e6294

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

E2E Product suite failed on 668233a1aa0f71584f7df785136b4ecfd13425c1 — 0 passed, 0 failed, 0 skipped.

Failed tests:

Logs: https://github.com/paritytech/dotli-community/actions/runs/27579711084
Artifacts: e2e-product-results (uploaded above) — open the failed test's trace.zip with npx playwright show-trace.

lrubasze and others added 3 commits June 15, 2026 15:39
Two sessions sharing one coalesced chainHead_v1_follow each forward
their own chainHead_v1_unpin for the same block, so the broker sends
two unpins to the single shared upstream. unfollow is ref-counted
across sessions but unpin is not.
Sessions sharing one coalesced chainHead_v1_follow each receive every
reported block and unpin independently, but the upstream pinned each
block only once. Forwarding every unpin sent duplicates to the single
shared follow (double-unpin).

Track pin holders per shared follow (block hash -> set of local follow
tokens) and forward the upstream unpin only when the last holder
releases the block, mirroring the existing unfollow ref-counting. The
local unpin response is synthesized so it no longer depends on the
upstream reply. On disconnect/unfollow a leaving session's orphaned
blocks are unpinned upstream while the follow stays alive; the last
token's unfollow releases the rest.
continue;
}
if (holders.size === 0) {
sharedFollow.pins.delete(hash);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include sharedFollow.pins.delete(hash);?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed for dedup reasons.
This is to prevent a leak. if none of the pins (subscriptions) is holding the block, then the block entry is no longer needed and might be dropped from such from pins list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broker double-unpin: chainHead_v1_unpin not ref-counted across shared follow

3 participants