cross-tab-worker-databus 0.21.14 → 0.21.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-3XBN7FFJ.js → chunk-GQ6NYWQL.js} +21 -6
- package/dist/{chunk-3XBN7FFJ.js.map → chunk-GQ6NYWQL.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +20 -5
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +20 -5
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/data-bus.d.ts +7 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/api.md +1 -1
- package/docs/architecture.md +5 -4
- package/docs/release-checklist.md +1 -1
- package/docs/roadmap.md +57 -1
- package/docs/zh/api.md +1 -1
- package/docs/zh/architecture.md +5 -4
- package/docs/zh/release-checklist.md +1 -1
- package/docs/zh/roadmap.md +49 -1
- package/package.json +1 -1
package/dist/core/data-bus.d.ts
CHANGED
|
@@ -444,6 +444,13 @@ export declare class CrossTabDataBus<TConfig = unknown, TData = unknown> {
|
|
|
444
444
|
* Run a transport operation now if the transport is ready, otherwise queue
|
|
445
445
|
* it behind the start promise. This ensures subscribe/unsubscribe calls made
|
|
446
446
|
* during startup are not lost.
|
|
447
|
+
*
|
|
448
|
+
* `stillWanted` expresses the deferred work as the *current* desired state
|
|
449
|
+
* rather than a captured call, and is checked at each place that resumes on a
|
|
450
|
+
* later task — the recovery gate and the opening flush. The immediate branch
|
|
451
|
+
* carries no check because every path into it evaluates the desired state in
|
|
452
|
+
* the same task: a caller that just mutated `transportSubscribedTopics`, or
|
|
453
|
+
* the gate re-entry one line above.
|
|
447
454
|
*/
|
|
448
455
|
private runTransport;
|
|
449
456
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-bus.d.ts","sourceRoot":"","sources":["../../src/core/data-bus.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"data-bus.d.ts","sourceRoot":"","sources":["../../src/core/data-bus.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
package/docs/api.md
CHANGED
|
@@ -92,7 +92,7 @@ Registers a local subscription and returns a cleanup function.
|
|
|
92
92
|
- Multiple handlers for the same topic use reference counting.
|
|
93
93
|
- The first handler in the current tab registers a cluster subscription.
|
|
94
94
|
- The current tab only leaves the topic after the last handler is released.
|
|
95
|
-
- Subscriptions are automatically queued when the transport is not yet ready, including while a transport recovery is pending: they are held behind the recovery gate and issued once the reopen succeeds instead of being written to the connection that just reported `error`.
|
|
95
|
+
- Subscriptions are automatically queued when the transport is not yet ready, including while a transport recovery is pending: they are held behind the recovery gate and issued once the reopen succeeds instead of being written to the connection that just reported `error`. What is issued is the subscription **still wanted** at that moment: a `subscribe()` released before its queued call ran is not sent, and a release whose topic was re-subscribed in the meantime is not sent either.
|
|
96
96
|
- A subscription requested while an explicit `stop()` is settling is not registered: `subscribe()` reports the rejection through `onError` and returns a no-op cleanup function. Wait for `stop()` to settle, then call `start()` before subscribing again.
|
|
97
97
|
- Wildcard subscriptions: a topic ending in `.*` (`chat.*`) matches any remainder, and `*` matches everything. The pattern is routed, owned, and transport-subscribed as a literal channel; publications tagged with a matching concrete topic (or with the pattern itself) are delivered to wildcard handlers. See `topicMatchesPattern` below.
|
|
98
98
|
- Topics must be non-empty: `subscribe()`, `publish()` and `publishBatch()` throw a `TypeError` when passed `''`, before any other effect (no start is requested and no handler is registered). No transport can address an empty channel, so a subscription to it could never receive anything, and a publication to it was dropped without a trace. `''` warned once per instance from 0.20.96 and became a rejection in 0.21.0, following the pre-1.0 deprecation policy; the guard sits with the option-validation asserts, so an empty topic fails the same way a bad `replay.maxPerTopic` does. `publishBatch('', [])` throws too — the invalid argument is checked ahead of the empty-array no-op.
|
package/docs/architecture.md
CHANGED
|
@@ -472,7 +472,7 @@ Application: subscribe(topic, handler)
|
|
|
472
472
|
→ if no route: selectLeastLoadedWorker, writeRoute, sendControl(SUBSCRIBE)
|
|
473
473
|
→ owner receives CONTROL/SUBSCRIBE
|
|
474
474
|
→ assignedTopics.set(topicKey, topic)
|
|
475
|
-
→
|
|
475
|
+
→ transportSubscribedTopics.add(topic) → transport.subscribe(topic)
|
|
476
476
|
```
|
|
477
477
|
|
|
478
478
|
**Unsubscribe propagation chain:**
|
|
@@ -485,13 +485,14 @@ Application: unsubscribe(topic, handler) (last handler)
|
|
|
485
485
|
→ if no subscribers left: delete route, sendControl(UNSUBSCRIBE)
|
|
486
486
|
→ owner receives CONTROL/UNSUBSCRIBE
|
|
487
487
|
→ assignedTopics.delete(topicKey)
|
|
488
|
-
→
|
|
488
|
+
→ transportSubscribedTopics.delete(topic) → transport.unsubscribe(topic)
|
|
489
489
|
```
|
|
490
490
|
|
|
491
491
|
**Disconnect / reconnect behavior:**
|
|
492
492
|
|
|
493
493
|
- On transport disconnect: `transportSubscribedTopics` is **cleared** immediately. The other three sets (`topicHandlers`, `subscribedTopics`, `assignedTopics`) survive unchanged.
|
|
494
494
|
- On transport reconnect: `CrossTabDataBus` iterates `assignedTopics` and re-calls `transport.subscribe(topic)` for each one, repopulating `transportSubscribedTopics`.
|
|
495
|
+
- Either transport call can be **deferred**: when the transport is not ready, the operation waits behind the opening (or behind a pending recovery attempt) instead of being written to a closed connection. Because `transportSubscribedTopics` is updated at the moment of the call rather than when it lands, a deferred operation is re-checked against that set before it runs — a subscribe whose topic was released in the meantime is dropped, and so is a release whose topic was re-subscribed in the meantime. Without the re-check, a parked subscribe can flush after its own unsubscribe and leave the transport holding a topic that none of the four sets above owns.
|
|
495
496
|
- This is how business subscription intent survives transport failures: the application never needs to re-subscribe after a reconnect.
|
|
496
497
|
|
|
497
498
|
## Message Flow
|
|
@@ -604,7 +605,7 @@ These invariants are pinned by regression tests (see `tests/stability.test.ts` a
|
|
|
604
605
|
- **Storage write recovery.** Coalesced writes retry with exponential backoff (50 ms → 1.6 s cap). A structurally failing key is dropped after 5 attempts (with a `console.warn`) without permanently blocking other queued keys, and the backoff delay resets once the queue fully drains or `clear()` cancels the retries.
|
|
605
606
|
- **Transport recovery budget.** Automatic recovery is paced by a cooldown, bounded by `recovery.maxAttempts`, and reports `exhausted` when the budget is spent. A successful reopen resets the attempt counter and the exhausted flag; explicit `subscribe` on a down transport can still recover manually. Scheduling alone does not reopen a connection: the backend must release the invalid connection before a retry can create or re-open one. `WebSocketTransport` resolves `start()` only after the socket opens, rejects a pre-open `error`/`close` or a `connectTimeoutMs` expiry, marks the socket active only until error/close, and drops the stale reference before the next `start()` invokes the factory, so late callbacks from the superseded socket are ignored.
|
|
606
607
|
- **BFCache suspension.** Hiding the tab stops the transport, bumps the persistence-retry generation (cancelling in-flight persistence retries without surfacing errors), pauses trace metrics and periodic dedup/replay cleanup, and gates dispatch; `pageshow` or an explicit `start()` reopens the transport, resumes those periodic resources, and re-establishes subscriptions exactly once per cycle.
|
|
607
|
-
- **Recovery waiter invalidation.** Each operation parked behind the recovery gate captures the recovery cancellation generation. `stop()` and `suspendTransport()` invalidate that generation when they supersede the recovery cycle, so a waiter whose microtask runs after an immediate explicit `start()` cannot replay into the replacement transport. The restarted cluster owns exactly one resubscription per assigned topic, and the stale operation cannot issue a duplicate subscription.
|
|
608
|
+
- **Recovery waiter invalidation.** Each operation parked behind the recovery gate captures the recovery cancellation generation. `stop()` and `suspendTransport()` invalidate that generation when they supersede the recovery cycle, so a waiter whose microtask runs after an immediate explicit `start()` cannot replay into the replacement transport. The restarted cluster owns exactly one resubscription per assigned topic, and the stale operation cannot issue a duplicate subscription. A parked transport subscription call is additionally re-tested against the desired-subscription set it was issued from (`transportSubscribedTopics`), which is what cancels a subscribe released — or a release re-subscribed — while it waited; the generation check alone cannot, because nothing about the recovery cycle changed in that window.
|
|
608
609
|
- **Hidden-document activation guard.** `WorkerClusterRuntime.start()` installs lifecycle listeners before it checks visibility. A start that reaches a hidden document records suspension without registering a worker, opening a channel, or starting heartbeats, so a restart queued behind an asynchronous stop cannot miss a `pagehide` during cleanup and reconnect in the background; the installed listener handles the later `pageshow`.
|
|
609
610
|
- **Handoff channel close ordering.** `pause()` defers the physical `channel.close()` by one task. Closing synchronously would discard messages still queued for delivery — including the handoff's `ROUTE_RELEASED` — stranding the handoff target with an unconfirmed route.
|
|
610
611
|
- **Unserved route drop.** A departing owner migrates only the routes that still have subscribers. When every remaining subscriber record of a route it owns has gone — a peer tab that died without releasing — the route is deleted instead of handed to a live peer, which would otherwise carry a transport subscription no Tab listens to. `pause()` runs no orphan-route cleanup (that lives in `reconcile`), so this is the only synchronous release of that record (pinned by regression).
|
|
@@ -682,7 +683,7 @@ A runtime `error` intentionally keeps `transportReady` true: the flag records th
|
|
|
682
683
|
- **Readiness during suspend**: A suspended bus sets `suspended = true` and reuses `startPromise`/`pendingStop` for the chained `transport.stop()`, so that promise proves cleanup completed rather than readiness. `ready()` checks `suspended` after the `stopping` gate and rejects with a suspended-state error instead of returning the stop gate. `pageshow`/`reopenTransport()` and an explicit `start()` clear the flag and install a real reopen promise, so `ready()` follows the newest lifecycle intent. Because `pagehide` pauses the *cluster* independently of the transport, an explicit `start()` that leaves suspension resumes the cluster too; without that, the bus would report a healthy transport while its channel listener, heartbeat, and route assignments stayed dormant until the next `pageshow`, and every incoming publication would be discarded by `isAssigned()` against the cleared assignment map. `getHealthSummary()` already reported `{ healthy: false, state: 'suspended' }`; rejection keeps that verdict consistent with `ready()`.
|
|
683
684
|
- **Superseded open invalidation**: Every fresh start, reopen, suspend, and stop advances `lifecycleEpoch`. An open captures its epoch, ignores stale status/message/error callbacks, and neither marks the transport ready nor performs failure cleanup after a newer transition owns the lifecycle. `stop()` therefore waits for pending opens/reopens and prevents a superseded open from becoming ready after the stop completes.
|
|
684
685
|
- **Recovery cooldown**: When the transport reports `error` while `started` is true and `stopping` is false, `updateStatus` schedules an automatic `reopenTransport()` after `RECOVERY_COOLDOWN_MS` (1000 ms). A second error within the cooldown window is suppressed to prevent a tight retry loop.
|
|
685
|
-
- **Transport recovery gate**: Scheduling a reopen also arms a recovery gate, so `subscribe` / `publish` issued during the cooldown cannot reach the failed connection; they are released after the reopen succeeds. The gate deliberately survives a failed automatic attempt: the next explicit operation starts an immediate on-demand reopen instead of waiting for the next paced attempt, and parked operations flush behind that success. An explicit `start()` that supersedes the automatic timer cancels that attempt and resets the failure ledger but leaves the gate closed; if the manual open also fails, its parked operations remain queued for a later automatic or demand-driven reopen instead of being discarded with the superseded opening. Exhausting `recovery.maxAttempts` releases the gate so the explicit-retry path remains reachable. Superseding the wait with `stop()` / `suspendTransport()` also invalidates every parked waiter before releasing the gate, so the explicit restart and its cluster resubscription cannot be followed by a duplicate stale operation. A runtime `error` does not clear `transportReady`, because clearing it would let caller traffic reopen the transport outside the cooldown and report readiness against a connection that is not carrying data.
|
|
686
|
+
- **Transport recovery gate**: Scheduling a reopen also arms a recovery gate, so `subscribe` / `publish` issued during the cooldown cannot reach the failed connection; they are released after the reopen succeeds. The gate deliberately survives a failed automatic attempt: the next explicit operation starts an immediate on-demand reopen instead of waiting for the next paced attempt, and parked operations flush behind that success (a parked transport subscription first re-checks that its topic is still wanted — see **Recovery waiter invalidation**). An explicit `start()` that supersedes the automatic timer cancels that attempt and resets the failure ledger but leaves the gate closed; if the manual open also fails, its parked operations remain queued for a later automatic or demand-driven reopen instead of being discarded with the superseded opening. Exhausting `recovery.maxAttempts` releases the gate so the explicit-retry path remains reachable. Superseding the wait with `stop()` / `suspendTransport()` also invalidates every parked waiter before releasing the gate, so the explicit restart and its cluster resubscription cannot be followed by a duplicate stale operation. A runtime `error` does not clear `transportReady`, because clearing it would let caller traffic reopen the transport outside the cooldown and report readiness against a connection that is not carrying data.
|
|
686
687
|
- **Stop during suspend**: `stop()` sets `stopping = true`, which prevents `suspendTransport()` from running. The cleanup awaits `startPromise` and `pendingStop` to ensure any in-flight open or stop completes before the final `transport.stop()`.
|
|
687
688
|
|
|
688
689
|
## Degradation
|
|
@@ -20,7 +20,7 @@ Only `pnpm bench:browser` / `pnpm bench:compare` / `pnpm bench:trend` stay local
|
|
|
20
20
|
1. Update `package.json`, `CHANGELOG.md`, and both roadmap files.
|
|
21
21
|
2. Run `pnpm check`, `pnpm lint`, `pnpm test:coverage`, `pnpm bench`, `pnpm test:e2e`, `pnpm bench:browser`, `pnpm verify:pack`, `pnpm verify:compat`, `pnpm verify:types`, and `git diff --check`. (`verify:compat` asserts the package `exports` subpaths and type fields present in the `COMPAT_BASE_TAG` baseline still exist; `verify:pack` steam-imports the full root public surface plus every subpath in ESM and CJS from the packed tarball; `verify:types` freezes every public entry's declared export names against the same baseline and asserts no public signature names a type that no entry exports — a type-only export emits no runtime key, so neither the `Object.keys()` freeze in `tests/dual-format.test.ts` nor `verify:compat` can see one change. `verify:compat` and `verify:types` resolve their baseline from the most recent release tag, so a shallow or tag-less clone must first run `git fetch --tags` or they fail with "no version tag found".)
|
|
22
22
|
3. Gate dependency security: `pnpm audit --registry=https://registry.npmjs.org` (the configured mirror registry lacks the audit endpoint; CI runs it on the public registry in the verify job). Fail the release on any known-vulnerability advisory; `pnpm-workspace.yaml` overrides pin patched ranges.
|
|
23
|
-
4. Gate browser benchmark regressions: run `pnpm bench:browser` (twice at minimum; the gate compares the newest report against the median of the same metric in up to the five preceding archived reports, **and additionally requires the new value to exceed the highest of those samples**) and then `pnpm bench:compare --fail-above-pct 50`. The median baseline exists because the in-page hot-path metrics alternate between a fast and a slow mode on identical code — two consecutive runs of `dedup1000Ms` measured 12.7 ms then 25.6 ms, and a single noisy previous report could fail the documented ceiling with no code change at all. The second leg exists because a median over an alternating sample is only stable while the modes stay mixed: when several consecutive reports land in the same mode the median itself jumps, which is how one comment-only tree read +121% against one baseline and −56.7% against another minutes apart. A metric excused by that leg is printed under `within-baseline, not gated` rather than passing silently — measured on the current archive, `publish/dedicated/perMessageMs`, `publish/shared/perMessageMs` and `databus/publishBatch1000Ms` were all inside their own five-report range, so treat that list as the metric's real resolution, not as a clean bill. `publish/dedicated/perMessageMs` is noisy the same way: two consecutive runs of identical code measured 49.9 ms then 71.1 ms, failing the gate at +75.7% while `dedup1000Ms` and `traceAndPublish1000Ms` *improved* in the same report — that single-metric-up-everything-else-down shape is the noise signature, and the next run reported 49.4 ms and passed. Before accepting or acting on a failure, re-run and check the spread; if the change touches a hot path, size it directly (a micro-benchmark of the 0.20.95 bounded-map rewrites measured 80–86 ns/op across all three variants, i.e. inside run-to-run variance). A baseline shift (e.g. a metric becoming real instead of a no-op) is an expected one-time failure. Refresh the long-run trend doc with `pnpm bench:trend` and commit it when the tables change.
|
|
23
|
+
4. Gate browser benchmark regressions: run `pnpm bench:browser` (twice at minimum; the gate compares the newest report against the median of the same metric in up to the five preceding archived reports, **and additionally requires the new value to exceed the highest of those samples**) and then `pnpm bench:compare --fail-above-pct 50`. The median baseline exists because the in-page hot-path metrics alternate between a fast and a slow mode on identical code — two consecutive runs of `dedup1000Ms` measured 12.7 ms then 25.6 ms, and a single noisy previous report could fail the documented ceiling with no code change at all. The second leg exists because a median over an alternating sample is only stable while the modes stay mixed: when several consecutive reports land in the same mode the median itself jumps, which is how one comment-only tree read +121% against one baseline and −56.7% against another minutes apart. A metric excused by that leg is printed under `within-baseline, not gated` rather than passing silently — measured on the current archive, `publish/dedicated/perMessageMs`, `publish/shared/perMessageMs` and `databus/publishBatch1000Ms` were all inside their own five-report range, so treat that list as the metric's real resolution, not as a clean bill. `publish/dedicated/perMessageMs` is noisy the same way: two consecutive runs of identical code measured 49.9 ms then 71.1 ms, failing the gate at +75.7% while `dedup1000Ms` and `traceAndPublish1000Ms` *improved* in the same report — that single-metric-up-everything-else-down shape is the noise signature, and the next run reported 49.4 ms and passed. Before accepting or acting on a failure, re-run and check the spread; if the change touches a hot path, size it directly (a micro-benchmark of the 0.20.95 bounded-map rewrites measured 80–86 ns/op across all three variants, i.e. inside run-to-run variance). A baseline shift (e.g. a metric becoming real instead of a no-op) is an expected one-time failure. Refresh the long-run trend doc with `pnpm bench:trend` and commit it when the tables change. **Re-running a failure can manufacture its own excuse, and that was measured.** `bench:compare`'s baseline — both the median and the maximum leg — is drawn from the most recent archived reports, and every `bench:browser` run appends to that same archive, so the samples you add while investigating a failure become the baseline that judges the next attempt. On 2026-09-24, at host load averages 7.7–15.1 with a zero non-comment `src/` diff in the tree, the first two runs measured `publish/dedicated 77.3 ms` and `publish/shared 67.7 ms` against a ~38/~34 ms fast mode and failed the 50% ceiling; five further re-runs reported **OK**, because the baseline median had slid to `70.6`. One clean run after taking those samples out of the archive showed every metric rising together (`dedicated +92.5%`, `shared +99.2%`, `dedup +43.4%`, `trace +21.0%`), which is the contention signature and the inverse of the single-metric-up shape above. So read the load average *before* re-running, and when a suspect failure occurs on a loaded host, record it as deferred with its numbers and move the off-mode samples aside instead of letting them into the baseline — and do not regenerate `docs/benchmarks.md` from them.
|
|
24
24
|
5. Confirm the package contains only intended files with `npm pack --dry-run --json`.
|
|
25
25
|
6. Commit on a feature branch, push that branch, and merge its green PR using squash or fast-forward (no merge commit). Fetch the merged commit, tag that exact commit, and push only the specific version tag; never push `main`/`master` directly or force-push. The workflow runs `node scripts/verify-release-version.mjs` to require `RELEASE_TAG` to equal `v` + the package version and to require exactly one non-empty CHANGELOG section.
|
|
26
26
|
|
package/docs/roadmap.md
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
0.21.
|
|
3
|
+
0.21.16 was released on September 24, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
|
|
4
|
+
|
|
5
|
+
## 0.21.16 delivered scope
|
|
6
|
+
|
|
7
|
+
- **A deferred transport operation is cancelable by its own release.** `subscribeTransport()` and
|
|
8
|
+
`unsubscribeTransport()` recorded intent in `transportSubscribedTopics` and then handed `runTransport()` a
|
|
9
|
+
captured call, so a subscribe parked behind a pending opening could flush *after* the unsubscribe that cancelled
|
|
10
|
+
it. The connection then held a channel that no local handler, cluster assignment, or route record owns — and
|
|
11
|
+
nothing recovers, because the dedupe that prevents double-subscribes reads the same stale set. `runTransport()`
|
|
12
|
+
now also takes the desired state as a predicate and re-reads it at each place that resumes on a later task. Three
|
|
13
|
+
regression cases, with per-leg mutation attribution: the two resume points have disjoint kill sets (1 test versus
|
|
14
|
+
2), and inverting the guard fails 20 cases including the pre-existing startup-queueing test.
|
|
15
|
+
- **The coordination fuzz now delivers frames the way a browser does.** `ChannelHub.setAsyncDelivery(true)` defers
|
|
16
|
+
each post by one microtask; it is opt-in because 15 tests post a frame and assert without awaiting. Enabling it
|
|
17
|
+
on the sweep's hub is what turned the previous release's unresolved observation into the defect above — measured
|
|
18
|
+
as a control in both directions, six seeds failing before the fix and zero after, with the synchronous hub
|
|
19
|
+
passing on the broken code.
|
|
20
|
+
- **What the raised fidelity did *not* claim is recorded at the sites.** The async hub adds no standing mutant kill
|
|
21
|
+
on the fixed code, the member-set re-read inside the deferred pump is defensive rather than observable through
|
|
22
|
+
this fake, and the unsubscribe direction's measured cost is a redundant wire frame rather than a stranded
|
|
23
|
+
channel.
|
|
24
|
+
- **Shipped prose the fix invalidated**, corrected in English and Chinese: both subscription propagation chains
|
|
25
|
+
wrote the set *after* the transport call (the code does it before), the recovery-gate paragraph credited only the
|
|
26
|
+
cancellation-generation guard, and `api.md`'s `subscribe()` row promised queueing without cancellation.
|
|
27
|
+
|
|
28
|
+
## 0.21.15 delivered scope
|
|
29
|
+
|
|
30
|
+
- Both point-to-frame addressee guards are now pinned by name. `handleMessage` dispatches on `type` alone and
|
|
31
|
+
the cluster channel is broadcast, so `targetWorkerId !== this.workerId` is the only thing keeping a runtime
|
|
32
|
+
from acting on a frame meant for someone else; measured by deleting each line in turn, 369 and then 358
|
|
33
|
+
tests stayed green, and the coordination fuzz cannot assert either one because its invariants are end-state
|
|
34
|
+
checks and a slower convergence still passes.
|
|
35
|
+
- The `ROUTE_RELEASED` pin needed a construction its staleness test could not refuse: a durable route that
|
|
36
|
+
genuinely names this runtime, with a matching `generation` and `handoffFromWorkerId`, and the ACK addressed
|
|
37
|
+
to a third worker. That is also why the older forged-`topicKey` case never reached the line.
|
|
38
|
+
- The coordination fuzz gained a third limit, placed where neither half of a loop owns it. Its two existing
|
|
39
|
+
bounds were both made of time, and the failure that had been turning `verify` red spent its time in
|
|
40
|
+
microtasks, where no same-thread deadline can reach it; `ChannelHub` now stops delivering past 50,000
|
|
41
|
+
posts in one seed, with the threshold taken from a measured distribution (p50 = 28, p99 = 90, max = 16,763)
|
|
42
|
+
rather than from the size of the wedge, and a cut seed is printed by name and excluded from the depth floor.
|
|
43
|
+
- What that loop actually is was then measured instead of inferred, and it is the harness rather than the
|
|
44
|
+
library: 11,542 accepts against 11,538 `confirmRoute` skips and 6 writes reaching storage, because the
|
|
45
|
+
owner's confirmation sits in its own batching writer while a peer reads the flushed record — and the cycle
|
|
46
|
+
cannot yield to a microtask because the test hub delivers every frame on the caller's stack. Deferring
|
|
47
|
+
delivery by one microtask removes the storm outright. The earlier note in this file that called it an open
|
|
48
|
+
product defect is corrected here, and the six injected-frame seeds that correction leaves behind are named
|
|
49
|
+
as the next question rather than resolved.
|
|
50
|
+
- `docs/release-checklist.md`, in both languages, now records that `pnpm bench:compare` can be closed by the
|
|
51
|
+
act of investigating it: its baseline is drawn from the same rolling archive every `bench:browser` run
|
|
52
|
+
appends to, so the instruction is to read the host load first and record a suspect failure as deferred
|
|
53
|
+
instead of chasing a green.
|
|
54
|
+
- `docs/progress.md` has a structural gate. Two PRs for one phase had left two `## Phase 142` blocks, with one
|
|
55
|
+
of them pushed below the anchor every entry is appended at; `tests/documentation.test.ts` now requires each
|
|
56
|
+
phase number to be new, later than the previous entry, and above the standing sections.
|
|
57
|
+
|
|
58
|
+
No behavior, wire-format, storage-layout or cluster-protocol change: `src/` is untouched in this version, and
|
|
59
|
+
the release is owed to `docs/release-checklist.md` and `CHANGELOG.md`, both of which ship in the package.
|
|
4
60
|
|
|
5
61
|
## 0.21.14 delivered scope
|
|
6
62
|
|
package/docs/zh/api.md
CHANGED
|
@@ -92,7 +92,7 @@ subscribe(
|
|
|
92
92
|
- 同一 Topic 的多个 handler 使用引用计数。
|
|
93
93
|
- 当前 Tab 第一个 handler 会登记集群订阅。
|
|
94
94
|
- 最后一个 handler 释放后,当前 Tab 才退出该 Topic。
|
|
95
|
-
- transport 尚未 ready 时订阅自动排队;transport 恢复待定时同样如此:订阅会挂在恢复门之后,等重开成功才下发,而不会写入刚刚上报 `error`
|
|
95
|
+
- transport 尚未 ready 时订阅自动排队;transport 恢复待定时同样如此:订阅会挂在恢复门之后,等重开成功才下发,而不会写入刚刚上报 `error` 的连接。下发的是那一刻**仍然被需要**的订阅:排队期间已被释放的 subscribe 不会发出,排队期间又被重新订阅的释放同样不会发出。
|
|
96
96
|
- 显式 `stop()` 尚未 settle 时发起的订阅不会登记:`subscribe()` 通过 `onError` 上报并返回 no-op 释放函数。调用方应等待 `stop()` settle,再调用 `start()` 后重新订阅。
|
|
97
97
|
- 通配符订阅:以 `.*` 结尾的 Topic(如 `chat.*`)匹配任意后缀,`*` 匹配全部。pattern 以字面量参与路由、归属与传输订阅;携带匹配的具体 topic(或 pattern 本身)的发布都会投递给通配 handler。匹配规则见下方 `topicMatchesPattern`。
|
|
98
98
|
- Topic 必须非空:`subscribe()`、`publish()`、`publishBatch()` 传入 `''` 时会抛出 `TypeError`,且在任何其他副作用之前(不会请求 start,也不会登记 handler)。没有任何 transport 能寻址空 channel,因此对它的订阅永远收不到消息,向它的发布也会被无声丢弃。`''` 自 0.20.96 起每个实例告警一次,自 0.21.0 起直接拒绝——遵循 1.0 前的弃用策略;该守卫与选项校验断言放在一起,空 topic 与非法 `replay.maxPerTopic` 以同样方式失败。`publishBatch('', [])` 同样抛错:非法参数先于空数组 no-op 检查。
|
package/docs/zh/architecture.md
CHANGED
|
@@ -416,7 +416,7 @@ sequenceDiagram
|
|
|
416
416
|
→ 无 route: 选择最低负载 Worker,写 route,sendControl(SUBSCRIBE)
|
|
417
417
|
→ owner 收到 CONTROL/SUBSCRIBE
|
|
418
418
|
→ assignedTopics.set(topicKey, topic)
|
|
419
|
-
→
|
|
419
|
+
→ transportSubscribedTopics.add(topic) → transport.subscribe(topic)
|
|
420
420
|
```
|
|
421
421
|
|
|
422
422
|
**退订传递链:**
|
|
@@ -429,13 +429,14 @@ sequenceDiagram
|
|
|
429
429
|
→ 无其他 subscriber: 删除 route,sendControl(UNSUBSCRIBE)
|
|
430
430
|
→ owner 收到 CONTROL/UNSUBSCRIBE
|
|
431
431
|
→ assignedTopics.delete(topicKey)
|
|
432
|
-
→
|
|
432
|
+
→ transportSubscribedTopics.delete(topic) → transport.unsubscribe(topic)
|
|
433
433
|
```
|
|
434
434
|
|
|
435
435
|
**断开/重连行为:**
|
|
436
436
|
|
|
437
437
|
- transport 断开时:`transportSubscribedTopics` **立即清空**。其他三个集合(`topicHandlers`、`subscribedTopics`、`assignedTopics`)保持不变。
|
|
438
438
|
- transport 重连时:`CrossTabDataBus` 遍历 `assignedTopics`,对每个 topic 重新调用 `transport.subscribe(topic)`,重新填充 `transportSubscribedTopics`。
|
|
439
|
+
- 这两个 transport 调用都可能**被延后**:transport 未就绪时,操作会排在 opening(或进行中的恢复尝试)之后,而不是写到已关闭的连接上。由于 `transportSubscribedTopics` 是在发起调用时更新、而非在调用真正落地时更新,延后的操作在执行前会先依据该集合重新核对——期间 topic 已被释放的订阅会被丢弃,期间又被重新订阅的释放同样会被丢弃。缺少这层核对时,一个停靠的 subscribe 可能在其自身的 unsubscribe 之后才 flush,使 transport 持有一个上面四个集合都不再拥有的 topic。
|
|
439
440
|
- 这就是业务订阅意图在 transport 故障后仍能保持的原因:应用层无需在重连后重新订阅。
|
|
440
441
|
|
|
441
442
|
## 消息流程
|
|
@@ -541,7 +542,7 @@ Transport 消息 → isAssigned(topic)? → 是 → broadcastEvent(EVENT)
|
|
|
541
542
|
- **存储写失败恢复。** 合并写入按指数退避重试(50 ms → 1.6 s 封顶)。结构性失败的关键在 5 次尝试后被丢弃(伴随 `console.warn`),且不会永久阻塞其他排队 key;队列完全清空或 `clear()` 取消重试后,退避延迟重置。
|
|
542
543
|
- **Transport 恢复预算。** 自动恢复由冷却时间限速、由 `recovery.maxAttempts` 限量,预算耗尽后标记 `exhausted`。成功的重开会重置尝试计数与 exhausted 标记;transport 宕机时显式 `subscribe` 仍可手动恢复。自动调度本身不会重开连接:后端必须先释放失效连接,重试才能创建或重开 socket。`WebSocketTransport` 只在 socket `open` 后 resolve `start()`;握手前的 `error`/`close` 或 `connectTimeoutMs` 超时都会 reject。它仅在 socket 有效期间将其标记为 active;`error`/`close` 会立即失效,下一次 `start()` 在调用工厂前清除旧引用,因此被取代 socket 的迟到回调会被忽略。
|
|
543
544
|
- **BFCache 挂起。** Tab 隐藏时停止 transport、递增持久化重试 generation(取消在途重试且不对外报错),同时暂停 trace metrics 与 dedup/replay 周期清理并门控分发;`pageshow` 或显式 `start()` 会重开 transport、恢复这些周期资源,并且每轮循环只重建一次订阅。
|
|
544
|
-
- **恢复 waiter 失效。** 每个停靠在 recovery gate 上的操作都会捕获当前恢复取消 generation;`stop()` 与 `suspendTransport()` 取代恢复周期时会递增该 generation,因此即使 waiter 的微任务在紧随其后的显式 `start()` 之后才运行,也不能把操作重放到替换 transport。重建后的 cluster 对每个已分配 topic
|
|
545
|
+
- **恢复 waiter 失效。** 每个停靠在 recovery gate 上的操作都会捕获当前恢复取消 generation;`stop()` 与 `suspendTransport()` 取代恢复周期时会递增该 generation,因此即使 waiter 的微任务在紧随其后的显式 `start()` 之后才运行,也不能把操作重放到替换 transport。重建后的 cluster 对每个已分配 topic 只负责一次重新订阅,旧操作不能再发出重复订阅。停靠的 transport 订阅调用还会额外依据发起它的那份期望集合(`transportSubscribedTopics`)重新核对——这正是排队期间被释放的 subscribe(以及排队期间又被重新订阅的 release)会被取消的原因;单靠 generation 检查做不到,因为那段窗口里恢复周期本身没有任何变化。
|
|
545
546
|
- **隐藏文档的激活守卫。** `WorkerClusterRuntime.start()` 会先安装 lifecycle listener 再检查可见性。若启动时文档已隐藏,则只记录挂起状态,不注册 worker、不打开 channel,也不启动 heartbeat;因此排队在异步 stop 之后的重启不会漏掉清理期间的 `pagehide` 并在后台重连,后续 `pageshow` 会由已安装的 listener 恢复。
|
|
546
547
|
- **挂起态就绪判定。** 挂起中的 bus 会把 `startPromise` 复用为 `pendingStop`(即 chained `transport.stop()` 的 gate),该 Promise 只能证明清理完成,不能证明可以承载数据。`ready()` 在 `stopping` 门之后检查 `suspended`,以挂起态错误 reject,而不是返回 stop gate;`pageshow`/`reopenTransport()` 与显式 `start()` 会清除标记并安装真正的重开 Promise,使 `ready()` 跟随最新生命周期意图。`getHealthSummary()` 原本就报告 `{ healthy: false, state: 'suspended' }`,reject 让 `ready()` 与该判定保持一致。由于 `pagehide` 会独立于 transport 暂停 cluster,显式 `start()` 在解除挂起时也必须一并恢复 cluster;否则 bus 会报告 transport 健康,而 channel listener、heartbeat 与 route 分配仍保持休眠直到下一次 `pageshow`,所有入站 publication 都会因 `isAssigned()` 对照已清空的分配表而被丢弃。
|
|
547
548
|
- **交接通道关闭顺序。** `pause()` 将物理 `channel.close()` 推迟一个任务。同步关闭会丢弃仍在排队等待投递的消息(包括交接的 `ROUTE_RELEASED`),使交接目标持有未确认路由。
|
|
@@ -619,7 +620,7 @@ DataBus 将"业务订阅意图"与"transport 当前订阅状态"分离。transpo
|
|
|
619
620
|
- **启动期间隐藏**:`pagehide` 在 `openTransport` 飞行中触发时,`suspendTransport()` 设置 `suspended = true`,并在飞行中的 start 之后链式执行 `transport.stop()`。`openTransport` 的 catch 路径检测到 `suspended` 后放弃本次 open,不视为失败。当重复的 hide/show 让排队的 resume opening 与更早的 stop gate 交错时,挂起会安装新的串行 stop gate 并恢复 `startPromise === pendingStop` 不变量,使下一次 `pageshow` 真正重开,而不是复用已被淘汰的 opening 并永久停留在挂起状态。
|
|
620
621
|
- **被取代 open 失效**:每次全新 start、reopen、suspend 和 stop 都会推进 `lifecycleEpoch`。open 会捕获自己的 epoch;一旦更新的转换接管生命周期,旧 open 的 status/message/error 回调会被忽略,也不会再把 transport 标记为 ready 或执行失败清理。因此 `stop()` 会等待未完成的 open/reopen,并阻止被取代的 open 在 stop 完成后变为 ready。
|
|
621
622
|
- **恢复冷却**:transport 上报 `error` 且 `started` 为 true、`stopping` 为 false 时,`updateStatus` 在 `RECOVERY_COOLDOWN_MS`(1000 ms)后调度自动 `reopenTransport()`。冷却窗口内的第二次错误被抑制,防止紧循环重试。
|
|
622
|
-
- **传输恢复门**:调度重开的同时会抬起恢复门,使冷却期间发起的 `subscribe` / `publish` 无法到达失效连接,等重开成功后才释放。自动尝试失败后门刻意保持关闭:下一次显式操作会立即触发按需重开,而不是等待下一个限速尝试,挂起的操作则在该次成功后一并 flush
|
|
623
|
+
- **传输恢复门**:调度重开的同时会抬起恢复门,使冷却期间发起的 `subscribe` / `publish` 无法到达失效连接,等重开成功后才释放。自动尝试失败后门刻意保持关闭:下一次显式操作会立即触发按需重开,而不是等待下一个限速尝试,挂起的操作则在该次成功后一并 flush(停靠的 transport 订阅会先核对该 topic 是否仍被需要,见「恢复 waiter 失效」)。显式 `start()` 取代自动 timer 时会取消该次尝试并重置失败账本,但保持门关闭;若手动重开也失败,停靠操作会继续等待后续自动或按需重开,而不会随被取代的 opening 一起静默丢弃。`recovery.maxAttempts` 耗尽时会释放门,以保留显式重试路径;若被 `stop()` / `suspendTransport()` 取代,则在释放门前使全部停靠 waiter 失效,因此显式重启及其 cluster 重新订阅之后不会再多执行一次过期操作。运行期 `error` 不会清除 `transportReady`——清除它会让调用方流量绕过冷却重开,并在连接尚未承载数据时报告 ready。
|
|
623
624
|
- **暂停期间停止**:`stop()` 设置 `stopping = true`,阻止 `suspendTransport()` 执行。清理过程会 await `startPromise` 和 `pendingStop`,确保任何飞行中的 open 或 stop 完成后才执行最终的 `transport.stop()`。
|
|
624
625
|
|
|
625
626
|
## 降级
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
1. 更新 `package.json`、`CHANGELOG.md` 和中英文 roadmap。
|
|
21
21
|
2. 运行 `pnpm check`、`pnpm lint`、`pnpm test:coverage`、`pnpm bench`、`pnpm test:e2e`、`pnpm bench:browser`、`pnpm verify:pack`、`pnpm verify:compat`、`pnpm verify:types` 以及 `git diff --check`(`verify:compat` 断言 `COMPAT_BASE_TAG` 基线中的 package `exports` 子路径与类型字段仍然存在;`verify:pack` 从打包产物冒烟导入完整根公共面与全部子路径的 ESM/CJS;`verify:types` 用同一基线冻结每个公共入口声明层导出的名字,并断言没有任何公共签名引用了「任何入口都未导出」的类型——纯类型导出不会在运行时产生任何键,因此 `tests/dual-format.test.ts` 的 `Object.keys()` 冻结和 `verify:compat` 都看不到它的变化。`verify:compat` 与 `verify:types` 从最近的发布 tag 解析基线,因此浅克隆或缺少 tag 的克隆需先执行 `git fetch --tags`,否则会以 "no version tag found" 失败)。
|
|
22
22
|
3. 依赖安全门禁:`pnpm audit --registry=https://registry.npmjs.org`(配置的镜像 registry 缺少 audit 端点;CI 在 verify job 中于公共 registry 运行)。任一已知漏洞公告即视为发布失败;`pnpm-workspace.yaml` overrides 钉住补丁版本。
|
|
23
|
-
4. 浏览器基准回归门禁:运行 `pnpm bench:browser`(至少两次;门禁将最新报告与之前最多五份归档报告中同一指标的中位数比较,**并且额外要求新值超过这些样本中的最大值**),随后执行 `pnpm bench:compare --fail-above-pct 50`。采用中位数基线是因为页内热路径指标在同一份代码上会在快/慢两档之间来回跳——`dedup1000Ms` 相邻两次实测分别为 12.7 ms 与 25.6 ms,只看"上一份报告"时,一次偶发噪声就能在无代码改动的情况下击穿既定上限。第二个条件则是因为:在两档交替的样本上,中位数只有当两档保持混合时才稳定——一旦连续几份报告落在同一档,中位数本身就会跳变,这正是同一份纯注释改动在一版基线前读出 +121%、几分钟后在另一版基线前读出 −56.7% 的原因。被该条件放过的指标会打印在 `within-baseline, not gated` 下,而不是静默通过——在当前归档上实测,`publish/dedicated/perMessageMs`、`publish/shared/perMessageMs` 与 `databus/publishBatch1000Ms` 三项都落在各自五份报告的区间内,因此应把这份清单读作该指标的真实分辨率,而不是一张合格证。`publish/dedicated/perMessageMs` 同样如此:同一份代码相邻两次实测为 49.9 ms 与 71.1 ms,令门禁以 +75.7% 失败,而同一份报告里 `dedup1000Ms` 与 `traceAndPublish1000Ms` 反而*变快*——"单指标上涨、其余下降"正是噪声的特征,第三次实测为 49.4 ms 并通过。因此在采信或处理失败前先复跑并观察离散度;若改动涉及热路径,就直接量化其开销(0.20.95 有界映射改写的微基准显示三种写法均为 80–86 ns/op,落在运行间波动之内)。基线迁移(例如某指标从空操作变为真实路径)属预期内的一次性失败。用 `pnpm bench:trend`
|
|
23
|
+
4. 浏览器基准回归门禁:运行 `pnpm bench:browser`(至少两次;门禁将最新报告与之前最多五份归档报告中同一指标的中位数比较,**并且额外要求新值超过这些样本中的最大值**),随后执行 `pnpm bench:compare --fail-above-pct 50`。采用中位数基线是因为页内热路径指标在同一份代码上会在快/慢两档之间来回跳——`dedup1000Ms` 相邻两次实测分别为 12.7 ms 与 25.6 ms,只看"上一份报告"时,一次偶发噪声就能在无代码改动的情况下击穿既定上限。第二个条件则是因为:在两档交替的样本上,中位数只有当两档保持混合时才稳定——一旦连续几份报告落在同一档,中位数本身就会跳变,这正是同一份纯注释改动在一版基线前读出 +121%、几分钟后在另一版基线前读出 −56.7% 的原因。被该条件放过的指标会打印在 `within-baseline, not gated` 下,而不是静默通过——在当前归档上实测,`publish/dedicated/perMessageMs`、`publish/shared/perMessageMs` 与 `databus/publishBatch1000Ms` 三项都落在各自五份报告的区间内,因此应把这份清单读作该指标的真实分辨率,而不是一张合格证。`publish/dedicated/perMessageMs` 同样如此:同一份代码相邻两次实测为 49.9 ms 与 71.1 ms,令门禁以 +75.7% 失败,而同一份报告里 `dedup1000Ms` 与 `traceAndPublish1000Ms` 反而*变快*——"单指标上涨、其余下降"正是噪声的特征,第三次实测为 49.4 ms 并通过。因此在采信或处理失败前先复跑并观察离散度;若改动涉及热路径,就直接量化其开销(0.20.95 有界映射改写的微基准显示三种写法均为 80–86 ns/op,落在运行间波动之内)。基线迁移(例如某指标从空操作变为真实路径)属预期内的一次性失败。用 `pnpm bench:trend` 刷新长期趋势文档,表格变化时一并提交。**复跑失败可能亲手制造出让它通过的基线,这一点已被实测。** `bench:compare` 的基线(中位数与最大值两个条件)取自最近若干份归档报告,而每一次 `pnpm bench:browser` 都会往同一个归档里追加——于是排查失败时新增的样本,就变成了裁决下一次尝试的基线。2026-09-24:主机负载均值 7.7–15.1、待发布树的非注释 `src/` 差异为 0 行,前两次实测 `publish/dedicated 77.3 ms`、`publish/shared 67.7 ms`,相对约 38/34 ms 的快档在 50% 上限上失败;随后五次复跑报告 **OK**,因为基线中位数已经滑到 `70.6`。把这些样本移出归档后的一次干净实测显示所有指标同时上涨(`dedicated +92.5%`、`shared +99.2%`、`dedup +43.4%`、`trace +21.0%`),那是资源竞争的形态,与上文"单指标上涨、其余下降"的噪声形态相反。因此:复跑之前先看负载(`uptime`);在满载主机上遇到可疑失败,就把数字连同负载一起记为"本轮推迟",并将偏离常态的样本移出基线——也不要据它们重新生成 `docs/benchmarks.md`。
|
|
24
24
|
5. 用 `npm pack --dry-run --json` 确认发布包只包含预期文件。
|
|
25
25
|
6. 在功能分支提交并推送该分支,PR 验证通过后使用 squash 或 fast-forward 合入(不创建 merge commit)。获取合入后的精确提交并打 tag,只推送该版本 tag;禁止直接推送 `main`/`master` 或 force-push。工作流运行 `node scripts/verify-release-version.mjs`,要求 `RELEASE_TAG` 等于 `v` 加 package 版本,且 CHANGELOG 中恰好有一个非空的对应版本章节。
|
|
26
26
|
|
package/docs/zh/roadmap.md
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
# 路线图
|
|
2
2
|
|
|
3
|
-
0.21.
|
|
3
|
+
0.21.16 已于 2026 年 9 月 24 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
|
|
4
|
+
|
|
5
|
+
## 0.21.16 已完成范围
|
|
6
|
+
|
|
7
|
+
- **延后的 transport 操作现在可以被它自己的释放取消。** `subscribeTransport()` 与 `unsubscribeTransport()`
|
|
8
|
+
先把意图写进 `transportSubscribedTopics`,再把一次*固化的调用*交给 `runTransport()`;因此停在 pending opening
|
|
9
|
+
后面的 subscribe,可能在已经取消它的那个 unsubscribe 之后才 flush。连接于是持有一条不属于任何本地 handler、
|
|
10
|
+
集群分配或路由记录的 channel,而且没有任何路径能恢复——因为防止重复订阅的去重判断读的就是这份过期集合。
|
|
11
|
+
`runTransport()` 现在额外接收“当前期望状态”的谓词,并在每个于后续任务中恢复的位置重新读取。三个回归用例,
|
|
12
|
+
并按腿归属变异结果:两个恢复点各有不相交的击杀集(1 个用例与 2 个用例),而把守卫反向后会有 20 个用例失败,
|
|
13
|
+
其中包括原有的“启动期间排队订阅”测试。
|
|
14
|
+
- **协同模糊测试的频道投递现在与浏览器一致。** `ChannelHub.setAsyncDelivery(true)` 把每次 post 延后一个
|
|
15
|
+
microtask;它是可选开启的,因为有 15 个测试发完帧就直接同步断言。在 sweep 的 hub 上启用它,正是把上一个版本
|
|
16
|
+
未解决的那个观察变成上面这个缺陷的原因——两个方向都做了对照测量:修复前六个 seed 失败,修复后为零,而同步
|
|
17
|
+
hub 在坏代码上是通过的。
|
|
18
|
+
- **提高保真度所*没有*主张的东西,同样写在原位。** 在已修复的代码上,异步 hub 不新增任何常驻变异击杀;延后
|
|
19
|
+
pump 内部重新读取成员集合属于防御性写法,在当前 fake 下不可观察;退订方向的实测代价是多一帧冗余线上消息,
|
|
20
|
+
而不是被搁置的 channel。
|
|
21
|
+
- **本次修复使其失效的随包文档**,中英两侧一并订正:两条订阅传递链都把集合写入画在 transport 调用*之后*(代码
|
|
22
|
+
是先写集合),恢复门段落只归功于取消 generation 守卫,而 `api.md` 的 `subscribe()` 一行只承诺了排队、没有承诺
|
|
23
|
+
取消。
|
|
24
|
+
|
|
25
|
+
## 0.21.15 已完成范围
|
|
26
|
+
|
|
27
|
+
- 两个点对点寻址守卫现在都被具名钉住了。`handleMessage` 只按 `type` 分发,而集群频道是广播的,所以
|
|
28
|
+
`targetWorkerId !== this.workerId` 这一行是唯一阻止某个 runtime 处理发给别人的帧的东西;逐行删除后测量,
|
|
29
|
+
先后有 369 个和 358 个测试仍然通过,而协调模糊测试无法断言它,因为它的不变式是结束态检查,收敛得慢一点
|
|
30
|
+
依然算通过。
|
|
31
|
+
- `ROUTE_RELEASED` 的那颗钉子需要一个它的陈旧性测试无法拒绝的构造:一条确实把本 runtime 写成 owner 的持久
|
|
32
|
+
路由,配上匹配的 `generation` 与 `handoffFromWorkerId`,再把 ACK 投给第三个 worker。这也正是较早那个伪造
|
|
33
|
+
`topicKey` 的用例从来走不到这一行的原因。
|
|
34
|
+
- 协调模糊测试增加了第三层限制,位置选在环路两边都不拥有的地方。原有的两层都由时间构成,而把 `verify` 跑红
|
|
35
|
+
的那个失败把时间花在微任务里,同线程上的任何截止时间都碰不到它;`ChannelHub` 现在会在单个 seed 超过 50,000
|
|
36
|
+
次投递后停止投递,阈值取自实测分布(p50 = 28、p99 = 90、最大值 = 16,763)而不是卡死的大小,被切断的 seed
|
|
37
|
+
会按名字打印并且不计入深度下限。
|
|
38
|
+
- 那条环路到底是什么,随后是被测量而不是被推断出来的,而它是测试装置而不是库:11,542 次接受对应 11,538 次
|
|
39
|
+
`confirmRoute` 跳过,而落到存储的写只有 6 次——因为 owner 的确认还躺在它自己的合批写入器里,对端读到的却是
|
|
40
|
+
已刷出的记录;而这个循环之所以让不出微任务,是测试用的 hub 把每一帧都在调用栈上同步投递。把投递推迟一个
|
|
41
|
+
微任务,风暴就彻底消失。本文件里此前把它写成"未修复的产品缺陷"的说法在此更正,而更正之后留下的六个注入帧
|
|
42
|
+
seed 作为下一个问题被点名,而不是被搪塞过去。
|
|
43
|
+
- `docs/release-checklist.md` 两种语言现在都写明:`pnpm bench:compare` 会被"调查它"这个动作本身关掉。它的
|
|
44
|
+
基线取自同一个滚动归档,而每次 `bench:browser` 都在往那个归档追加,所以规程变成:先看主机负载,把可疑失败
|
|
45
|
+
连同负载记为 deferred,而不是去追一个绿色。
|
|
46
|
+
- `docs/progress.md` 有了结构性门禁。同一个阶段的两个 PR 曾留下两个 `## Phase 142` 块,其中一个被挤到所有
|
|
47
|
+
条目都追加在前的那个锚点之下;`tests/documentation.test.ts` 现在要求每个阶段编号既是新的、又晚于上一条、
|
|
48
|
+
并且在常驻章节之上。
|
|
49
|
+
|
|
50
|
+
没有行为、线格式、存储布局或集群协议变更:本版本 `src/` 未改动,发布义务来自随包发布的
|
|
51
|
+
`docs/release-checklist.md` 与 `CHANGELOG.md`。
|
|
4
52
|
|
|
5
53
|
## 0.21.14 已完成范围
|
|
6
54
|
|
package/package.json
CHANGED