tla-ts 0.0.2 → 0.0.3-dev.6979965
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 +63 -11
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +1 -1
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -2
- package/dist/run-cli.js +20 -2
- package/dist/transfer.cjs +12 -12
- package/dist/transfer.js +12 -12
- package/package.json +3 -1
- package/src/ast.ts +43 -3
- package/src/check.ts +43 -10
- package/src/cli-options.ts +58 -0
- package/src/errors.ts +37 -1
- package/src/evaluate.ts +3 -1
- package/src/fingerprints.ts +46 -11
- package/src/lexer.ts +6 -1
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parallel-worker.ts +99 -130
- package/src/parallel.ts +62 -32
- package/src/parser.ts +285 -52
- package/src/run-cli.ts +295 -27
- package/src/standard-levels.ts +3 -3
- package/src/transfer.ts +22 -1
- package/src/values.ts +53 -20
package/src/parallel-worker.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { createCanonicalizer } from "./canonical";
|
|
2
2
|
import { createSearch } from "./check";
|
|
3
3
|
import { createEnvironment } from "./environment";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
createFingerprintTable,
|
|
6
|
+
insertSharedFingerprint,
|
|
7
|
+
isSharedFingerprintsFull,
|
|
8
|
+
writeStateFingerprint,
|
|
9
|
+
} from "./fingerprints";
|
|
10
|
+
import type {
|
|
11
|
+
CoordinatorMessage,
|
|
12
|
+
LevelMessage,
|
|
13
|
+
Port,
|
|
14
|
+
StartMessage,
|
|
15
|
+
WorkerMessage,
|
|
16
|
+
} from "./parallel";
|
|
17
|
+
import { getSuccessors, type Successor } from "./successors";
|
|
7
18
|
import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
|
|
8
19
|
import { OUT_DEGREE_BUCKETS } from "./tlc-summary";
|
|
9
20
|
import { createStateDecoder, createStateEncoder, type EncodedStates } from "./transfer";
|
|
@@ -11,21 +22,21 @@ import { getRankedStringCount, setStringOrder, type Value } from "./values";
|
|
|
11
22
|
|
|
12
23
|
const WORKER_THREADS_MODULE = "node:worker_threads";
|
|
13
24
|
|
|
14
|
-
interface Pending {
|
|
15
|
-
states: Value[][];
|
|
16
|
-
origins: number[];
|
|
17
|
-
fingerprints: number[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
25
|
interface PeerMessage {
|
|
21
|
-
phase: "
|
|
26
|
+
phase: "balance";
|
|
22
27
|
level: number;
|
|
23
28
|
from: number;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
states: EncodedStates;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Expansion {
|
|
33
|
+
origin: number;
|
|
34
|
+
successors: Successor[] | undefined;
|
|
35
|
+
index: number;
|
|
36
|
+
generated: number;
|
|
37
|
+
newStates: Int32Array;
|
|
38
|
+
next: Value[][];
|
|
39
|
+
fingerprintStates: Value[][];
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
interface Transfer {
|
|
@@ -117,12 +128,13 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
117
128
|
const nextAction = search?.spec.next;
|
|
118
129
|
if (!search || nextAction === undefined)
|
|
119
130
|
return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
|
|
120
|
-
const count = start.count;
|
|
121
131
|
const self = start.index;
|
|
122
132
|
const canonicalize = createCanonicalizer();
|
|
123
133
|
const decode = createStateDecoder(environment.variables.length);
|
|
124
|
-
const
|
|
125
|
-
|
|
134
|
+
const shared = { fingerprints: start.fingerprints };
|
|
135
|
+
// Under symmetry, a successor the worker generated before has the representative it had then,
|
|
136
|
+
// so the worker skips the search for one.
|
|
137
|
+
const generatedStates = search.isSymmetric ? createFingerprintTable() : undefined;
|
|
126
138
|
const fingerprint = new Int32Array(2);
|
|
127
139
|
const inbox = createInbox();
|
|
128
140
|
const peers = start.ports.flatMap((port, index) => (port ? [{ port, index }] : []));
|
|
@@ -130,82 +142,44 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
130
142
|
peer.port.addEventListener("message", (event) => inbox.deliver(event.data as PeerMessage));
|
|
131
143
|
peer.port.start?.();
|
|
132
144
|
});
|
|
133
|
-
for (let index = 0; index < start.seedFingerprints.length; index += 2) {
|
|
134
|
-
table.insert(start.seedFingerprints[index], start.seedFingerprints[index + 1]);
|
|
135
|
-
}
|
|
136
145
|
const frontier = { states: decode(start.seed).map(canonicalize) };
|
|
137
146
|
const send = (index: number, message: PeerMessage, transfer: unknown[]) =>
|
|
138
147
|
start.ports[index]?.postMessage(message, transfer);
|
|
139
148
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const successors =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
// The worker pauses before a successor when the shared table needs to grow, and resumes there.
|
|
150
|
+
const expand = (expansion: Expansion) => {
|
|
151
|
+
const states = frontier.states;
|
|
152
|
+
for (; expansion.origin < states.length; expansion.origin++) {
|
|
153
|
+
const state = states[expansion.origin];
|
|
154
|
+
if (!expansion.successors) {
|
|
155
|
+
expansion.successors = getSuccessors(nextAction, state, environment, search.spec.scope);
|
|
156
|
+
expansion.generated += expansion.successors.length;
|
|
157
|
+
if (expansion.successors.length === 0 && start.config.checkDeadlock) return undefined;
|
|
158
|
+
}
|
|
159
|
+
const successors = expansion.successors;
|
|
160
|
+
for (; expansion.index < successors.length; expansion.index++) {
|
|
161
|
+
if (isSharedFingerprintsFull(shared.fingerprints)) return "paused";
|
|
162
|
+
const successor = successors[expansion.index];
|
|
154
163
|
if (search.findViolatedActionProperty(state, successor.state)) return undefined;
|
|
155
164
|
if (!search.satisfiesActionConstraints(state, successor.state)) continue;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (owner === self) {
|
|
160
|
-
if (!table.insert(fingerprint[0], fingerprint[1])) continue;
|
|
161
|
-
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
162
|
-
if (!search.satisfiesConstraints(successor.state, state)) {
|
|
163
|
-
table.remove(fingerprint[0], fingerprint[1]);
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
next.push(successor.state);
|
|
167
|
-
newStates[origin]++;
|
|
168
|
-
continue;
|
|
165
|
+
if (generatedStates) {
|
|
166
|
+
writeStateFingerprint(successor.state, fingerprint);
|
|
167
|
+
if (!generatedStates.insert(fingerprint[0], fingerprint[1])) continue;
|
|
169
168
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const answer = (fingerprints: Int32Array | number[]) => {
|
|
180
|
-
const bits = new Uint8Array(fingerprints.length / 2);
|
|
181
|
-
for (let index = 0; index < bits.length; index++) {
|
|
182
|
-
bits[index] = table.insert(fingerprints[index * 2], fingerprints[index * 2 + 1]) ? 1 : 0;
|
|
183
|
-
}
|
|
184
|
-
return bits;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const commit = (pending: Pending, bits: Uint8Array, next: Value[][], newStates: Int32Array) => {
|
|
188
|
-
const unmarks: number[] = [];
|
|
189
|
-
for (let index = 0; index < bits.length; index++) {
|
|
190
|
-
if (bits[index] === 0) continue;
|
|
191
|
-
const state = pending.states[index];
|
|
192
|
-
const parentState = frontier.states[pending.origins[index]];
|
|
193
|
-
if (search.findViolatedInvariant(state, parentState)) return undefined;
|
|
194
|
-
if (search.satisfiesConstraints(state, parentState)) {
|
|
195
|
-
next.push(state);
|
|
196
|
-
newStates[pending.origins[index]]++;
|
|
197
|
-
continue;
|
|
169
|
+
canonicalize(successor.state);
|
|
170
|
+
const fingerprintState = search.getFingerprintState(successor.state);
|
|
171
|
+
writeStateFingerprint(fingerprintState, fingerprint);
|
|
172
|
+
if (!insertSharedFingerprint(shared.fingerprints, fingerprint[0], fingerprint[1])) continue;
|
|
173
|
+
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
174
|
+
if (!search.satisfiesConstraints(successor.state, state)) continue;
|
|
175
|
+
expansion.next.push(successor.state);
|
|
176
|
+
expansion.fingerprintStates.push(fingerprintState);
|
|
177
|
+
expansion.newStates[expansion.origin]++;
|
|
198
178
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
return Int32Array.from(unmarks);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const removeAll = (fingerprints: Int32Array) => {
|
|
206
|
-
for (let index = 0; index < fingerprints.length; index += 2) {
|
|
207
|
-
table.remove(fingerprints[index], fingerprints[index + 1]);
|
|
179
|
+
expansion.successors = undefined;
|
|
180
|
+
expansion.index = 0;
|
|
208
181
|
}
|
|
182
|
+
return "done";
|
|
209
183
|
};
|
|
210
184
|
|
|
211
185
|
const balance = async (level: number, next: Value[][], sizes: number[]) => {
|
|
@@ -222,60 +196,50 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
222
196
|
send(index, { phase: "balance", level, from: self, states }, [states.words.buffer]);
|
|
223
197
|
});
|
|
224
198
|
const incoming = await inbox.receive("balance", level, peers.length);
|
|
225
|
-
incoming.forEach((message) =>
|
|
226
|
-
if (message.states) next.push(...decode(message.states).map(canonicalize));
|
|
227
|
-
});
|
|
199
|
+
incoming.forEach((message) => next.push(...decode(message.states).map(canonicalize)));
|
|
228
200
|
return true;
|
|
229
201
|
};
|
|
230
202
|
|
|
203
|
+
const waiters = { resume: () => {}, balance: (_sizes: number[]) => {} };
|
|
204
|
+
const report = (message: WorkerMessage) =>
|
|
205
|
+
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
206
|
+
|
|
231
207
|
const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const unmarks = commit(
|
|
247
|
-
expanded.pending[reply.from],
|
|
248
|
-
reply.bits ?? new Uint8Array(),
|
|
249
|
-
next,
|
|
250
|
-
expanded.newStates,
|
|
251
|
-
);
|
|
252
|
-
if (!unmarks) return undefined;
|
|
253
|
-
send(reply.from, { phase: "sizes", level, from: self, unmarks }, [unmarks.buffer]);
|
|
208
|
+
const expansion: Expansion = {
|
|
209
|
+
origin: 0,
|
|
210
|
+
successors: undefined,
|
|
211
|
+
index: 0,
|
|
212
|
+
generated: 0,
|
|
213
|
+
newStates: new Int32Array(frontier.states.length),
|
|
214
|
+
next: [],
|
|
215
|
+
fingerprintStates: [],
|
|
216
|
+
};
|
|
217
|
+
for (let status = expand(expansion); status !== "done"; status = expand(expansion)) {
|
|
218
|
+
if (status === undefined) return undefined;
|
|
219
|
+
const resumed = new Promise<void>((resolve) => (waiters.resume = resolve));
|
|
220
|
+
report({ type: "paused" });
|
|
221
|
+
await resumed;
|
|
254
222
|
}
|
|
223
|
+
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
224
|
+
const next = expansion.next;
|
|
255
225
|
const added = next.length;
|
|
256
226
|
const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
|
|
257
|
-
|
|
258
|
-
const fingerprints =
|
|
259
|
-
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
sizes[self] = added;
|
|
263
|
-
peers.forEach((peer) =>
|
|
264
|
-
send(peer.index, { phase: "sizes", level, from: self, size: added }, []),
|
|
265
|
-
);
|
|
266
|
-
const received = await inbox.receive("sizes", level, peers.length * 2);
|
|
267
|
-
received.forEach((message) => {
|
|
268
|
-
if (message.unmarks) removeAll(message.unmarks);
|
|
269
|
-
else sizes[message.from] = message.size ?? 0;
|
|
270
|
-
});
|
|
271
|
-
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
272
|
-
if (!(await balance(level, next, sizes))) return undefined;
|
|
227
|
+
expansion.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
|
|
228
|
+
const fingerprints = search.getStoredFingerprints(next, expansion.fingerprintStates);
|
|
229
|
+
const sizes = new Promise<number[]>((resolve) => (waiters.balance = resolve));
|
|
230
|
+
report({ type: "expanded", from: self, added });
|
|
231
|
+
if (!(await balance(level, next, await sizes))) return undefined;
|
|
273
232
|
frontier.states = next;
|
|
274
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
type: "level",
|
|
235
|
+
level,
|
|
236
|
+
generated: expansion.generated,
|
|
237
|
+
added,
|
|
238
|
+
outDegrees,
|
|
239
|
+
fingerprints,
|
|
240
|
+
};
|
|
275
241
|
};
|
|
276
242
|
|
|
277
|
-
const report = (message: WorkerMessage) =>
|
|
278
|
-
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
279
243
|
const step = (level: number) => {
|
|
280
244
|
runLevel(level).then(
|
|
281
245
|
(message) => report(message ?? { type: "abort" }),
|
|
@@ -283,8 +247,13 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
283
247
|
);
|
|
284
248
|
};
|
|
285
249
|
parent.addEventListener("message", (event) => {
|
|
286
|
-
const message = event.data as
|
|
250
|
+
const message = event.data as CoordinatorMessage;
|
|
287
251
|
if (message.type === "level") step(message.level);
|
|
252
|
+
if (message.type === "resize") {
|
|
253
|
+
shared.fingerprints = message.fingerprints;
|
|
254
|
+
waiters.resume();
|
|
255
|
+
}
|
|
256
|
+
if (message.type === "balance") waiters.balance(message.sizes);
|
|
288
257
|
});
|
|
289
258
|
report({ type: "ready" });
|
|
290
259
|
};
|
package/src/parallel.ts
CHANGED
|
@@ -11,7 +11,14 @@ import { parseConfig, type ModelConfig } from "./config";
|
|
|
11
11
|
import { createEnvironment } from "./environment";
|
|
12
12
|
import { TlaEvaluationError } from "./errors";
|
|
13
13
|
import { isImpure } from "./evaluate";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createSharedFingerprints,
|
|
16
|
+
growSharedFingerprints,
|
|
17
|
+
insertSharedFingerprint,
|
|
18
|
+
isSharedFingerprintsFull,
|
|
19
|
+
writeStateFingerprint,
|
|
20
|
+
type SharedFingerprints,
|
|
21
|
+
} from "./fingerprints";
|
|
15
22
|
import { getInitialStates } from "./successors";
|
|
16
23
|
import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
|
|
17
24
|
import { getFingerprintDistance, OUT_DEGREE_BUCKETS } from "./tlc-summary";
|
|
@@ -34,10 +41,9 @@ export interface StartMessage {
|
|
|
34
41
|
modules: [string, string | undefined][];
|
|
35
42
|
classes: [string, Uint8Array | undefined][];
|
|
36
43
|
index: number;
|
|
37
|
-
count: number;
|
|
38
44
|
ports: (Port | undefined)[];
|
|
39
45
|
seed: EncodedStates;
|
|
40
|
-
|
|
46
|
+
fingerprints: SharedFingerprints;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
export interface LevelMessage {
|
|
@@ -49,7 +55,17 @@ export interface LevelMessage {
|
|
|
49
55
|
fingerprints: BigInt64Array;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export type WorkerMessage =
|
|
58
|
+
export type WorkerMessage =
|
|
59
|
+
| { type: "ready" }
|
|
60
|
+
| { type: "abort" }
|
|
61
|
+
| { type: "paused" }
|
|
62
|
+
| { type: "expanded"; from: number; added: number }
|
|
63
|
+
| LevelMessage;
|
|
64
|
+
|
|
65
|
+
export type CoordinatorMessage =
|
|
66
|
+
| { type: "level"; level: number }
|
|
67
|
+
| { type: "resize"; fingerprints: SharedFingerprints }
|
|
68
|
+
| { type: "balance"; sizes: number[] };
|
|
53
69
|
|
|
54
70
|
interface WorkerHandle {
|
|
55
71
|
postMessage: (message: unknown, transfer?: unknown[]) => void;
|
|
@@ -90,6 +106,7 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
90
106
|
const count = options.workers ?? 1;
|
|
91
107
|
const isSequential =
|
|
92
108
|
count < 2 ||
|
|
109
|
+
typeof SharedArrayBuffer !== "function" ||
|
|
93
110
|
config.view !== undefined ||
|
|
94
111
|
config.postconditions.length > 0 ||
|
|
95
112
|
config.possible.length > 0;
|
|
@@ -131,12 +148,9 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
const canonicalize = createCanonicalizer();
|
|
134
|
-
const
|
|
151
|
+
const shared = { fingerprints: createSharedFingerprints() };
|
|
135
152
|
const fingerprint = new Int32Array(2);
|
|
136
|
-
const seeds = Array.from({ length: count },
|
|
137
|
-
encoder: createStateEncoder(),
|
|
138
|
-
fingerprints: [] as number[],
|
|
139
|
-
}));
|
|
153
|
+
const seeds = Array.from({ length: count }, createStateEncoder);
|
|
140
154
|
const statistics = {
|
|
141
155
|
generatedStates: 0,
|
|
142
156
|
distinctStates: 0,
|
|
@@ -169,17 +183,15 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
169
183
|
canonicalize(state);
|
|
170
184
|
statistics.generatedStates++;
|
|
171
185
|
writeStateFingerprint(search.getFingerprintState(state), fingerprint);
|
|
172
|
-
if (
|
|
186
|
+
if (isSharedFingerprintsFull(shared.fingerprints)) {
|
|
187
|
+
shared.fingerprints = growSharedFingerprints(shared.fingerprints);
|
|
188
|
+
}
|
|
189
|
+
if (!insertSharedFingerprint(shared.fingerprints, fingerprint[0], fingerprint[1])) continue;
|
|
173
190
|
if (search.findViolatedInvariant(state) || search.findFailedInitialPredicate(state)) {
|
|
174
191
|
return false;
|
|
175
192
|
}
|
|
176
|
-
if (!search.satisfiesConstraints(state))
|
|
177
|
-
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const seed = seeds[getFingerprintOwner(fingerprint[1], count)];
|
|
181
|
-
if (!seed.encoder.encode(state)) return false;
|
|
182
|
-
seed.fingerprints.push(fingerprint[0], fingerprint[1]);
|
|
193
|
+
if (!search.satisfiesConstraints(state)) continue;
|
|
194
|
+
if (!seeds[seeded.length % count].encode(state)) return false;
|
|
183
195
|
seeded.push(state);
|
|
184
196
|
statistics.distinctStates++;
|
|
185
197
|
}
|
|
@@ -200,18 +212,19 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
200
212
|
if (statistics.distinctStates === 0) return createSuccess();
|
|
201
213
|
|
|
202
214
|
const reports: WorkerMessage[] = [];
|
|
203
|
-
const pending = { resolve: (_reports: WorkerMessage[]) => {} };
|
|
215
|
+
const pending = { count, resolve: (_reports: WorkerMessage[]) => {} };
|
|
204
216
|
const onMessage = (message: WorkerMessage) => {
|
|
205
217
|
reports.push(message);
|
|
206
|
-
if (message.type === "abort" || reports.length === count) {
|
|
218
|
+
if (message.type === "abort" || reports.length === pending.count) {
|
|
207
219
|
pending.resolve(reports.splice(0));
|
|
208
220
|
}
|
|
209
221
|
};
|
|
210
|
-
const waitForReports = () =>
|
|
222
|
+
const waitForReports = (expected: number) =>
|
|
211
223
|
new Promise<WorkerMessage[]>((resolve) => {
|
|
224
|
+
pending.count = expected;
|
|
212
225
|
pending.resolve = resolve;
|
|
213
226
|
});
|
|
214
|
-
const ready = waitForReports();
|
|
227
|
+
const ready = waitForReports(count);
|
|
215
228
|
const workers = await Promise.all(Array.from({ length: count }, () => spawnWorker(onMessage)));
|
|
216
229
|
const terminate = () => workers.forEach((worker) => worker.terminate());
|
|
217
230
|
const channels = new Map<string, MessageChannel>();
|
|
@@ -225,8 +238,7 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
225
238
|
const ports = Array.from({ length: count }, (_, peer) =>
|
|
226
239
|
peer === index ? undefined : getPort(index, peer),
|
|
227
240
|
);
|
|
228
|
-
const seed = seeds[index].
|
|
229
|
-
const seedFingerprints = Int32Array.from(seeds[index].fingerprints);
|
|
241
|
+
const seed = seeds[index].finish();
|
|
230
242
|
const message: StartMessage = {
|
|
231
243
|
type: "start",
|
|
232
244
|
source: options.source,
|
|
@@ -234,26 +246,44 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
234
246
|
modules: [...modules],
|
|
235
247
|
classes: [...classes],
|
|
236
248
|
index,
|
|
237
|
-
count,
|
|
238
249
|
ports,
|
|
239
250
|
seed,
|
|
240
|
-
|
|
251
|
+
fingerprints: shared.fingerprints,
|
|
241
252
|
};
|
|
242
|
-
worker.postMessage(message, [
|
|
243
|
-
...ports.filter((port) => port !== undefined),
|
|
244
|
-
seed.words.buffer,
|
|
245
|
-
seedFingerprints.buffer,
|
|
246
|
-
]);
|
|
253
|
+
worker.postMessage(message, [...ports.filter((port) => port !== undefined), seed.words.buffer]);
|
|
247
254
|
});
|
|
248
255
|
const isAborted = (messages: WorkerMessage[]) =>
|
|
249
256
|
messages.some((message) => message.type === "abort");
|
|
257
|
+
const send = (message: CoordinatorMessage) =>
|
|
258
|
+
workers.forEach((worker) => worker.postMessage(message));
|
|
259
|
+
// Workers pause when the shared table needs to grow, and resume once it has.
|
|
260
|
+
const collectExpansions = async (messages: WorkerMessage[]): Promise<WorkerMessage[]> => {
|
|
261
|
+
const paused = messages.filter((message) => message.type === "paused").length;
|
|
262
|
+
if (paused === 0 || isAborted(messages)) return messages;
|
|
263
|
+
shared.fingerprints = growSharedFingerprints(shared.fingerprints);
|
|
264
|
+
const resumed = waitForReports(paused);
|
|
265
|
+
send({ type: "resize", fingerprints: shared.fingerprints });
|
|
266
|
+
const expanded = messages.filter((message) => message.type !== "paused");
|
|
267
|
+
return [...expanded, ...(await collectExpansions(await resumed))];
|
|
268
|
+
};
|
|
250
269
|
if (isAborted(await ready)) {
|
|
251
270
|
terminate();
|
|
252
271
|
return fallBack();
|
|
253
272
|
}
|
|
254
273
|
for (let level = 1; ; level++) {
|
|
255
|
-
const
|
|
256
|
-
|
|
274
|
+
const expanding = waitForReports(count);
|
|
275
|
+
send({ type: "level", level });
|
|
276
|
+
const expansions = await collectExpansions(await expanding);
|
|
277
|
+
if (isAborted(expansions)) {
|
|
278
|
+
terminate();
|
|
279
|
+
return fallBack();
|
|
280
|
+
}
|
|
281
|
+
const sizes = Array.from({ length: count }, () => 0);
|
|
282
|
+
expansions.forEach((message) => {
|
|
283
|
+
if (message.type === "expanded") sizes[message.from] = message.added;
|
|
284
|
+
});
|
|
285
|
+
const reported = waitForReports(count);
|
|
286
|
+
send({ type: "balance", sizes });
|
|
257
287
|
const messages = await reported;
|
|
258
288
|
if (isAborted(messages)) {
|
|
259
289
|
terminate();
|