flatsql 3.11.1 → 3.12.1
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 +61 -795
- package/bin/flatsql.mjs +76 -0
- package/cpp/README.md +18 -0
- package/cpp/include/flatsql/flatsql.h +162 -0
- package/cpp/include/flatsql/flatsql_io.h +159 -0
- package/docs/API-3.12.md +126 -0
- package/package.json +28 -123
- package/wasm/flatsql.wasm +0 -0
- package/wasm/index.d.ts +61 -548
- package/wasm/index.js +280 -1281
- package/wasm/integrity.json +6 -26
- package/wasm/runtime/LICENSE.sdk.txt +190 -0
- package/wasm/runtime/NOTICE.txt +2 -0
- package/wasm/runtime/browser-guest.mjs +1 -0
- package/wasm/runtime/browser-host.mjs +1 -0
- package/wasm/runtime/node-guest.mjs +2 -0
- package/wasm/runtime/node-host.mjs +5 -0
- package/wasm/runtime/opfsIoWorker.mjs +1 -0
- package/wasm/runtime/wasiThreadBrowserWorker.mjs +1 -0
- package/dist/artifacts/index.d.ts +0 -3
- package/dist/artifacts/index.d.ts.map +0 -1
- package/dist/artifacts/index.js +0 -3
- package/dist/artifacts/index.js.map +0 -1
- package/dist/artifacts/transport.d.ts +0 -5
- package/dist/artifacts/transport.d.ts.map +0 -1
- package/dist/artifacts/transport.js +0 -45
- package/dist/artifacts/transport.js.map +0 -1
- package/dist/artifacts/types.d.ts +0 -34
- package/dist/artifacts/types.d.ts.map +0 -1
- package/dist/artifacts/types.js +0 -2
- package/dist/artifacts/types.js.map +0 -1
- package/dist/btree/btree.d.ts +0 -34
- package/dist/btree/btree.d.ts.map +0 -1
- package/dist/btree/btree.js +0 -234
- package/dist/btree/btree.js.map +0 -1
- package/dist/btree/index.d.ts +0 -3
- package/dist/btree/index.d.ts.map +0 -1
- package/dist/btree/index.js +0 -3
- package/dist/btree/index.js.map +0 -1
- package/dist/btree/types.d.ts +0 -36
- package/dist/btree/types.d.ts.map +0 -1
- package/dist/btree/types.js +0 -55
- package/dist/btree/types.js.map +0 -1
- package/dist/cluster/index.d.ts +0 -11
- package/dist/cluster/index.d.ts.map +0 -1
- package/dist/cluster/index.js +0 -59
- package/dist/cluster/index.js.map +0 -1
- package/dist/response/artifact.d.ts +0 -4
- package/dist/response/artifact.d.ts.map +0 -1
- package/dist/response/artifact.js +0 -102
- package/dist/response/artifact.js.map +0 -1
- package/dist/response/cache-key.d.ts +0 -6
- package/dist/response/cache-key.d.ts.map +0 -1
- package/dist/response/cache-key.js +0 -114
- package/dist/response/cache-key.js.map +0 -1
- package/dist/response/index.d.ts +0 -5
- package/dist/response/index.d.ts.map +0 -1
- package/dist/response/index.js +0 -5
- package/dist/response/index.js.map +0 -1
- package/dist/response/memory-cache.d.ts +0 -17
- package/dist/response/memory-cache.d.ts.map +0 -1
- package/dist/response/memory-cache.js +0 -76
- package/dist/response/memory-cache.js.map +0 -1
- package/dist/response/types.d.ts +0 -57
- package/dist/response/types.d.ts.map +0 -1
- package/dist/response/types.js +0 -2
- package/dist/response/types.js.map +0 -1
- package/dist/schema/index.d.ts +0 -3
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -3
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/parser.d.ts +0 -25
- package/dist/schema/parser.d.ts.map +0 -1
- package/dist/schema/parser.js +0 -474
- package/dist/schema/parser.js.map +0 -1
- package/dist/schema/types.d.ts +0 -33
- package/dist/schema/types.d.ts.map +0 -1
- package/dist/schema/types.js +0 -29
- package/dist/schema/types.js.map +0 -1
- package/dist/standalone/index.d.ts +0 -3
- package/dist/standalone/index.d.ts.map +0 -1
- package/dist/standalone/index.js +0 -3
- package/dist/standalone/index.js.map +0 -1
- package/dist/standalone/process-client.d.ts +0 -85
- package/dist/standalone/process-client.d.ts.map +0 -1
- package/dist/standalone/process-client.js +0 -537
- package/dist/standalone/process-client.js.map +0 -1
- package/dist/standalone/wasmedge-runner.d.ts +0 -19
- package/dist/standalone/wasmedge-runner.d.ts.map +0 -1
- package/dist/standalone/wasmedge-runner.js +0 -108
- package/dist/standalone/wasmedge-runner.js.map +0 -1
- package/dist/storage/index.d.ts +0 -3
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -3
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/stacked-flatbuffers.d.ts +0 -38
- package/dist/storage/stacked-flatbuffers.d.ts.map +0 -1
- package/dist/storage/stacked-flatbuffers.js +0 -253
- package/dist/storage/stacked-flatbuffers.js.map +0 -1
- package/dist/storage/types.d.ts +0 -23
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js +0 -27
- package/dist/storage/types.js.map +0 -1
- package/dist/stress/aggregate.d.ts +0 -3
- package/dist/stress/aggregate.d.ts.map +0 -1
- package/dist/stress/aggregate.js +0 -61
- package/dist/stress/aggregate.js.map +0 -1
- package/dist/stress/docker-compose.d.ts +0 -17
- package/dist/stress/docker-compose.d.ts.map +0 -1
- package/dist/stress/docker-compose.js +0 -156
- package/dist/stress/docker-compose.js.map +0 -1
- package/dist/stress/index.d.ts +0 -10
- package/dist/stress/index.d.ts.map +0 -1
- package/dist/stress/index.js +0 -10
- package/dist/stress/index.js.map +0 -1
- package/dist/stress/metrics.d.ts +0 -3
- package/dist/stress/metrics.d.ts.map +0 -1
- package/dist/stress/metrics.js +0 -83
- package/dist/stress/metrics.js.map +0 -1
- package/dist/stress/native-publish-event-workload.d.ts +0 -3
- package/dist/stress/native-publish-event-workload.d.ts.map +0 -1
- package/dist/stress/native-publish-event-workload.js +0 -465
- package/dist/stress/native-publish-event-workload.js.map +0 -1
- package/dist/stress/production-gate.d.ts +0 -4
- package/dist/stress/production-gate.d.ts.map +0 -1
- package/dist/stress/production-gate.js +0 -12
- package/dist/stress/production-gate.js.map +0 -1
- package/dist/stress/report.d.ts +0 -3
- package/dist/stress/report.d.ts.map +0 -1
- package/dist/stress/report.js +0 -174
- package/dist/stress/report.js.map +0 -1
- package/dist/stress/sds-discovery.d.ts +0 -4
- package/dist/stress/sds-discovery.d.ts.map +0 -1
- package/dist/stress/sds-discovery.js +0 -40
- package/dist/stress/sds-discovery.js.map +0 -1
- package/dist/stress/smoke-runner.d.ts +0 -3
- package/dist/stress/smoke-runner.d.ts.map +0 -1
- package/dist/stress/smoke-runner.js +0 -64
- package/dist/stress/smoke-runner.js.map +0 -1
- package/dist/stress/types.d.ts +0 -219
- package/dist/stress/types.d.ts.map +0 -1
- package/dist/stress/types.js +0 -2
- package/dist/stress/types.js.map +0 -1
- package/dist/stress/workload.d.ts +0 -6
- package/dist/stress/workload.d.ts.map +0 -1
- package/dist/stress/workload.js +0 -144
- package/dist/stress/workload.js.map +0 -1
- package/docs/PARTITION-STORE-WASM.md +0 -338
- package/docs/PARTITION-STORE.md +0 -2380
- package/docs/agents/response-artifacts.md +0 -65
- package/docs/response-artifacts.md +0 -86
- package/scripts/stress-docker.sh +0 -279
- package/sdm/flatsql-spatial.wasm +0 -0
- package/sdm/index.d.ts +0 -88
- package/sdm/index.js +0 -253
- package/sdm/manifest.json +0 -402
- package/stress/docker/Dockerfile +0 -11
- package/stress/docker/docker-compose.yml +0 -51
- package/stress/sds/aggregate.mjs +0 -97
- package/stress/sds/run.mjs +0 -105
- package/stress/sds/write-compose.mjs +0 -96
- package/wasm/flatsql-client.js +0 -280
- package/wasm/flatsql-io.d.ts +0 -97
- package/wasm/flatsql-io.js +0 -625
- package/wasm/flatsql-p4-threads.wasm +0 -0
- package/wasm/flatsql-ps-threads.wasm +0 -0
- package/wasm/flatsql-wasi-noeh.wasm +0 -0
- package/wasm/flatsql-wasi.wasm +0 -0
- package/wasm/flatsql.js +0 -2
- package/wasm/flatsql.worker.js +0 -209
- package/wasm/native/flatsql_wasmedge_runner.cpp +0 -858
- package/wasm/ps-node-fault.d.mts +0 -23
- package/wasm/ps-node-fault.mjs +0 -381
- package/wasm/ps-node-guest.mjs +0 -173
- package/wasm/ps-node-host.d.mts +0 -95
- package/wasm/ps-node-host.mjs +0 -476
- package/wasm/ps-node-imports.mjs +0 -847
- package/wasm/ps.d.ts +0 -21
- package/wasm/ps.js +0 -75
- package/wasm/wasi.d.ts +0 -11
- package/wasm/wasi.js +0 -51
package/wasm/ps-node-host.mjs
DELETED
|
@@ -1,476 +0,0 @@
|
|
|
1
|
-
// FlatSQL partition store: the Node wasi-threads host (design §5.6, T4;
|
|
2
|
-
// docs/PARTITION-STORE-WASM.md).
|
|
3
|
-
//
|
|
4
|
-
// Runs flatsql-ps-threads.wasm (a reactor) and the wasm test commands on
|
|
5
|
-
// Node 24+ with real threads:
|
|
6
|
-
// - the guest's own thread is a worker (ps-node-guest.mjs); this thread
|
|
7
|
-
// supervises and is never blocked by the guest;
|
|
8
|
-
// - wasi.thread-spawn claims a pre-started worker from a pool this thread
|
|
9
|
-
// keeps (grown on demand up to a cap); workers are reused, so a guest that
|
|
10
|
-
// starts and stops engines thousands of times never exhausts it, and any
|
|
11
|
-
// guest thread may spawn;
|
|
12
|
-
// - env.flatsql_io_* is space-data-module-sdk's Node sync-fs provider over a
|
|
13
|
-
// shared virtual-handle table; the fault overlay (ps-node-fault.mjs) is
|
|
14
|
-
// interposed on every syscall when `fault` is given;
|
|
15
|
-
// - WASI preview1 is this host's own (ps-node-imports.mjs): shared
|
|
16
|
-
// descriptors, process-wide clocks, preopens.
|
|
17
|
-
//
|
|
18
|
-
// Requires the optional peer dependency space-data-module-sdk >= 0.8.24.
|
|
19
|
-
//
|
|
20
|
-
// node wasm/ps-node-host.mjs <command.wasm> [args...]
|
|
21
|
-
// runs a wasm command in a private root (FLATSQL_PS_ROOT to choose it,
|
|
22
|
-
// FLATSQL_PS_KEEP_ROOT=1 to keep it), with the checkout mounted at its own
|
|
23
|
-
// path and the FLATSQL_PS_* / PS_* environment; FLATSQL_PS_TIMEOUT_MS
|
|
24
|
-
// bounds the run. Prints the thread report to stderr (and writes it as
|
|
25
|
-
// JSON to FLATSQL_PS_REPORT when set).
|
|
26
|
-
|
|
27
|
-
import fs from "node:fs";
|
|
28
|
-
import os from "node:os";
|
|
29
|
-
import path from "node:path";
|
|
30
|
-
import { fileURLToPath } from "node:url";
|
|
31
|
-
import { Worker } from "node:worker_threads";
|
|
32
|
-
|
|
33
|
-
import { createNodeSyncFsIoTable } from "space-data-module-sdk/host/flatsql-io/node";
|
|
34
|
-
|
|
35
|
-
import {
|
|
36
|
-
CONTROL_EXITED,
|
|
37
|
-
CONTROL_EXIT_CODE,
|
|
38
|
-
CONTROL_FAULT,
|
|
39
|
-
CONTROL_FAULT_TID,
|
|
40
|
-
CONTROL_HEADER,
|
|
41
|
-
CONTROL_SLOT,
|
|
42
|
-
CONTROL_STARTED,
|
|
43
|
-
CONTROL_TIDS,
|
|
44
|
-
POOL_CREATED,
|
|
45
|
-
POOL_DECLINED,
|
|
46
|
-
POOL_MAX_RUNNING,
|
|
47
|
-
POOL_RUNNING,
|
|
48
|
-
POOL_SPAWNED,
|
|
49
|
-
POOL_WANT,
|
|
50
|
-
createControlBlock,
|
|
51
|
-
createThreadPool,
|
|
52
|
-
createWasiFdTable,
|
|
53
|
-
} from "./ps-node-imports.mjs";
|
|
54
|
-
|
|
55
|
-
const GUEST_URL = new URL("./ps-node-guest.mjs", import.meta.url);
|
|
56
|
-
|
|
57
|
-
/** The shared memory limits a module imports (env.memory), in pages. */
|
|
58
|
-
export function importedMemoryLimits(bytes) {
|
|
59
|
-
const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
60
|
-
let at = 8;
|
|
61
|
-
const leb = () => {
|
|
62
|
-
let result = 0;
|
|
63
|
-
let shift = 0;
|
|
64
|
-
for (;;) {
|
|
65
|
-
const b = u8[at++];
|
|
66
|
-
result += (b & 0x7f) * 2 ** shift;
|
|
67
|
-
shift += 7;
|
|
68
|
-
if (!(b & 0x80)) return result;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const name = () => {
|
|
72
|
-
const n = leb();
|
|
73
|
-
const s = Buffer.from(u8.subarray(at, at + n)).toString("utf8");
|
|
74
|
-
at += n;
|
|
75
|
-
return s;
|
|
76
|
-
};
|
|
77
|
-
while (at < u8.length) {
|
|
78
|
-
const id = u8[at++];
|
|
79
|
-
const size = leb();
|
|
80
|
-
const end = at + size;
|
|
81
|
-
if (id === 2) {
|
|
82
|
-
const count = leb();
|
|
83
|
-
for (let i = 0; i < count; i += 1) {
|
|
84
|
-
const mod = name();
|
|
85
|
-
const field = name();
|
|
86
|
-
const kind = u8[at++];
|
|
87
|
-
if (kind === 0) leb();
|
|
88
|
-
else if (kind === 1) {
|
|
89
|
-
at += 1;
|
|
90
|
-
const flags = leb();
|
|
91
|
-
leb();
|
|
92
|
-
if (flags & 1) leb();
|
|
93
|
-
} else if (kind === 2) {
|
|
94
|
-
const flags = leb();
|
|
95
|
-
const initial = leb();
|
|
96
|
-
const maximum = flags & 1 ? leb() : undefined;
|
|
97
|
-
return { module: mod, name: field, initial, maximum, shared: (flags & 2) !== 0 };
|
|
98
|
-
} else if (kind === 3) {
|
|
99
|
-
at += 2;
|
|
100
|
-
} else {
|
|
101
|
-
throw new Error(`unknown import kind ${kind}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
at = end;
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// The guest's file system: `root` (a host directory) is the guest's "/" for
|
|
112
|
-
// WASI and the flatsql_io root; `mounts` preopen further host directories at
|
|
113
|
-
// guest paths. With no root a private temporary directory is created and
|
|
114
|
-
// removed afterwards.
|
|
115
|
-
function prepareRoot(options) {
|
|
116
|
-
if (options.root) {
|
|
117
|
-
fs.mkdirSync(options.root, { recursive: true });
|
|
118
|
-
return { root: path.resolve(options.root), cleanup: () => {} };
|
|
119
|
-
}
|
|
120
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "flatsql-ps-node-"));
|
|
121
|
-
return {
|
|
122
|
-
root,
|
|
123
|
-
cleanup: () => {
|
|
124
|
-
if (!options.keepRoot) fs.rmSync(root, { recursive: true, force: true });
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function hostConfig(options, root) {
|
|
130
|
-
fs.mkdirSync(path.join(root, "tmp"), { recursive: true });
|
|
131
|
-
const preopens = [{ guest: "/", host: root }, ...(options.mounts ?? [])];
|
|
132
|
-
let deterministic = null;
|
|
133
|
-
if (options.deterministic) {
|
|
134
|
-
const randomState = new SharedArrayBuffer(8);
|
|
135
|
-
new BigUint64Array(randomState)[0] = BigInt(options.deterministic.randomSeed ?? 0x9e3779b97f4a7c15n) | 1n;
|
|
136
|
-
deterministic = { randomSeed: String(options.deterministic.randomSeed ?? 0), randomState };
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
args: options.args ?? [],
|
|
140
|
-
env: { TMPDIR: "/tmp", ...(options.env ?? {}) },
|
|
141
|
-
fdTable: createWasiFdTable({ preopens }),
|
|
142
|
-
control: createControlBlock(),
|
|
143
|
-
pool: createThreadPool(options.maxThreads ?? 512),
|
|
144
|
-
spawnWaitMs: options.spawnWaitMs ?? 60000,
|
|
145
|
-
ioRoot: root,
|
|
146
|
-
ioTable: createNodeSyncFsIoTable({ maxHandles: options.maxHandles ?? 16384 }),
|
|
147
|
-
instanceId: options.instanceId ?? 0,
|
|
148
|
-
fault: options.fault?.buffer ?? null,
|
|
149
|
-
stdoutFd: options.stdoutFd,
|
|
150
|
-
stderrFd: options.stderrFd,
|
|
151
|
-
deterministic,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function loadModule(source) {
|
|
156
|
-
if (source instanceof WebAssembly.Module) return { module: source, bytes: null };
|
|
157
|
-
const bytes = typeof source === "string" ? fs.readFileSync(source) : new Uint8Array(source);
|
|
158
|
-
return { module: await WebAssembly.compile(bytes), bytes };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function createMemory(bytes, options) {
|
|
162
|
-
const limits = importedMemoryLimits(bytes);
|
|
163
|
-
if (!limits || limits.module !== "env" || limits.name !== "memory" || !limits.shared) {
|
|
164
|
-
throw new Error("the module must import a shared env.memory");
|
|
165
|
-
}
|
|
166
|
-
const initial = Math.max(limits.initial, options.initialPages ?? 0);
|
|
167
|
-
const maximum = Math.min(limits.maximum ?? 65536, options.maximumPages ?? 65536);
|
|
168
|
-
const memory = new WebAssembly.Memory({ initial, maximum, shared: true });
|
|
169
|
-
MAXIMUM_BYTES.set(memory, maximum * 65536);
|
|
170
|
-
return memory;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// The byte size each memory may grow to (createMemory).
|
|
174
|
-
const MAXIMUM_BYTES = new WeakMap();
|
|
175
|
-
|
|
176
|
-
// V8 bounds-checks memory.fill/copy and atomics against each thread's cached
|
|
177
|
-
// view of a shared memory's size, and another thread's memory.grow updates
|
|
178
|
-
// that view only at this thread's next stack check. A guest thread that
|
|
179
|
-
// fills a block carved from memory another thread has just grown traps
|
|
180
|
-
// ("memory access out of bounds"). So the heap grows to the memory's maximum
|
|
181
|
-
// before any guest thread starts, and never grows after (a partial pre-grow
|
|
182
|
-
// only moves the failure). The heap stops 16 MiB short of 4 GiB: a segment
|
|
183
|
-
// ending at 2^32 would wrap the guest allocator's pointer arithmetic.
|
|
184
|
-
// WasmEdge (the SDN host) keeps one shared size and is not affected.
|
|
185
|
-
function heapLimitBytes(memory, options) {
|
|
186
|
-
const max = MAXIMUM_BYTES.get(memory) ?? memory.buffer.byteLength;
|
|
187
|
-
const cap = options.heapBytes ?? Infinity;
|
|
188
|
-
return Math.min(max, 2 ** 32 - 16 * 2 ** 20, memory.buffer.byteLength + cap);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* One guest process: its memory, its file system, its thread pool, and the
|
|
193
|
-
* workers that run it. The supervisor starts pool workers when a spawn asks
|
|
194
|
-
* for one and watches for a guest fault or exit.
|
|
195
|
-
*/
|
|
196
|
-
class Supervisor {
|
|
197
|
-
constructor(module, memory, cfg, options) {
|
|
198
|
-
this.module = module;
|
|
199
|
-
this.memory = memory;
|
|
200
|
-
this.cfg = cfg;
|
|
201
|
-
this.control = new Int32Array(cfg.control);
|
|
202
|
-
this.pool = new Int32Array(cfg.pool);
|
|
203
|
-
this.cap = this.pool[0];
|
|
204
|
-
this.workers = new Set();
|
|
205
|
-
this.listeners = new Set();
|
|
206
|
-
this.stopped = false;
|
|
207
|
-
this.poolErrors = [];
|
|
208
|
-
// Memory size when the first guest thread was seen running (tick): any
|
|
209
|
-
// growth after it is the precondition of the V8 stale-view trap above.
|
|
210
|
-
this.memoryAtFirstThread = 0;
|
|
211
|
-
const warm = Math.min(options.warmThreads ?? 16, this.cap);
|
|
212
|
-
for (let i = 0; i < warm; i += 1) this.startPoolWorker();
|
|
213
|
-
this.timer = setInterval(() => this.tick(), 2);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
startPoolWorker() {
|
|
217
|
-
const slot = Atomics.add(this.pool, POOL_CREATED, 1);
|
|
218
|
-
if (slot >= this.cap) {
|
|
219
|
-
Atomics.sub(this.pool, POOL_CREATED, 1);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const worker = new Worker(GUEST_URL, {
|
|
223
|
-
workerData: { mode: "pool", slot, wasmModule: this.module, memory: this.memory, cfg: this.cfg },
|
|
224
|
-
});
|
|
225
|
-
worker.on("error", (error) => this.poolErrors.push(String(error?.stack ?? error)));
|
|
226
|
-
worker.once("exit", () => this.workers.delete(worker));
|
|
227
|
-
this.workers.add(worker);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
tick() {
|
|
231
|
-
if (this.stopped) return;
|
|
232
|
-
const want = Atomics.exchange(this.pool, POOL_WANT, 0);
|
|
233
|
-
for (let i = 0; i < want; i += 1) this.startPoolWorker();
|
|
234
|
-
if (!this.memoryAtFirstThread && Atomics.load(this.pool, POOL_SPAWNED) > 0) {
|
|
235
|
-
this.memoryAtFirstThread = this.memory.buffer.byteLength;
|
|
236
|
-
}
|
|
237
|
-
for (const listener of this.listeners) listener();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
startGuest(mode) {
|
|
241
|
-
const worker = new Worker(GUEST_URL, {
|
|
242
|
-
workerData: { mode, wasmModule: this.module, memory: this.memory, cfg: this.cfg },
|
|
243
|
-
});
|
|
244
|
-
this.workers.add(worker);
|
|
245
|
-
worker.once("exit", () => this.workers.delete(worker));
|
|
246
|
-
return worker;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
exited() {
|
|
250
|
-
return Atomics.load(this.control, CONTROL_EXITED) !== 0;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
faulted() {
|
|
254
|
-
return Atomics.load(this.control, CONTROL_FAULT) !== 0;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
report() {
|
|
258
|
-
const c = this.control;
|
|
259
|
-
const slots = Math.min(Atomics.load(c, CONTROL_SLOT), CONTROL_TIDS);
|
|
260
|
-
const tids = new Set();
|
|
261
|
-
for (let i = 0; i < slots; i += 1) tids.add(Atomics.load(c, CONTROL_HEADER + i));
|
|
262
|
-
return {
|
|
263
|
-
guestThreadsSpawned: Atomics.load(this.pool, POOL_SPAWNED),
|
|
264
|
-
// Pool threads running at once, plus the guest's own thread.
|
|
265
|
-
maxConcurrentGuestThreads: Atomics.load(this.pool, POOL_MAX_RUNNING) + 1,
|
|
266
|
-
runningGuestThreads: Atomics.load(this.pool, POOL_RUNNING),
|
|
267
|
-
spawnsDeclined: Atomics.load(this.pool, POOL_DECLINED),
|
|
268
|
-
workerThreads: Atomics.load(c, CONTROL_STARTED),
|
|
269
|
-
distinctWorkerThreadIds: tids.size,
|
|
270
|
-
memoryBytes: this.memory.buffer.byteLength,
|
|
271
|
-
// Bytes the memory grew after the first guest thread ran (sampled every
|
|
272
|
-
// 2 ms). Under V8 that growth can trap a thread (heapLimitBytes).
|
|
273
|
-
memoryGrownWhileThreadsRan: this.memoryAtFirstThread
|
|
274
|
-
? Math.max(0, this.memory.buffer.byteLength - this.memoryAtFirstThread)
|
|
275
|
-
: 0,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async stop() {
|
|
280
|
-
this.stopped = true;
|
|
281
|
-
clearInterval(this.timer);
|
|
282
|
-
await Promise.all([...this.workers].map((w) => w.terminate().catch(() => {})));
|
|
283
|
-
this.workers.clear();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Run a wasi-threads command (the ps test commands) to completion.
|
|
289
|
-
*
|
|
290
|
-
* @param {string|Uint8Array} wasm path or bytes
|
|
291
|
-
* @param {object} [options]
|
|
292
|
-
* @param {string[]} [options.args] argv after argv[0]
|
|
293
|
-
* @param {Record<string,string>} [options.env] (TMPDIR defaults to "/tmp")
|
|
294
|
-
* @param {string} [options.root] host directory the guest sees as "/" (WASI
|
|
295
|
-
* and flatsql_io); default a private temporary directory, removed after
|
|
296
|
-
* @param {boolean} [options.keepRoot] keep that temporary directory
|
|
297
|
-
* @param {Array<{guest:string,host:string}>} [options.mounts] more preopens
|
|
298
|
-
* @param {number} [options.maxThreads] pool cap (default 512)
|
|
299
|
-
* @param {number} [options.warmThreads] workers started up front (default 16)
|
|
300
|
-
* @param {number} [options.timeoutMs] stop the guest after this long
|
|
301
|
-
* @param {number} [options.killAfterMs] kill the guest after this long (a
|
|
302
|
-
* crash, not a failure: the result has killed: true)
|
|
303
|
-
* @param {() => void} [options.beforeKill] runs just before a kill (the fault
|
|
304
|
-
* overlay freezes here)
|
|
305
|
-
* @param {object} [options.fault] a fault overlay (createFaultOverlay)
|
|
306
|
-
* @param {number} [options.stdoutFd] guest stdout goes to this fd (default 1)
|
|
307
|
-
* @param {{ randomSeed?: bigint }} [options.deterministic] seeded random_get
|
|
308
|
-
* @returns {Promise<{ exitCode: number, killed: boolean, timedOut: boolean,
|
|
309
|
-
* fault: boolean, error: string|null, elapsedMs: number, threads: object }>}
|
|
310
|
-
*/
|
|
311
|
-
export async function runPsCommand(wasm, options = {}) {
|
|
312
|
-
const { module, bytes } = await loadModule(wasm);
|
|
313
|
-
const memory = createMemory(bytes ?? options.bytes, options);
|
|
314
|
-
const argv0 = typeof wasm === "string" ? path.basename(wasm) : "command.wasm";
|
|
315
|
-
const fsRoot = prepareRoot(options);
|
|
316
|
-
const cfg = hostConfig({ ...options, args: [argv0, ...(options.args ?? [])] }, fsRoot.root);
|
|
317
|
-
const started = performance.now();
|
|
318
|
-
const sup = new Supervisor(module, memory, cfg, options);
|
|
319
|
-
const guest = sup.startGuest("command");
|
|
320
|
-
const result = await new Promise((resolve) => {
|
|
321
|
-
let settled = false;
|
|
322
|
-
const timers = [];
|
|
323
|
-
const settle = (value) => {
|
|
324
|
-
if (settled) return;
|
|
325
|
-
settled = true;
|
|
326
|
-
for (const t of timers) clearTimeout(t);
|
|
327
|
-
sup.listeners.clear();
|
|
328
|
-
resolve(value);
|
|
329
|
-
};
|
|
330
|
-
const base = { exitCode: 0, killed: false, timedOut: false, fault: false, error: null };
|
|
331
|
-
guest.on("message", (msg) => {
|
|
332
|
-
if (msg.t === "done") settle({ ...base, exitCode: msg.code, error: msg.error, fault: msg.error !== null });
|
|
333
|
-
});
|
|
334
|
-
guest.on("error", (error) => settle({ ...base, exitCode: 134, error: String(error?.stack ?? error), fault: true }));
|
|
335
|
-
guest.on("exit", (code) => settle({ ...base, exitCode: code || 134, error: `guest worker exited (${code})`, fault: true }));
|
|
336
|
-
// A pool thread that exits the process or traps: the guest's own thread
|
|
337
|
-
// may be blocked in a join forever, so decide from here.
|
|
338
|
-
sup.listeners.add(() => {
|
|
339
|
-
if (sup.faulted()) {
|
|
340
|
-
settle({
|
|
341
|
-
...base,
|
|
342
|
-
exitCode: 134,
|
|
343
|
-
error: `guest thread ${Atomics.load(sup.control, CONTROL_FAULT_TID)} trapped`,
|
|
344
|
-
fault: true,
|
|
345
|
-
});
|
|
346
|
-
} else if (sup.exited()) {
|
|
347
|
-
timers.push(setTimeout(() => settle({ ...base, exitCode: Atomics.load(sup.control, CONTROL_EXIT_CODE) }), 100));
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
if (options.timeoutMs) {
|
|
351
|
-
timers.push(setTimeout(() => settle({ ...base, exitCode: 124, timedOut: true, error: `timed out after ${options.timeoutMs} ms` }), options.timeoutMs));
|
|
352
|
-
}
|
|
353
|
-
if (options.killAfterMs !== undefined) {
|
|
354
|
-
timers.push(setTimeout(() => {
|
|
355
|
-
options.beforeKill?.();
|
|
356
|
-
settle({ ...base, exitCode: 137, killed: true });
|
|
357
|
-
}, options.killAfterMs));
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
const threads = sup.report();
|
|
361
|
-
await sup.stop();
|
|
362
|
-
fsRoot.cleanup();
|
|
363
|
-
return { ...result, elapsedMs: performance.now() - started, threads, root: fsRoot.root, poolErrors: sup.poolErrors };
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Instantiate flatsql-ps-threads.wasm as one instance (writer or reader) with
|
|
368
|
-
* an exec thread of its own. Returns an RPC handle for the C ABI:
|
|
369
|
-
* `call(fn, ...args)`, `write(bytes) -> ptr`, `read(ptr, len)`, `threads()`,
|
|
370
|
-
* `close()`.
|
|
371
|
-
*/
|
|
372
|
-
export async function createPsNodeInstance(wasm, options = {}) {
|
|
373
|
-
const { module, bytes } = await loadModule(wasm);
|
|
374
|
-
const memory = createMemory(bytes ?? options.bytes, options);
|
|
375
|
-
const fsRoot = prepareRoot(options);
|
|
376
|
-
const cfg = hostConfig(options, fsRoot.root);
|
|
377
|
-
const sup = new Supervisor(module, memory, cfg, options);
|
|
378
|
-
const worker = sup.startGuest("reactor");
|
|
379
|
-
const pending = new Map();
|
|
380
|
-
let nextId = 1;
|
|
381
|
-
let failure = null;
|
|
382
|
-
const fail = (error) => {
|
|
383
|
-
failure = error;
|
|
384
|
-
for (const p of pending.values()) p.reject(error);
|
|
385
|
-
pending.clear();
|
|
386
|
-
};
|
|
387
|
-
sup.listeners.add(() => {
|
|
388
|
-
if (!failure && sup.faulted()) fail(new Error(`guest thread ${Atomics.load(sup.control, CONTROL_FAULT_TID)} trapped`));
|
|
389
|
-
});
|
|
390
|
-
await new Promise((resolve, reject) => {
|
|
391
|
-
worker.on("message", (msg) => {
|
|
392
|
-
if (msg.t === "ready") resolve();
|
|
393
|
-
else if (msg.t === "reply") {
|
|
394
|
-
const p = pending.get(msg.id);
|
|
395
|
-
pending.delete(msg.id);
|
|
396
|
-
if (!p) return;
|
|
397
|
-
if (msg.error) p.reject(new Error(msg.error));
|
|
398
|
-
else p.resolve(msg.value);
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
worker.on("error", (error) => {
|
|
402
|
-
fail(error);
|
|
403
|
-
reject(error);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
// Before any guest thread: the heap to the memory's maximum (heapLimitBytes).
|
|
407
|
-
if (options.growHeap !== false) {
|
|
408
|
-
await new Promise((resolve, reject) => {
|
|
409
|
-
const id = nextId++;
|
|
410
|
-
pending.set(id, { resolve, reject });
|
|
411
|
-
worker.postMessage({ op: "growHeap", limitBytes: heapLimitBytes(memory, options), id });
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
const rpc = (msg) => {
|
|
415
|
-
if (failure) return Promise.reject(failure);
|
|
416
|
-
const id = nextId++;
|
|
417
|
-
return new Promise((resolve, reject) => {
|
|
418
|
-
pending.set(id, { resolve, reject });
|
|
419
|
-
worker.postMessage({ ...msg, id });
|
|
420
|
-
});
|
|
421
|
-
};
|
|
422
|
-
return {
|
|
423
|
-
memory,
|
|
424
|
-
root: fsRoot.root,
|
|
425
|
-
call: (fn, ...args) => rpc({ op: "call", fn, args }),
|
|
426
|
-
write: (data) => rpc({ op: "write", bytes: data instanceof Uint8Array ? data : new Uint8Array(data) }),
|
|
427
|
-
read: (ptr, len) => rpc({ op: "read", ptr, len }),
|
|
428
|
-
threads: () => sup.report(),
|
|
429
|
-
faulted: () => sup.faulted(),
|
|
430
|
-
async close() {
|
|
431
|
-
await sup.stop();
|
|
432
|
-
fsRoot.cleanup();
|
|
433
|
-
},
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// ---- CLI ---------------------------------------------------------------------
|
|
438
|
-
const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
439
|
-
if (invokedDirectly) {
|
|
440
|
-
const [wasmPath, ...args] = process.argv.slice(2);
|
|
441
|
-
if (!wasmPath) {
|
|
442
|
-
process.stderr.write("usage: node wasm/ps-node-host.mjs <command.wasm> [args...]\n");
|
|
443
|
-
process.exit(2);
|
|
444
|
-
}
|
|
445
|
-
const env = {};
|
|
446
|
-
for (const [k, v] of Object.entries(process.env)) if (k.startsWith("FLATSQL_PS_") || k.startsWith("PS_")) env[k] = v;
|
|
447
|
-
const timeoutMs = Number(process.env.FLATSQL_PS_TIMEOUT_MS ?? 0) || undefined;
|
|
448
|
-
// The checkout is visible at its own path (the test commands read their
|
|
449
|
-
// vectors from the build tree); everything else lives in a private root.
|
|
450
|
-
const repo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
451
|
-
const r = await runPsCommand(wasmPath, {
|
|
452
|
-
args,
|
|
453
|
-
env,
|
|
454
|
-
timeoutMs,
|
|
455
|
-
root: process.env.FLATSQL_PS_ROOT || undefined,
|
|
456
|
-
keepRoot: process.env.FLATSQL_PS_KEEP_ROOT === "1",
|
|
457
|
-
mounts: [{ guest: repo, host: repo }],
|
|
458
|
-
});
|
|
459
|
-
const t = r.threads;
|
|
460
|
-
if (process.env.FLATSQL_PS_REPORT) {
|
|
461
|
-
fs.writeFileSync(process.env.FLATSQL_PS_REPORT, `${JSON.stringify({ ...r, root: undefined })}\n`);
|
|
462
|
-
}
|
|
463
|
-
process.stderr.write(
|
|
464
|
-
`ps-node-host: exit ${r.exitCode}${r.error ? ` (${r.error})` : ""}; guest threads spawned ${t.guestThreadsSpawned}, ` +
|
|
465
|
-
`max concurrent ${t.maxConcurrentGuestThreads} on ${t.distinctWorkerThreadIds} distinct worker threads` +
|
|
466
|
-
`${t.spawnsDeclined ? `, ${t.spawnsDeclined} spawns declined` : ""}; memory ${(t.memoryBytes / 2 ** 20).toFixed(0)} MiB; ` +
|
|
467
|
-
`${(r.elapsedMs / 1000).toFixed(1)} s\n`,
|
|
468
|
-
);
|
|
469
|
-
if (t.memoryGrownWhileThreadsRan) {
|
|
470
|
-
process.stderr.write(
|
|
471
|
-
`ps-node-host: WARNING memory grew ${(t.memoryGrownWhileThreadsRan / 2 ** 20).toFixed(0)} MiB while guest ` +
|
|
472
|
-
`threads ran (V8 can trap a thread on memory another thread grew; grow the heap before threads start)\n`,
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
process.exit(r.exitCode);
|
|
476
|
-
}
|