Skip to content

feat(routing-policy): adaptive default proactively holds warm standby + asymmetric F/R (app-agnostic) - #4047

Open
0pcom wants to merge 1 commit into
skycoin:developfrom
0pcom:feat/adaptive-proactive-mux-standby
Open

feat(routing-policy): adaptive default proactively holds warm standby + asymmetric F/R (app-agnostic)#4047
0pcom wants to merge 1 commit into
skycoin:developfrom
0pcom:feat/adaptive-proactive-mux-standby

Conversation

@0pcom

@0pcom 0pcom commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

What

Makes the adaptive composite preset — the wired default (preset:adaptive) — actually manifest its mux / warm-standby / asymmetric behavior on real apps, not just the ad-hoc mux-bw rig.

Previously decideAdaptive seeded Mux=1 for three hardcoded client-app names and the empty spec for everything else, so the default route group came up single-leg and only grew reactively under sustained load. The router-layer machinery to reserve N disjoint routes, hold warm standby (on_tick demote/promote), and shape asymmetric forward/reverse mux already exists and is app-agnostic (establishMuxRoutes, EffectiveMuxRoutes, appendRouteAsymmetric, setLegStandby). The only gap was the default policy. This wires it.

Changes (policy layer only — no router core changes)

  • App-agnostic default. decideAdaptive now applies to every overlay app that dials a route group (proxy, vpn, skynet-client, the resolving-proxy / browser chain, custom-named sessions). Only latency-sensitive chat stays a single lean route (same idiom as rotating-bw). No hardcoded allowlist of binary names — the old three-name switch made the policy a silent no-op for renamed sessions.
  • Asymmetric F/R by default. ForwardMux=1 (one lean upstream/request leg, no mux head-of-line cost) + ReverseMux=adaptRevActive+adaptStandbyMax (a wider bulk-download mux sized active + a warm-standby reserve).
  • Proactive warm standby. tickAdaptive parks the surplus reverse legs as warm standby (down to the steady active target) once no leg is a latency outlier and the group is not saturated — holding instant-promote spares without waiting for an idle signal. It parks the newest surplus leg, never leg 0 (the primary/forward leg the router refuses to demote). Under load the saturated branch promotes a spare (dip-free) before adding a cold leg.

Parity & safety

  • Native == wazero parity preserved: bundle.wasm rebuilt from the shared pkg/router/policy/preset package via TinyGo; parity_test (decide + multi-tick on_tick) green.
  • Graceful degradation preserved: no candidates / unknown transport kinds → Chosen nil, router picks the path exactly as before (the wasm / no-provider path).
  • mux=1 apps and single-route dials unchanged.

Not included (design blocker)

Runtime load-based F/R flip (swap which direction is wide from on_tick per-leg {bps}) is not implemented: the RotationAction ABI has no forward/reverse-width field, and per-direction rules are provisioned at setup, so re-shaping the fwd/rev leg allocation at runtime needs a setup-node re-provision (a dip) or an ABI + wire extension. Reported rather than shipped as a fragile re-dial. The static asymmetric shape (lean forward / wide reverse) is shipped.

Tests

go build ., go vet, go test ./pkg/router/policy/... ./pkg/router/ all green, including the native↔wazero parity gate and a new TestEngine_OnTick_AdaptiveHoldsWarmStandby.

… + asymmetric F/R (app-agnostic)

The `adaptive` composite preset is the wired default (config generator sets
`preset:adaptive`). It previously seeded Mux=1 for three hardcoded client-app
names and the empty spec for everything else, so the default route group came up
single-leg and only grew reactively under sustained load — the mux / warm-standby
/ asymmetric behavior never manifested on real apps, only in the ad-hoc mux-bw rig.

The router-layer machinery to reserve N disjoint routes, hold warm standby
(on_tick demote/promote), and shape asymmetric forward/reverse mux already exists
and is app-agnostic (establishMuxRoutes, EffectiveMuxRoutes, appendRouteAsymmetric,
setLegStandby). The gap was purely in the default policy. This wires it:

- decideAdaptive is now APP-AGNOSTIC: every overlay app that dials a route group
  (proxy, vpn, skynet-client, the resolving-proxy / browser chain, custom-named
  sessions) gets the adaptive mux; only latency-sensitive chat stays a single
  lean route (same idiom as rotating-bw). No hardcoded allowlist of binary names.
- Asymmetric by default: ForwardMux=1 (a single lean upstream/request leg, no mux
  head-of-line cost) + ReverseMux=adaptRevActive+adaptStandbyMax (a wider bulk
  download mux sized active + warm-standby reserve).
- tickAdaptive proactively parks the surplus reverse legs as warm standby (down
  to the steady active target) once no leg is a latency outlier and the group is
  not saturated — holding instant-promote spares WITHOUT waiting for an idle
  signal. Parks the newest surplus leg, never leg 0 (the primary/forward leg the
  router refuses to demote). Under load the saturated branch grows the active
  width by promoting a spare (dip-free) before adding a cold leg.

Native == wazero parity preserved: bundle.wasm rebuilt from the shared preset
package via TinyGo; parity_test (decide + multi-tick on_tick) green. Graceful
degradation preserved (no candidates / unknown transport kinds -> Chosen nil,
router picks the path as before). mux=1 apps and single-route dials unchanged.
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