@crashlab/scheduler 0.1.0 → 0.1.2
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/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -123,6 +123,7 @@ var Scheduler = class {
|
|
|
123
123
|
while (j < ready.length && ready[j].when === ready[i].when) j++;
|
|
124
124
|
if (j - i > 1) {
|
|
125
125
|
const group = ready.slice(i, j);
|
|
126
|
+
group.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
126
127
|
shuffleInPlace(group, this._rng);
|
|
127
128
|
for (let k = 0; k < group.length; k++) {
|
|
128
129
|
ready[i + k] = group[k];
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/prng.ts"],"sourcesContent":["import type { IClock, PendingOp } from './types.js';\nimport { mulberry32, shuffleInPlace } from './prng.js';\n\nexport type { IClock, PendingOp } from './types.js';\n\nexport interface SchedulerOptions {\n /** Optional virtual clock. When attached, the scheduler can be called from clock.advance(). */\n clock?: IClock;\n /** Seed for the PRNG that determines execution order of same-tick ops. */\n prngSeed?: number;\n}\n\n/**\n * Cooperative deterministic scheduler.\n *\n * Holds mock-I/O completions until their virtual time arrives, then\n * releases them in a PRNG-determined order — making all macro-level\n * race conditions reproducible across runs with the same seed.\n *\n * **This scheduler does NOT intercept V8 microtasks or Promise internals.**\n * It controls ordering exclusively at mock I/O boundaries.\n */\nexport class Scheduler {\n private _clock: IClock | undefined;\n private readonly _rng: () => number;\n private readonly _pending: PendingOp[] = [];\n private _runChain: Promise<void> = Promise.resolve();\n private _autoDrainScheduled = false;\n private _requestedTick: number | null = null;\n\n constructor(opts: SchedulerOptions = {}) {\n this._clock = opts.clock;\n this._rng = mulberry32(opts.prngSeed ?? 0);\n }\n\n // public API\n\n /**\n * Enqueue a mock-I/O completion.\n *\n * The `run` callback is **not** invoked immediately — it is held in the\n * pending queue until `runTick(t)` is called with `t >= op.when`.\n */\n enqueueCompletion(op: PendingOp): void {\n this._pending.push(op);\n }\n\n /**\n * Request an asynchronous drain for ops ready at `virtualTime`.\n *\n * Multiple calls in the same turn are coalesced into one microtask and the\n * highest requested virtual time is used.\n */\n requestRunTick(virtualTime: number): void {\n this._requestedTick = this._requestedTick == null\n ? virtualTime\n : Math.max(this._requestedTick, virtualTime);\n\n if (this._autoDrainScheduled) return;\n this._autoDrainScheduled = true;\n\n queueMicrotask(() => {\n this._autoDrainScheduled = false;\n const tick = this._requestedTick;\n this._requestedTick = null;\n if (tick == null) return;\n void this.runTick(tick).catch(() => {\n // Errors still surface to explicit runTick callers; requestRunTick is\n // best-effort and must not throw asynchronously.\n });\n if (this._requestedTick != null) {\n this.requestRunTick(this._requestedTick);\n }\n });\n }\n\n /**\n * Attach (or replace) the clock reference.\n *\n * Integration contract: when the clock advances to time `t`, it should\n * call `await scheduler.runTick(t)`.\n */\n attachClock(clock: IClock): void {\n this._clock = clock;\n }\n\n /**\n * Collect all enqueued ops with `when <= virtualTime`, shuffle them\n * deterministically via the seeded PRNG, then execute their `run()`\n * callbacks **sequentially** in that shuffled order, awaiting one\n * microtask checkpoint (`await Promise.resolve()`) between each.\n */\n async runTick(virtualTime: number): Promise<void> {\n const run = async (): Promise<void> => {\n // Loop to handle cascading completions: ops enqueued during callback\n // execution that are also ready at this virtual time are picked up\n // in the next iteration, preserving causal ordering.\n while (true) {\n // 1. Partition: pull out all ops ready at this tick.\n const ready: PendingOp[] = [];\n const remaining: PendingOp[] = [];\n for (const op of this._pending) {\n if (op.when <= virtualTime) {\n ready.push(op);\n } else {\n remaining.push(op);\n }\n }\n this._pending.length = 0;\n this._pending.push(...remaining);\n\n if (ready.length === 0) break;\n\n // 2. Stable-sort by `when` ascending so earlier ops run first,\n // then shuffle within each same-`when` group via PRNG.\n ready.sort((a, b) => a.when - b.when);\n\n // Group by `when` and shuffle each group.\n let i = 0;\n while (i < ready.length) {\n let j = i;\n while (j < ready.length && ready[j].when === ready[i].when) j++;\n if (j - i > 1) {\n const group = ready.slice(i, j);\n shuffleInPlace(group, this._rng);\n for (let k = 0; k < group.length; k++) {\n ready[i + k] = group[k];\n }\n }\n i = j;\n }\n\n // 3. Execute sequentially, with a microtask boundary between each.\n for (const op of ready) {\n await op.run();\n await Promise.resolve(); // microtask checkpoint\n }\n }\n };\n\n const chained = this._runChain.then(run);\n this._runChain = chained.catch(() => {});\n return chained;\n }\n\n /**\n * Drain **all** pending ops immediately, regardless of their `when`.\n * Useful for test teardown.\n */\n async drain(): Promise<void> {\n await this.runTick(Number.MAX_SAFE_INTEGER);\n }\n\n /** Number of operations currently held in the pending queue. */\n get pendingCount(): number {\n return this._pending.length;\n }\n}\n","/**\n * Mulberry32 — fast 32-bit seeded PRNG.\n * Returns a function yielding numbers in [0, 1).\n *\n * This is a self-contained fallback so `@crashlab/scheduler` has zero\n * hard dependencies. If `@crashlab/random` is available in the monorepo,\n * consumers can pass their own SeededRandom instead.\n */\nexport function mulberry32(seed: number): () => number {\n let s = seed | 0;\n return function next(): number {\n s = (s + 0x6d2b79f5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Deterministic Fisher-Yates shuffle using a seeded PRNG.\n * Mutates `arr` in place and returns it.\n */\nexport function shuffleInPlace<T>(arr: T[], rng: () => number): T[] {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1));\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,SAAS,WAAW,MAA4B;AACrD,MAAI,IAAI,OAAO;AACf,SAAO,SAAS,OAAe;AAC7B,QAAK,IAAI,aAAc;AACvB,QAAI,IAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACvC,QAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,KAAK,CAAC,IAAK;AAC7C,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;AAMO,SAAS,eAAkB,KAAU,KAAwB;AAClE,WAAS,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACvC,UAAM,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,EAAE;AACpC,KAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAAA,EACpC;AACA,SAAO;AACT;;;ADNO,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACS;AAAA,EACA,WAAwB,CAAC;AAAA,EAClC,YAA2B,QAAQ,QAAQ;AAAA,EAC3C,sBAAsB;AAAA,EACtB,iBAAgC;AAAA,EAExC,YAAY,OAAyB,CAAC,GAAG;AACvC,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,WAAW,KAAK,YAAY,CAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAAkB,IAAqB;AACrC,SAAK,SAAS,KAAK,EAAE;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,aAA2B;AACxC,SAAK,iBAAiB,KAAK,kBAAkB,OACzC,cACA,KAAK,IAAI,KAAK,gBAAgB,WAAW;AAE7C,QAAI,KAAK,oBAAqB;AAC9B,SAAK,sBAAsB;AAE3B,mBAAe,MAAM;AACnB,WAAK,sBAAsB;AAC3B,YAAM,OAAO,KAAK;AAClB,WAAK,iBAAiB;AACtB,UAAI,QAAQ,KAAM;AAClB,WAAK,KAAK,QAAQ,IAAI,EAAE,MAAM,MAAM;AAAA,MAGpC,CAAC;AACD,UAAI,KAAK,kBAAkB,MAAM;AAC/B,aAAK,eAAe,KAAK,cAAc;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAqB;AAC/B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,aAAoC;AAChD,UAAM,MAAM,YAA2B;AAIrC,aAAO,MAAM;AAEX,cAAM,QAAqB,CAAC;AAC5B,cAAM,YAAyB,CAAC;AAChC,mBAAW,MAAM,KAAK,UAAU;AAC9B,cAAI,GAAG,QAAQ,aAAa;AAC1B,kBAAM,KAAK,EAAE;AAAA,UACf,OAAO;AACL,sBAAU,KAAK,EAAE;AAAA,UACnB;AAAA,QACF;AACA,aAAK,SAAS,SAAS;AACvB,aAAK,SAAS,KAAK,GAAG,SAAS;AAE/B,YAAI,MAAM,WAAW,EAAG;AAIxB,cAAM,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAGpC,YAAI,IAAI;AACR,eAAO,IAAI,MAAM,QAAQ;AACvB,cAAI,IAAI;AACR,iBAAO,IAAI,MAAM,UAAU,MAAM,CAAC,EAAE,SAAS,MAAM,CAAC,EAAE,KAAM;AAC5D,cAAI,IAAI,IAAI,GAAG;AACb,kBAAM,QAAQ,MAAM,MAAM,GAAG,CAAC;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/prng.ts"],"sourcesContent":["import type { IClock, PendingOp } from './types.js';\nimport { mulberry32, shuffleInPlace } from './prng.js';\n\nexport type { IClock, PendingOp } from './types.js';\n\nexport interface SchedulerOptions {\n /** Optional virtual clock. When attached, the scheduler can be called from clock.advance(). */\n clock?: IClock;\n /** Seed for the PRNG that determines execution order of same-tick ops. */\n prngSeed?: number;\n}\n\n/**\n * Cooperative deterministic scheduler.\n *\n * Holds mock-I/O completions until their virtual time arrives, then\n * releases them in a PRNG-determined order — making all macro-level\n * race conditions reproducible across runs with the same seed.\n *\n * **This scheduler does NOT intercept V8 microtasks or Promise internals.**\n * It controls ordering exclusively at mock I/O boundaries.\n */\nexport class Scheduler {\n private _clock: IClock | undefined;\n private readonly _rng: () => number;\n private readonly _pending: PendingOp[] = [];\n private _runChain: Promise<void> = Promise.resolve();\n private _autoDrainScheduled = false;\n private _requestedTick: number | null = null;\n\n constructor(opts: SchedulerOptions = {}) {\n this._clock = opts.clock;\n this._rng = mulberry32(opts.prngSeed ?? 0);\n }\n\n // public API\n\n /**\n * Enqueue a mock-I/O completion.\n *\n * The `run` callback is **not** invoked immediately — it is held in the\n * pending queue until `runTick(t)` is called with `t >= op.when`.\n */\n enqueueCompletion(op: PendingOp): void {\n this._pending.push(op);\n }\n\n /**\n * Request an asynchronous drain for ops ready at `virtualTime`.\n *\n * Multiple calls in the same turn are coalesced into one microtask and the\n * highest requested virtual time is used.\n */\n requestRunTick(virtualTime: number): void {\n this._requestedTick = this._requestedTick == null\n ? virtualTime\n : Math.max(this._requestedTick, virtualTime);\n\n if (this._autoDrainScheduled) return;\n this._autoDrainScheduled = true;\n\n queueMicrotask(() => {\n this._autoDrainScheduled = false;\n const tick = this._requestedTick;\n this._requestedTick = null;\n if (tick == null) return;\n void this.runTick(tick).catch(() => {\n // Errors still surface to explicit runTick callers; requestRunTick is\n // best-effort and must not throw asynchronously.\n });\n if (this._requestedTick != null) {\n this.requestRunTick(this._requestedTick);\n }\n });\n }\n\n /**\n * Attach (or replace) the clock reference.\n *\n * Integration contract: when the clock advances to time `t`, it should\n * call `await scheduler.runTick(t)`.\n */\n attachClock(clock: IClock): void {\n this._clock = clock;\n }\n\n /**\n * Collect all enqueued ops with `when <= virtualTime`, shuffle them\n * deterministically via the seeded PRNG, then execute their `run()`\n * callbacks **sequentially** in that shuffled order, awaiting one\n * microtask checkpoint (`await Promise.resolve()`) between each.\n */\n async runTick(virtualTime: number): Promise<void> {\n const run = async (): Promise<void> => {\n // Loop to handle cascading completions: ops enqueued during callback\n // execution that are also ready at this virtual time are picked up\n // in the next iteration, preserving causal ordering.\n while (true) {\n // 1. Partition: pull out all ops ready at this tick.\n const ready: PendingOp[] = [];\n const remaining: PendingOp[] = [];\n for (const op of this._pending) {\n if (op.when <= virtualTime) {\n ready.push(op);\n } else {\n remaining.push(op);\n }\n }\n this._pending.length = 0;\n this._pending.push(...remaining);\n\n if (ready.length === 0) break;\n\n // 2. Stable-sort by `when` ascending so earlier ops run first,\n // then shuffle within each same-`when` group via PRNG.\n ready.sort((a, b) => a.when - b.when);\n\n // Group by `when` and shuffle each group.\n let i = 0;\n while (i < ready.length) {\n let j = i;\n while (j < ready.length && ready[j].when === ready[i].when) j++;\n if (j - i > 1) {\n const group = ready.slice(i, j);\n // Sort by id first so the shuffle always starts from the same\n // canonical order regardless of real-event-loop enqueue order.\n // Without this, two ops enqueued as [A,B] vs [B,A] (due to\n // non-deterministic real timing) would produce different shuffle\n // outcomes for the same seed, breaking replay determinism.\n group.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n shuffleInPlace(group, this._rng);\n for (let k = 0; k < group.length; k++) {\n ready[i + k] = group[k];\n }\n }\n i = j;\n }\n\n // 3. Execute sequentially, with a microtask boundary between each.\n for (const op of ready) {\n await op.run();\n await Promise.resolve(); // microtask checkpoint\n }\n }\n };\n\n const chained = this._runChain.then(run);\n this._runChain = chained.catch(() => {});\n return chained;\n }\n\n /**\n * Drain **all** pending ops immediately, regardless of their `when`.\n * Useful for test teardown.\n */\n async drain(): Promise<void> {\n await this.runTick(Number.MAX_SAFE_INTEGER);\n }\n\n /** Number of operations currently held in the pending queue. */\n get pendingCount(): number {\n return this._pending.length;\n }\n}\n","/**\n * Mulberry32 — fast 32-bit seeded PRNG.\n * Returns a function yielding numbers in [0, 1).\n *\n * This is a self-contained fallback so `@crashlab/scheduler` has zero\n * hard dependencies. If `@crashlab/random` is available in the monorepo,\n * consumers can pass their own SeededRandom instead.\n */\nexport function mulberry32(seed: number): () => number {\n let s = seed | 0;\n return function next(): number {\n s = (s + 0x6d2b79f5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Deterministic Fisher-Yates shuffle using a seeded PRNG.\n * Mutates `arr` in place and returns it.\n */\nexport function shuffleInPlace<T>(arr: T[], rng: () => number): T[] {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1));\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,SAAS,WAAW,MAA4B;AACrD,MAAI,IAAI,OAAO;AACf,SAAO,SAAS,OAAe;AAC7B,QAAK,IAAI,aAAc;AACvB,QAAI,IAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACvC,QAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,KAAK,CAAC,IAAK;AAC7C,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;AAMO,SAAS,eAAkB,KAAU,KAAwB;AAClE,WAAS,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACvC,UAAM,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,EAAE;AACpC,KAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAAA,EACpC;AACA,SAAO;AACT;;;ADNO,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACS;AAAA,EACA,WAAwB,CAAC;AAAA,EAClC,YAA2B,QAAQ,QAAQ;AAAA,EAC3C,sBAAsB;AAAA,EACtB,iBAAgC;AAAA,EAExC,YAAY,OAAyB,CAAC,GAAG;AACvC,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,WAAW,KAAK,YAAY,CAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAAkB,IAAqB;AACrC,SAAK,SAAS,KAAK,EAAE;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,aAA2B;AACxC,SAAK,iBAAiB,KAAK,kBAAkB,OACzC,cACA,KAAK,IAAI,KAAK,gBAAgB,WAAW;AAE7C,QAAI,KAAK,oBAAqB;AAC9B,SAAK,sBAAsB;AAE3B,mBAAe,MAAM;AACnB,WAAK,sBAAsB;AAC3B,YAAM,OAAO,KAAK;AAClB,WAAK,iBAAiB;AACtB,UAAI,QAAQ,KAAM;AAClB,WAAK,KAAK,QAAQ,IAAI,EAAE,MAAM,MAAM;AAAA,MAGpC,CAAC;AACD,UAAI,KAAK,kBAAkB,MAAM;AAC/B,aAAK,eAAe,KAAK,cAAc;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAqB;AAC/B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,aAAoC;AAChD,UAAM,MAAM,YAA2B;AAIrC,aAAO,MAAM;AAEX,cAAM,QAAqB,CAAC;AAC5B,cAAM,YAAyB,CAAC;AAChC,mBAAW,MAAM,KAAK,UAAU;AAC9B,cAAI,GAAG,QAAQ,aAAa;AAC1B,kBAAM,KAAK,EAAE;AAAA,UACf,OAAO;AACL,sBAAU,KAAK,EAAE;AAAA,UACnB;AAAA,QACF;AACA,aAAK,SAAS,SAAS;AACvB,aAAK,SAAS,KAAK,GAAG,SAAS;AAE/B,YAAI,MAAM,WAAW,EAAG;AAIxB,cAAM,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAGpC,YAAI,IAAI;AACR,eAAO,IAAI,MAAM,QAAQ;AACvB,cAAI,IAAI;AACR,iBAAO,IAAI,MAAM,UAAU,MAAM,CAAC,EAAE,SAAS,MAAM,CAAC,EAAE,KAAM;AAC5D,cAAI,IAAI,IAAI,GAAG;AACb,kBAAM,QAAQ,MAAM,MAAM,GAAG,CAAC;AAM9B,kBAAM,KAAK,CAAC,GAAG,MAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAE;AAC7D,2BAAe,OAAO,KAAK,IAAI;AAC/B,qBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,oBAAM,IAAI,CAAC,IAAI,MAAM,CAAC;AAAA,YACxB;AAAA,UACF;AACA,cAAI;AAAA,QACN;AAGA,mBAAW,MAAM,OAAO;AACtB,gBAAM,GAAG,IAAI;AACb,gBAAM,QAAQ,QAAQ;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,KAAK,GAAG;AACvC,SAAK,YAAY,QAAQ,MAAM,MAAM;AAAA,IAAC,CAAC;AACvC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,UAAM,KAAK,QAAQ,OAAO,gBAAgB;AAAA,EAC5C;AAAA;AAAA,EAGA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;","names":[]}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGpD,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,cAAc,CAAuB;gBAEjC,IAAI,GAAE,gBAAqB;IAOvC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAItC;;;;;OAKG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAuBzC;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIhC;;;;;OAKG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGpD,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,cAAc,CAAuB;gBAEjC,IAAI,GAAE,gBAAqB;IAOvC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAItC;;;;;OAKG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAuBzC;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIhC;;;;;OAKG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2DjD;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,gEAAgE;IAChE,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF"}
|
package/dist/index.js
CHANGED
|
@@ -97,6 +97,7 @@ var Scheduler = class {
|
|
|
97
97
|
while (j < ready.length && ready[j].when === ready[i].when) j++;
|
|
98
98
|
if (j - i > 1) {
|
|
99
99
|
const group = ready.slice(i, j);
|
|
100
|
+
group.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
100
101
|
shuffleInPlace(group, this._rng);
|
|
101
102
|
for (let k = 0; k < group.length; k++) {
|
|
102
103
|
ready[i + k] = group[k];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/prng.ts","../src/index.ts"],"sourcesContent":["/**\n * Mulberry32 — fast 32-bit seeded PRNG.\n * Returns a function yielding numbers in [0, 1).\n *\n * This is a self-contained fallback so `@crashlab/scheduler` has zero\n * hard dependencies. If `@crashlab/random` is available in the monorepo,\n * consumers can pass their own SeededRandom instead.\n */\nexport function mulberry32(seed: number): () => number {\n let s = seed | 0;\n return function next(): number {\n s = (s + 0x6d2b79f5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Deterministic Fisher-Yates shuffle using a seeded PRNG.\n * Mutates `arr` in place and returns it.\n */\nexport function shuffleInPlace<T>(arr: T[], rng: () => number): T[] {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1));\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n","import type { IClock, PendingOp } from './types.js';\nimport { mulberry32, shuffleInPlace } from './prng.js';\n\nexport type { IClock, PendingOp } from './types.js';\n\nexport interface SchedulerOptions {\n /** Optional virtual clock. When attached, the scheduler can be called from clock.advance(). */\n clock?: IClock;\n /** Seed for the PRNG that determines execution order of same-tick ops. */\n prngSeed?: number;\n}\n\n/**\n * Cooperative deterministic scheduler.\n *\n * Holds mock-I/O completions until their virtual time arrives, then\n * releases them in a PRNG-determined order — making all macro-level\n * race conditions reproducible across runs with the same seed.\n *\n * **This scheduler does NOT intercept V8 microtasks or Promise internals.**\n * It controls ordering exclusively at mock I/O boundaries.\n */\nexport class Scheduler {\n private _clock: IClock | undefined;\n private readonly _rng: () => number;\n private readonly _pending: PendingOp[] = [];\n private _runChain: Promise<void> = Promise.resolve();\n private _autoDrainScheduled = false;\n private _requestedTick: number | null = null;\n\n constructor(opts: SchedulerOptions = {}) {\n this._clock = opts.clock;\n this._rng = mulberry32(opts.prngSeed ?? 0);\n }\n\n // public API\n\n /**\n * Enqueue a mock-I/O completion.\n *\n * The `run` callback is **not** invoked immediately — it is held in the\n * pending queue until `runTick(t)` is called with `t >= op.when`.\n */\n enqueueCompletion(op: PendingOp): void {\n this._pending.push(op);\n }\n\n /**\n * Request an asynchronous drain for ops ready at `virtualTime`.\n *\n * Multiple calls in the same turn are coalesced into one microtask and the\n * highest requested virtual time is used.\n */\n requestRunTick(virtualTime: number): void {\n this._requestedTick = this._requestedTick == null\n ? virtualTime\n : Math.max(this._requestedTick, virtualTime);\n\n if (this._autoDrainScheduled) return;\n this._autoDrainScheduled = true;\n\n queueMicrotask(() => {\n this._autoDrainScheduled = false;\n const tick = this._requestedTick;\n this._requestedTick = null;\n if (tick == null) return;\n void this.runTick(tick).catch(() => {\n // Errors still surface to explicit runTick callers; requestRunTick is\n // best-effort and must not throw asynchronously.\n });\n if (this._requestedTick != null) {\n this.requestRunTick(this._requestedTick);\n }\n });\n }\n\n /**\n * Attach (or replace) the clock reference.\n *\n * Integration contract: when the clock advances to time `t`, it should\n * call `await scheduler.runTick(t)`.\n */\n attachClock(clock: IClock): void {\n this._clock = clock;\n }\n\n /**\n * Collect all enqueued ops with `when <= virtualTime`, shuffle them\n * deterministically via the seeded PRNG, then execute their `run()`\n * callbacks **sequentially** in that shuffled order, awaiting one\n * microtask checkpoint (`await Promise.resolve()`) between each.\n */\n async runTick(virtualTime: number): Promise<void> {\n const run = async (): Promise<void> => {\n // Loop to handle cascading completions: ops enqueued during callback\n // execution that are also ready at this virtual time are picked up\n // in the next iteration, preserving causal ordering.\n while (true) {\n // 1. Partition: pull out all ops ready at this tick.\n const ready: PendingOp[] = [];\n const remaining: PendingOp[] = [];\n for (const op of this._pending) {\n if (op.when <= virtualTime) {\n ready.push(op);\n } else {\n remaining.push(op);\n }\n }\n this._pending.length = 0;\n this._pending.push(...remaining);\n\n if (ready.length === 0) break;\n\n // 2. Stable-sort by `when` ascending so earlier ops run first,\n // then shuffle within each same-`when` group via PRNG.\n ready.sort((a, b) => a.when - b.when);\n\n // Group by `when` and shuffle each group.\n let i = 0;\n while (i < ready.length) {\n let j = i;\n while (j < ready.length && ready[j].when === ready[i].when) j++;\n if (j - i > 1) {\n const group = ready.slice(i, j);\n shuffleInPlace(group, this._rng);\n for (let k = 0; k < group.length; k++) {\n ready[i + k] = group[k];\n }\n }\n i = j;\n }\n\n // 3. Execute sequentially, with a microtask boundary between each.\n for (const op of ready) {\n await op.run();\n await Promise.resolve(); // microtask checkpoint\n }\n }\n };\n\n const chained = this._runChain.then(run);\n this._runChain = chained.catch(() => {});\n return chained;\n }\n\n /**\n * Drain **all** pending ops immediately, regardless of their `when`.\n * Useful for test teardown.\n */\n async drain(): Promise<void> {\n await this.runTick(Number.MAX_SAFE_INTEGER);\n }\n\n /** Number of operations currently held in the pending queue. */\n get pendingCount(): number {\n return this._pending.length;\n }\n}\n"],"mappings":";AAQO,SAAS,WAAW,MAA4B;AACrD,MAAI,IAAI,OAAO;AACf,SAAO,SAAS,OAAe;AAC7B,QAAK,IAAI,aAAc;AACvB,QAAI,IAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACvC,QAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,KAAK,CAAC,IAAK;AAC7C,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;AAMO,SAAS,eAAkB,KAAU,KAAwB;AAClE,WAAS,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACvC,UAAM,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,EAAE;AACpC,KAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAAA,EACpC;AACA,SAAO;AACT;;;ACNO,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACS;AAAA,EACA,WAAwB,CAAC;AAAA,EAClC,YAA2B,QAAQ,QAAQ;AAAA,EAC3C,sBAAsB;AAAA,EACtB,iBAAgC;AAAA,EAExC,YAAY,OAAyB,CAAC,GAAG;AACvC,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,WAAW,KAAK,YAAY,CAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAAkB,IAAqB;AACrC,SAAK,SAAS,KAAK,EAAE;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,aAA2B;AACxC,SAAK,iBAAiB,KAAK,kBAAkB,OACzC,cACA,KAAK,IAAI,KAAK,gBAAgB,WAAW;AAE7C,QAAI,KAAK,oBAAqB;AAC9B,SAAK,sBAAsB;AAE3B,mBAAe,MAAM;AACnB,WAAK,sBAAsB;AAC3B,YAAM,OAAO,KAAK;AAClB,WAAK,iBAAiB;AACtB,UAAI,QAAQ,KAAM;AAClB,WAAK,KAAK,QAAQ,IAAI,EAAE,MAAM,MAAM;AAAA,MAGpC,CAAC;AACD,UAAI,KAAK,kBAAkB,MAAM;AAC/B,aAAK,eAAe,KAAK,cAAc;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAqB;AAC/B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,aAAoC;AAChD,UAAM,MAAM,YAA2B;AAIrC,aAAO,MAAM;AAEX,cAAM,QAAqB,CAAC;AAC5B,cAAM,YAAyB,CAAC;AAChC,mBAAW,MAAM,KAAK,UAAU;AAC9B,cAAI,GAAG,QAAQ,aAAa;AAC1B,kBAAM,KAAK,EAAE;AAAA,UACf,OAAO;AACL,sBAAU,KAAK,EAAE;AAAA,UACnB;AAAA,QACF;AACA,aAAK,SAAS,SAAS;AACvB,aAAK,SAAS,KAAK,GAAG,SAAS;AAE/B,YAAI,MAAM,WAAW,EAAG;AAIxB,cAAM,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAGpC,YAAI,IAAI;AACR,eAAO,IAAI,MAAM,QAAQ;AACvB,cAAI,IAAI;AACR,iBAAO,IAAI,MAAM,UAAU,MAAM,CAAC,EAAE,SAAS,MAAM,CAAC,EAAE,KAAM;AAC5D,cAAI,IAAI,IAAI,GAAG;AACb,kBAAM,QAAQ,MAAM,MAAM,GAAG,CAAC;
|
|
1
|
+
{"version":3,"sources":["../src/prng.ts","../src/index.ts"],"sourcesContent":["/**\n * Mulberry32 — fast 32-bit seeded PRNG.\n * Returns a function yielding numbers in [0, 1).\n *\n * This is a self-contained fallback so `@crashlab/scheduler` has zero\n * hard dependencies. If `@crashlab/random` is available in the monorepo,\n * consumers can pass their own SeededRandom instead.\n */\nexport function mulberry32(seed: number): () => number {\n let s = seed | 0;\n return function next(): number {\n s = (s + 0x6d2b79f5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Deterministic Fisher-Yates shuffle using a seeded PRNG.\n * Mutates `arr` in place and returns it.\n */\nexport function shuffleInPlace<T>(arr: T[], rng: () => number): T[] {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1));\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n","import type { IClock, PendingOp } from './types.js';\nimport { mulberry32, shuffleInPlace } from './prng.js';\n\nexport type { IClock, PendingOp } from './types.js';\n\nexport interface SchedulerOptions {\n /** Optional virtual clock. When attached, the scheduler can be called from clock.advance(). */\n clock?: IClock;\n /** Seed for the PRNG that determines execution order of same-tick ops. */\n prngSeed?: number;\n}\n\n/**\n * Cooperative deterministic scheduler.\n *\n * Holds mock-I/O completions until their virtual time arrives, then\n * releases them in a PRNG-determined order — making all macro-level\n * race conditions reproducible across runs with the same seed.\n *\n * **This scheduler does NOT intercept V8 microtasks or Promise internals.**\n * It controls ordering exclusively at mock I/O boundaries.\n */\nexport class Scheduler {\n private _clock: IClock | undefined;\n private readonly _rng: () => number;\n private readonly _pending: PendingOp[] = [];\n private _runChain: Promise<void> = Promise.resolve();\n private _autoDrainScheduled = false;\n private _requestedTick: number | null = null;\n\n constructor(opts: SchedulerOptions = {}) {\n this._clock = opts.clock;\n this._rng = mulberry32(opts.prngSeed ?? 0);\n }\n\n // public API\n\n /**\n * Enqueue a mock-I/O completion.\n *\n * The `run` callback is **not** invoked immediately — it is held in the\n * pending queue until `runTick(t)` is called with `t >= op.when`.\n */\n enqueueCompletion(op: PendingOp): void {\n this._pending.push(op);\n }\n\n /**\n * Request an asynchronous drain for ops ready at `virtualTime`.\n *\n * Multiple calls in the same turn are coalesced into one microtask and the\n * highest requested virtual time is used.\n */\n requestRunTick(virtualTime: number): void {\n this._requestedTick = this._requestedTick == null\n ? virtualTime\n : Math.max(this._requestedTick, virtualTime);\n\n if (this._autoDrainScheduled) return;\n this._autoDrainScheduled = true;\n\n queueMicrotask(() => {\n this._autoDrainScheduled = false;\n const tick = this._requestedTick;\n this._requestedTick = null;\n if (tick == null) return;\n void this.runTick(tick).catch(() => {\n // Errors still surface to explicit runTick callers; requestRunTick is\n // best-effort and must not throw asynchronously.\n });\n if (this._requestedTick != null) {\n this.requestRunTick(this._requestedTick);\n }\n });\n }\n\n /**\n * Attach (or replace) the clock reference.\n *\n * Integration contract: when the clock advances to time `t`, it should\n * call `await scheduler.runTick(t)`.\n */\n attachClock(clock: IClock): void {\n this._clock = clock;\n }\n\n /**\n * Collect all enqueued ops with `when <= virtualTime`, shuffle them\n * deterministically via the seeded PRNG, then execute their `run()`\n * callbacks **sequentially** in that shuffled order, awaiting one\n * microtask checkpoint (`await Promise.resolve()`) between each.\n */\n async runTick(virtualTime: number): Promise<void> {\n const run = async (): Promise<void> => {\n // Loop to handle cascading completions: ops enqueued during callback\n // execution that are also ready at this virtual time are picked up\n // in the next iteration, preserving causal ordering.\n while (true) {\n // 1. Partition: pull out all ops ready at this tick.\n const ready: PendingOp[] = [];\n const remaining: PendingOp[] = [];\n for (const op of this._pending) {\n if (op.when <= virtualTime) {\n ready.push(op);\n } else {\n remaining.push(op);\n }\n }\n this._pending.length = 0;\n this._pending.push(...remaining);\n\n if (ready.length === 0) break;\n\n // 2. Stable-sort by `when` ascending so earlier ops run first,\n // then shuffle within each same-`when` group via PRNG.\n ready.sort((a, b) => a.when - b.when);\n\n // Group by `when` and shuffle each group.\n let i = 0;\n while (i < ready.length) {\n let j = i;\n while (j < ready.length && ready[j].when === ready[i].when) j++;\n if (j - i > 1) {\n const group = ready.slice(i, j);\n // Sort by id first so the shuffle always starts from the same\n // canonical order regardless of real-event-loop enqueue order.\n // Without this, two ops enqueued as [A,B] vs [B,A] (due to\n // non-deterministic real timing) would produce different shuffle\n // outcomes for the same seed, breaking replay determinism.\n group.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n shuffleInPlace(group, this._rng);\n for (let k = 0; k < group.length; k++) {\n ready[i + k] = group[k];\n }\n }\n i = j;\n }\n\n // 3. Execute sequentially, with a microtask boundary between each.\n for (const op of ready) {\n await op.run();\n await Promise.resolve(); // microtask checkpoint\n }\n }\n };\n\n const chained = this._runChain.then(run);\n this._runChain = chained.catch(() => {});\n return chained;\n }\n\n /**\n * Drain **all** pending ops immediately, regardless of their `when`.\n * Useful for test teardown.\n */\n async drain(): Promise<void> {\n await this.runTick(Number.MAX_SAFE_INTEGER);\n }\n\n /** Number of operations currently held in the pending queue. */\n get pendingCount(): number {\n return this._pending.length;\n }\n}\n"],"mappings":"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","names":[]}
|