@crashlab/scheduler 0.1.0
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/README.md +29 -0
- package/dist/index.cjs +160 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/prng.d.ts +15 -0
- package/dist/prng.d.ts.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @crashlab/scheduler
|
|
2
|
+
|
|
3
|
+
Cooperative deterministic scheduler for mock I/O boundaries. Holds enqueued completions until their virtual time arrives, then releases them in a PRNG-shuffled order — making all macro-level race conditions fully reproducible with the same seed. Does **not** intercept V8 microtasks or Promise internals.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Scheduler } from '@crashlab/scheduler';
|
|
9
|
+
|
|
10
|
+
const sched = new Scheduler({ prngSeed: 42 });
|
|
11
|
+
|
|
12
|
+
// Mock I/O layer enqueues completions:
|
|
13
|
+
sched.enqueueCompletion({ id: 'db-read-1', when: 80, run: () => resolveQuery1() });
|
|
14
|
+
sched.enqueueCompletion({ id: 'db-read-2', when: 80, run: () => resolveQuery2() });
|
|
15
|
+
|
|
16
|
+
// When the virtual clock advances to t=80:
|
|
17
|
+
await sched.runTick(80);
|
|
18
|
+
// Both completions run in deterministic PRNG-shuffled order.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Clock integration
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { VirtualClock } from '@crashlab/clock';
|
|
25
|
+
|
|
26
|
+
const clock = new VirtualClock(0);
|
|
27
|
+
const sched = new Scheduler({ clock, prngSeed: 42 });
|
|
28
|
+
// Contract: when clock.advance() reaches time t, call await sched.runTick(t).
|
|
29
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Scheduler: () => Scheduler
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/prng.ts
|
|
28
|
+
function mulberry32(seed) {
|
|
29
|
+
let s = seed | 0;
|
|
30
|
+
return function next() {
|
|
31
|
+
s = s + 1831565813 | 0;
|
|
32
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
33
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
34
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function shuffleInPlace(arr, rng) {
|
|
38
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
39
|
+
const j = Math.floor(rng() * (i + 1));
|
|
40
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
41
|
+
}
|
|
42
|
+
return arr;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/index.ts
|
|
46
|
+
var Scheduler = class {
|
|
47
|
+
_clock;
|
|
48
|
+
_rng;
|
|
49
|
+
_pending = [];
|
|
50
|
+
_runChain = Promise.resolve();
|
|
51
|
+
_autoDrainScheduled = false;
|
|
52
|
+
_requestedTick = null;
|
|
53
|
+
constructor(opts = {}) {
|
|
54
|
+
this._clock = opts.clock;
|
|
55
|
+
this._rng = mulberry32(opts.prngSeed ?? 0);
|
|
56
|
+
}
|
|
57
|
+
// public API
|
|
58
|
+
/**
|
|
59
|
+
* Enqueue a mock-I/O completion.
|
|
60
|
+
*
|
|
61
|
+
* The `run` callback is **not** invoked immediately — it is held in the
|
|
62
|
+
* pending queue until `runTick(t)` is called with `t >= op.when`.
|
|
63
|
+
*/
|
|
64
|
+
enqueueCompletion(op) {
|
|
65
|
+
this._pending.push(op);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Request an asynchronous drain for ops ready at `virtualTime`.
|
|
69
|
+
*
|
|
70
|
+
* Multiple calls in the same turn are coalesced into one microtask and the
|
|
71
|
+
* highest requested virtual time is used.
|
|
72
|
+
*/
|
|
73
|
+
requestRunTick(virtualTime) {
|
|
74
|
+
this._requestedTick = this._requestedTick == null ? virtualTime : Math.max(this._requestedTick, virtualTime);
|
|
75
|
+
if (this._autoDrainScheduled) return;
|
|
76
|
+
this._autoDrainScheduled = true;
|
|
77
|
+
queueMicrotask(() => {
|
|
78
|
+
this._autoDrainScheduled = false;
|
|
79
|
+
const tick = this._requestedTick;
|
|
80
|
+
this._requestedTick = null;
|
|
81
|
+
if (tick == null) return;
|
|
82
|
+
void this.runTick(tick).catch(() => {
|
|
83
|
+
});
|
|
84
|
+
if (this._requestedTick != null) {
|
|
85
|
+
this.requestRunTick(this._requestedTick);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Attach (or replace) the clock reference.
|
|
91
|
+
*
|
|
92
|
+
* Integration contract: when the clock advances to time `t`, it should
|
|
93
|
+
* call `await scheduler.runTick(t)`.
|
|
94
|
+
*/
|
|
95
|
+
attachClock(clock) {
|
|
96
|
+
this._clock = clock;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Collect all enqueued ops with `when <= virtualTime`, shuffle them
|
|
100
|
+
* deterministically via the seeded PRNG, then execute their `run()`
|
|
101
|
+
* callbacks **sequentially** in that shuffled order, awaiting one
|
|
102
|
+
* microtask checkpoint (`await Promise.resolve()`) between each.
|
|
103
|
+
*/
|
|
104
|
+
async runTick(virtualTime) {
|
|
105
|
+
const run = async () => {
|
|
106
|
+
while (true) {
|
|
107
|
+
const ready = [];
|
|
108
|
+
const remaining = [];
|
|
109
|
+
for (const op of this._pending) {
|
|
110
|
+
if (op.when <= virtualTime) {
|
|
111
|
+
ready.push(op);
|
|
112
|
+
} else {
|
|
113
|
+
remaining.push(op);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this._pending.length = 0;
|
|
117
|
+
this._pending.push(...remaining);
|
|
118
|
+
if (ready.length === 0) break;
|
|
119
|
+
ready.sort((a, b) => a.when - b.when);
|
|
120
|
+
let i = 0;
|
|
121
|
+
while (i < ready.length) {
|
|
122
|
+
let j = i;
|
|
123
|
+
while (j < ready.length && ready[j].when === ready[i].when) j++;
|
|
124
|
+
if (j - i > 1) {
|
|
125
|
+
const group = ready.slice(i, j);
|
|
126
|
+
shuffleInPlace(group, this._rng);
|
|
127
|
+
for (let k = 0; k < group.length; k++) {
|
|
128
|
+
ready[i + k] = group[k];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
i = j;
|
|
132
|
+
}
|
|
133
|
+
for (const op of ready) {
|
|
134
|
+
await op.run();
|
|
135
|
+
await Promise.resolve();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const chained = this._runChain.then(run);
|
|
140
|
+
this._runChain = chained.catch(() => {
|
|
141
|
+
});
|
|
142
|
+
return chained;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Drain **all** pending ops immediately, regardless of their `when`.
|
|
146
|
+
* Useful for test teardown.
|
|
147
|
+
*/
|
|
148
|
+
async drain() {
|
|
149
|
+
await this.runTick(Number.MAX_SAFE_INTEGER);
|
|
150
|
+
}
|
|
151
|
+
/** Number of operations currently held in the pending queue. */
|
|
152
|
+
get pendingCount() {
|
|
153
|
+
return this._pending.length;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
157
|
+
0 && (module.exports = {
|
|
158
|
+
Scheduler
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { IClock, PendingOp } from './types.js';
|
|
2
|
+
export type { IClock, PendingOp } from './types.js';
|
|
3
|
+
export interface SchedulerOptions {
|
|
4
|
+
/** Optional virtual clock. When attached, the scheduler can be called from clock.advance(). */
|
|
5
|
+
clock?: IClock;
|
|
6
|
+
/** Seed for the PRNG that determines execution order of same-tick ops. */
|
|
7
|
+
prngSeed?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Cooperative deterministic scheduler.
|
|
11
|
+
*
|
|
12
|
+
* Holds mock-I/O completions until their virtual time arrives, then
|
|
13
|
+
* releases them in a PRNG-determined order — making all macro-level
|
|
14
|
+
* race conditions reproducible across runs with the same seed.
|
|
15
|
+
*
|
|
16
|
+
* **This scheduler does NOT intercept V8 microtasks or Promise internals.**
|
|
17
|
+
* It controls ordering exclusively at mock I/O boundaries.
|
|
18
|
+
*/
|
|
19
|
+
export declare class Scheduler {
|
|
20
|
+
private _clock;
|
|
21
|
+
private readonly _rng;
|
|
22
|
+
private readonly _pending;
|
|
23
|
+
private _runChain;
|
|
24
|
+
private _autoDrainScheduled;
|
|
25
|
+
private _requestedTick;
|
|
26
|
+
constructor(opts?: SchedulerOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Enqueue a mock-I/O completion.
|
|
29
|
+
*
|
|
30
|
+
* The `run` callback is **not** invoked immediately — it is held in the
|
|
31
|
+
* pending queue until `runTick(t)` is called with `t >= op.when`.
|
|
32
|
+
*/
|
|
33
|
+
enqueueCompletion(op: PendingOp): void;
|
|
34
|
+
/**
|
|
35
|
+
* Request an asynchronous drain for ops ready at `virtualTime`.
|
|
36
|
+
*
|
|
37
|
+
* Multiple calls in the same turn are coalesced into one microtask and the
|
|
38
|
+
* highest requested virtual time is used.
|
|
39
|
+
*/
|
|
40
|
+
requestRunTick(virtualTime: number): void;
|
|
41
|
+
/**
|
|
42
|
+
* Attach (or replace) the clock reference.
|
|
43
|
+
*
|
|
44
|
+
* Integration contract: when the clock advances to time `t`, it should
|
|
45
|
+
* call `await scheduler.runTick(t)`.
|
|
46
|
+
*/
|
|
47
|
+
attachClock(clock: IClock): void;
|
|
48
|
+
/**
|
|
49
|
+
* Collect all enqueued ops with `when <= virtualTime`, shuffle them
|
|
50
|
+
* deterministically via the seeded PRNG, then execute their `run()`
|
|
51
|
+
* callbacks **sequentially** in that shuffled order, awaiting one
|
|
52
|
+
* microtask checkpoint (`await Promise.resolve()`) between each.
|
|
53
|
+
*/
|
|
54
|
+
runTick(virtualTime: number): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Drain **all** pending ops immediately, regardless of their `when`.
|
|
57
|
+
* Useful for test teardown.
|
|
58
|
+
*/
|
|
59
|
+
drain(): Promise<void>;
|
|
60
|
+
/** Number of operations currently held in the pending queue. */
|
|
61
|
+
get pendingCount(): number;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;IAqDjD;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,gEAAgE;IAChE,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/prng.ts
|
|
2
|
+
function mulberry32(seed) {
|
|
3
|
+
let s = seed | 0;
|
|
4
|
+
return function next() {
|
|
5
|
+
s = s + 1831565813 | 0;
|
|
6
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
7
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
8
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function shuffleInPlace(arr, rng) {
|
|
12
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
13
|
+
const j = Math.floor(rng() * (i + 1));
|
|
14
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
15
|
+
}
|
|
16
|
+
return arr;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var Scheduler = class {
|
|
21
|
+
_clock;
|
|
22
|
+
_rng;
|
|
23
|
+
_pending = [];
|
|
24
|
+
_runChain = Promise.resolve();
|
|
25
|
+
_autoDrainScheduled = false;
|
|
26
|
+
_requestedTick = null;
|
|
27
|
+
constructor(opts = {}) {
|
|
28
|
+
this._clock = opts.clock;
|
|
29
|
+
this._rng = mulberry32(opts.prngSeed ?? 0);
|
|
30
|
+
}
|
|
31
|
+
// public API
|
|
32
|
+
/**
|
|
33
|
+
* Enqueue a mock-I/O completion.
|
|
34
|
+
*
|
|
35
|
+
* The `run` callback is **not** invoked immediately — it is held in the
|
|
36
|
+
* pending queue until `runTick(t)` is called with `t >= op.when`.
|
|
37
|
+
*/
|
|
38
|
+
enqueueCompletion(op) {
|
|
39
|
+
this._pending.push(op);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Request an asynchronous drain for ops ready at `virtualTime`.
|
|
43
|
+
*
|
|
44
|
+
* Multiple calls in the same turn are coalesced into one microtask and the
|
|
45
|
+
* highest requested virtual time is used.
|
|
46
|
+
*/
|
|
47
|
+
requestRunTick(virtualTime) {
|
|
48
|
+
this._requestedTick = this._requestedTick == null ? virtualTime : Math.max(this._requestedTick, virtualTime);
|
|
49
|
+
if (this._autoDrainScheduled) return;
|
|
50
|
+
this._autoDrainScheduled = true;
|
|
51
|
+
queueMicrotask(() => {
|
|
52
|
+
this._autoDrainScheduled = false;
|
|
53
|
+
const tick = this._requestedTick;
|
|
54
|
+
this._requestedTick = null;
|
|
55
|
+
if (tick == null) return;
|
|
56
|
+
void this.runTick(tick).catch(() => {
|
|
57
|
+
});
|
|
58
|
+
if (this._requestedTick != null) {
|
|
59
|
+
this.requestRunTick(this._requestedTick);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Attach (or replace) the clock reference.
|
|
65
|
+
*
|
|
66
|
+
* Integration contract: when the clock advances to time `t`, it should
|
|
67
|
+
* call `await scheduler.runTick(t)`.
|
|
68
|
+
*/
|
|
69
|
+
attachClock(clock) {
|
|
70
|
+
this._clock = clock;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Collect all enqueued ops with `when <= virtualTime`, shuffle them
|
|
74
|
+
* deterministically via the seeded PRNG, then execute their `run()`
|
|
75
|
+
* callbacks **sequentially** in that shuffled order, awaiting one
|
|
76
|
+
* microtask checkpoint (`await Promise.resolve()`) between each.
|
|
77
|
+
*/
|
|
78
|
+
async runTick(virtualTime) {
|
|
79
|
+
const run = async () => {
|
|
80
|
+
while (true) {
|
|
81
|
+
const ready = [];
|
|
82
|
+
const remaining = [];
|
|
83
|
+
for (const op of this._pending) {
|
|
84
|
+
if (op.when <= virtualTime) {
|
|
85
|
+
ready.push(op);
|
|
86
|
+
} else {
|
|
87
|
+
remaining.push(op);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this._pending.length = 0;
|
|
91
|
+
this._pending.push(...remaining);
|
|
92
|
+
if (ready.length === 0) break;
|
|
93
|
+
ready.sort((a, b) => a.when - b.when);
|
|
94
|
+
let i = 0;
|
|
95
|
+
while (i < ready.length) {
|
|
96
|
+
let j = i;
|
|
97
|
+
while (j < ready.length && ready[j].when === ready[i].when) j++;
|
|
98
|
+
if (j - i > 1) {
|
|
99
|
+
const group = ready.slice(i, j);
|
|
100
|
+
shuffleInPlace(group, this._rng);
|
|
101
|
+
for (let k = 0; k < group.length; k++) {
|
|
102
|
+
ready[i + k] = group[k];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
i = j;
|
|
106
|
+
}
|
|
107
|
+
for (const op of ready) {
|
|
108
|
+
await op.run();
|
|
109
|
+
await Promise.resolve();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const chained = this._runChain.then(run);
|
|
114
|
+
this._runChain = chained.catch(() => {
|
|
115
|
+
});
|
|
116
|
+
return chained;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Drain **all** pending ops immediately, regardless of their `when`.
|
|
120
|
+
* Useful for test teardown.
|
|
121
|
+
*/
|
|
122
|
+
async drain() {
|
|
123
|
+
await this.runTick(Number.MAX_SAFE_INTEGER);
|
|
124
|
+
}
|
|
125
|
+
/** Number of operations currently held in the pending queue. */
|
|
126
|
+
get pendingCount() {
|
|
127
|
+
return this._pending.length;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
export {
|
|
131
|
+
Scheduler
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":"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","names":[]}
|
package/dist/prng.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mulberry32 — fast 32-bit seeded PRNG.
|
|
3
|
+
* Returns a function yielding numbers in [0, 1).
|
|
4
|
+
*
|
|
5
|
+
* This is a self-contained fallback so `@crashlab/scheduler` has zero
|
|
6
|
+
* hard dependencies. If `@crashlab/random` is available in the monorepo,
|
|
7
|
+
* consumers can pass their own SeededRandom instead.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mulberry32(seed: number): () => number;
|
|
10
|
+
/**
|
|
11
|
+
* Deterministic Fisher-Yates shuffle using a seeded PRNG.
|
|
12
|
+
* Mutates `arr` in place and returns it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function shuffleInPlace<T>(arr: T[], rng: () => number): T[];
|
|
15
|
+
//# sourceMappingURL=prng.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prng.d.ts","sourceRoot":"","sources":["../src/prng.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CAQrD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,MAAM,GAAG,CAAC,EAAE,CAMlE"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal clock interface (duck-typed — no hard dep on @crashlab/clock).
|
|
3
|
+
*
|
|
4
|
+
* When the clock advances to time `t`, it should call:
|
|
5
|
+
* await scheduler.runTick(t)
|
|
6
|
+
*/
|
|
7
|
+
export interface IClock {
|
|
8
|
+
now(): number;
|
|
9
|
+
}
|
|
10
|
+
/** An enqueued mock-I/O completion that the scheduler holds until its virtual time arrives. */
|
|
11
|
+
export interface PendingOp {
|
|
12
|
+
/** Unique operation identifier (for debugging / logging). */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Virtual timestamp (ms) when this operation completes. */
|
|
15
|
+
when: number;
|
|
16
|
+
/** Callback to execute when the scheduler releases this operation. */
|
|
17
|
+
run: () => Promise<void> | void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,IAAI,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACjC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crashlab/scheduler",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup && tsc --build tsconfig.json --force",
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"prepack": "npm run build",
|
|
23
|
+
"build:pkg": "tsup && tsc --build tsconfig.json --force"
|
|
24
|
+
}
|
|
25
|
+
}
|