mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-10-19 13:30:37 +00:00
fix(executors): make registerLazyExecutor HMR-safe (#15792)
⚠️ base-red inherited: #15306
Validated in local merge-train _artifacts/merge-wave-14/train.log on devbox-vm-06-dev002 @ 9f6fb4713421394d33142e91521fb43887000b13 (FAST), base 97da13f773. Both #15792 and #15858 boarded. Static gates green except inherited translation drift of docs/reference/ENVIRONMENT.md, the same violations on origin/release/v3.8.52. The changed node:test (executors-registry-hmr-safe) passed. Vitest timeouts in autoCombo and MCP audit reproduced on the pure origin/release/v3.8.52 tip (5 files timed out there; 4 on the train). Those files are not in this train's diff.
This commit is contained in:
parent
97da13f773
commit
363ed1bbae
3 changed files with 69 additions and 2 deletions
1
changelog.d/fixes/15792-executors-hmr-safe.md
Normal file
1
changelog.d/fixes/15792-executors-hmr-safe.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- **fix(executors):** make `registerLazyExecutor` HMR-safe so hot-reload no longer double-registers executors ([#15792](https://github.com/diegosouzapw/OmniRoute/pull/15792)) — thanks @Junior-HJ
|
||||
|
|
@ -37,11 +37,17 @@ export function getRegisteredExecutor(alias: string): BaseExecutor | undefined {
|
|||
// static registration.
|
||||
const lazyLoaders = new Map<string, () => Promise<BaseExecutor>>();
|
||||
const lazyInFlight = new Map<string, Promise<BaseExecutor>>();
|
||||
// Bumped on every re-register so a load already in flight cannot publish the
|
||||
// old executor or delete the replacement loader (#15792).
|
||||
const lazyGeneration = new Map<string, number>();
|
||||
|
||||
export function registerLazyExecutor(alias: string, load: () => Promise<BaseExecutor>): void {
|
||||
if (registry.has(alias) || lazyLoaders.has(alias)) {
|
||||
throw new Error(`executor alias already registered: "${alias}"`);
|
||||
// If re-evaluated under HMR / dev reload, overwrite lazy loader and clear cached instance
|
||||
if (registry.has(alias)) {
|
||||
registry.delete(alias);
|
||||
}
|
||||
lazyInFlight.delete(alias);
|
||||
lazyGeneration.set(alias, (lazyGeneration.get(alias) ?? 0) + 1);
|
||||
lazyLoaders.set(alias, load);
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +58,9 @@ export function loadRegisteredExecutor(alias: string): Promise<BaseExecutor> | u
|
|||
if (!load) return undefined;
|
||||
let inFlight = lazyInFlight.get(alias);
|
||||
if (!inFlight) {
|
||||
const generation = lazyGeneration.get(alias) ?? 0;
|
||||
inFlight = load().then((executor) => {
|
||||
if ((lazyGeneration.get(alias) ?? 0) !== generation) return executor;
|
||||
registerExecutor(alias, executor);
|
||||
lazyLoaders.delete(alias);
|
||||
lazyInFlight.delete(alias);
|
||||
|
|
|
|||
58
tests/unit/executors-registry-hmr-safe.test.ts
Normal file
58
tests/unit/executors-registry-hmr-safe.test.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
registerLazyExecutor,
|
||||
loadRegisteredExecutor,
|
||||
hasRegisteredExecutor,
|
||||
} from "../../open-sse/executors/registry.ts";
|
||||
import { BaseExecutor } from "../../open-sse/executors/base.ts";
|
||||
|
||||
class DummyExecutor extends BaseExecutor {
|
||||
constructor(public version: number) {
|
||||
super("dummy");
|
||||
}
|
||||
async execute(): Promise<Response> {
|
||||
return new Response("ok");
|
||||
}
|
||||
}
|
||||
|
||||
test("registerLazyExecutor is HMR-safe and re-registration overwrites cached instance", async () => {
|
||||
const alias = `test-hmr-alias-${Date.now()}`;
|
||||
|
||||
// Initial lazy registration
|
||||
registerLazyExecutor(alias, async () => new DummyExecutor(1));
|
||||
assert.ok(hasRegisteredExecutor(alias), "lazy executor is registered");
|
||||
|
||||
// Load and cache instance v1
|
||||
const v1 = (await loadRegisteredExecutor(alias)) as DummyExecutor;
|
||||
assert.equal(v1.version, 1);
|
||||
|
||||
// Re-evaluation under HMR: re-register same alias must not throw
|
||||
assert.doesNotThrow(() => {
|
||||
registerLazyExecutor(alias, async () => new DummyExecutor(2));
|
||||
}, "re-registration under HMR should not throw duplicate alias error");
|
||||
|
||||
// Loading again resolves new instance v2 (cached v1 was purged)
|
||||
const v2 = (await loadRegisteredExecutor(alias)) as DummyExecutor;
|
||||
assert.equal(v2.version, 2, "subsequent load resolves updated executor version");
|
||||
});
|
||||
|
||||
test("an in-flight load cannot republish the stale executor after HMR", async () => {
|
||||
const alias = `test-hmr-inflight-${Date.now()}`;
|
||||
let release: (executor: DummyExecutor) => void = () => {};
|
||||
const gate = new Promise<DummyExecutor>((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
|
||||
registerLazyExecutor(alias, () => gate);
|
||||
const first = loadRegisteredExecutor(alias);
|
||||
assert.ok(first, "first load starts");
|
||||
|
||||
registerLazyExecutor(alias, async () => new DummyExecutor(2));
|
||||
release(new DummyExecutor(1));
|
||||
const stale = (await first) as DummyExecutor;
|
||||
assert.equal(stale.version, 1, "the caller that started v1 still receives v1");
|
||||
|
||||
const current = (await loadRegisteredExecutor(alias)) as DummyExecutor;
|
||||
assert.equal(current.version, 2, "a load started after HMR resolves the replacement");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue