Skip to content

fix(tpd): land a busy visor's full transport list via a targeted, bounded discovery-leaf fetch - #4046

Merged
0pcom merged 1 commit into
skycoin:developfrom
0pcom:fix/tpd-targeted-discovery-fetch
Aug 21, 2026
Merged

0pcom merged 1 commit into
skycoin:developfrom
0pcom:fix/tpd-targeted-discovery-fetch

Conversation

@0pcom

@0pcom 0pcom commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Problem

A visor publishes its entire transport set as one inlined tp-list leaf on its CXO feed, alongside a bulky per-transport telemetry subtree (transports/<uuid>/current, timelines, bitmaps). TPD's aggregator only reconciled discovery once CXO finished filling the whole Root (OnRootFilled) or, best-effort, when a fill broke (OnFillingBreaks).

For a busy visor the Root carries hundreds-to-thousands of telemetry objects, and the whole-Root fill can't complete within MaxFillingTime (90s) over the short-lived delivering conn before it churns. So OnRootFilled rarely fires and TPD reflected only ~10% of the visor's transports (measured live: 850 local transports → ~51-121 in TPD, decaying), starving the route-finder.

Fix

Decouple discovery from the whole-Root telemetry fill. On OnRootReceived, do a bounded, targeted fetch of just the tp-list leaf path — registry + root TreeNode + the small top-level Children index page(s) + the tp-list TreeEntry — over the delivering conn, and reconcile the reporter's full transport set immediately. This is independent of the 90s whole-Root fill and of OnFillingBreaks; the telemetry subtree fill can still run and fail as before.

Because the tp-list snapshot is a single inlined TreeEntry, landing that handful of path objects lands the entire list — and it survives the same short conn a whole-tree fill can't.

How

  • New public (*node.Conn).Getter() exposes the connection's existing on-demand single-object getter (one RqObject per Get, hash-verified, bounded by ResponseTimeout). No change to the fill path.
  • The aggregator reads the leaf path through the existing (*skyobject.Container).Preview pack: objects are found in the local store if the concurrent fill already has them, else pulled from the peer on demand and held in memory only (never written to CXDS) — so this neither competes with nor disturbs the whole-Root fill or the cleanup sweep.

Safety / bounds

  • Bounded: a boundedGetter caps objects (64) and wall-clock (30s) per Root, so a hostile/oversized Root can't drive unbounded per-object requests or hang the walk (budget-exceeded unwinds cleanly — childByName treats an unreadable child as absent).
  • Coalesced per feed so a rapidly republishing visor can't pile up concurrent fetches.
  • Detached goroutine — the blocking per-object requests never stall the CXO node's per-head event loop.
  • Idempotent vs the OnRootFilled path: both call the declarative ReconcileTransportsFromCXO (register-all + deregister-absent).
  • Last-good cache per reporter, re-applied only when a fresh fetch fails, holding transports against the redis TTL until a later Root lands; gated on receiving a Root (a truly-gone visor stops being refreshed and expires normally) and dropped when the feed is reclaimed.

Tests

  • TestTargetedFetchLandsFullListDespiteTelemetry: a Root with a large telemetry subtree whose objects are withheld still reconciles all 200 of its transports via the targeted path, and the fetch is proven bounded to the tp-list path (telemetry objects never requested).
  • TestTargetedFetchMissingLeafCleanMiss: a Root with no tp-list leaf is a clean miss (falls back to cache, no panic/partial).
  • TestBoundedGetterBudget: object-count and deadline caps both enforced.

go build ./..., go vet, go test ./pkg/cxo/... ./pkg/transport-discovery/... (incl. -race on the aggregator) all pass; existing aggregator/fill tests unchanged.

…nded discovery-leaf fetch

A visor publishes its entire transport set as one inlined `tp-list` leaf on
its CXO feed, alongside a bulky per-transport telemetry subtree. TPD's
aggregator only reconciled discovery once CXO finished filling the WHOLE
Root (OnRootFilled) or, best-effort, when a fill broke (OnFillingBreaks).
For a busy visor the Root carries hundreds-to-thousands of telemetry
objects, and the whole-Root fill can't complete within MaxFillingTime over
the short-lived delivering conn — so OnRootFilled rarely fires and TPD
reflected only ~10% of the visor's transports, starving the route-finder.

Decouple discovery from the telemetry fill. On OnRootReceived, do a
bounded, targeted fetch of just the tp-list leaf path (registry + root
TreeNode + the small top-level Children index + the tp-list TreeEntry) over
the delivering conn and reconcile the reporter's full transport set
immediately — independent of the 90s whole-Root fill and OnFillingBreaks.
Because the snapshot is a single inlined TreeEntry, landing that handful of
path objects lands the ENTIRE list, and it survives the same short conn a
whole-tree fill can't.

The fetch reuses the existing (*Container).Preview + a new public
(*Conn).Getter() — objects are read from the local store if the concurrent
fill already has them, else pulled from the peer on demand and held in
memory only (never written to CXDS), so it neither competes with nor
disturbs the whole-Root fill or the cleanup sweep. It is:

  - Bounded: a boundedGetter caps objects (64) and wall-clock (30s) per
    Root so a hostile/oversized Root can't drive unbounded requests or hang.
  - Coalesced per feed so a rapidly republishing visor can't pile up fetches.
  - Idempotent vs the OnRootFilled path: both call the declarative
    ReconcileTransportsFromCXO (register-all + deregister-absent).
  - Backed by a last-good per-reporter cache re-applied when a fetch fails,
    holding transports against the redis TTL until a fresh Root lands;
    dropped when the feed is reclaimed.

Tests: a Root with a large telemetry subtree whose objects are withheld
still reconciles ALL of its transports via the targeted path; the fetch is
proven bounded to the tp-list path (telemetry never requested); missing
leaf is a clean miss; boundedGetter honors both caps.
@0pcom
0pcom merged commit d5460f1 into skycoin:develop Aug 21, 2026
11 of 15 checks passed
@0pcom
0pcom deleted the fix/tpd-targeted-discovery-fetch branch August 24, 2026 23:47
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.

1 participant