@bytecodealliance/preview2-shim 0.0.21 → 0.14.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 +4 -14
- package/lib/browser/cli.js +2 -4
- package/lib/browser/clocks.js +15 -27
- package/lib/browser/filesystem.js +2 -30
- package/lib/browser/http.js +1 -3
- package/lib/browser/io.js +4 -2
- package/lib/common/assert.js +7 -0
- package/lib/io/calls.js +64 -0
- package/lib/io/worker-http.js +95 -0
- package/lib/io/worker-io.js +322 -0
- package/lib/io/worker-thread.js +569 -0
- package/lib/nodejs/cli.js +45 -59
- package/lib/nodejs/clocks.js +13 -27
- package/lib/nodejs/filesystem.js +539 -459
- package/lib/nodejs/http.js +440 -173
- package/lib/nodejs/index.js +4 -1
- package/lib/nodejs/io.js +1 -0
- package/lib/nodejs/sockets/socket-common.js +116 -0
- package/lib/nodejs/sockets/socketopts-bindings.js +94 -0
- package/lib/nodejs/sockets/tcp-socket-impl.js +794 -0
- package/lib/nodejs/sockets/udp-socket-impl.js +628 -0
- package/lib/nodejs/sockets/wasi-sockets.js +320 -0
- package/lib/nodejs/sockets.js +11 -200
- package/lib/synckit/index.js +4 -2
- package/package.json +1 -5
- package/types/interfaces/wasi-cli-terminal-input.d.ts +4 -0
- package/types/interfaces/wasi-cli-terminal-output.d.ts +4 -0
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +19 -6
- package/types/interfaces/wasi-filesystem-types.d.ts +1 -178
- package/types/interfaces/wasi-http-outgoing-handler.d.ts +2 -2
- package/types/interfaces/wasi-http-types.d.ts +412 -82
- package/types/interfaces/wasi-io-error.d.ts +16 -0
- package/types/interfaces/wasi-io-poll.d.ts +19 -8
- package/types/interfaces/wasi-io-streams.d.ts +26 -46
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +9 -21
- package/types/interfaces/wasi-sockets-network.d.ts +4 -0
- package/types/interfaces/wasi-sockets-tcp.d.ts +75 -18
- package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +1 -1
- package/types/interfaces/wasi-sockets-udp.d.ts +282 -193
- package/types/wasi-cli-command.d.ts +28 -28
- package/types/wasi-http-proxy.d.ts +12 -12
- package/lib/common/io.js +0 -183
- package/lib/common/make-request.js +0 -30
- package/types/interfaces/wasi-clocks-timezone.d.ts +0 -56
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { resolve } from "node:dns/promises";
|
|
2
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
3
|
+
import { hrtime } from "node:process";
|
|
4
|
+
import { runAsWorker } from "../synckit/index.js";
|
|
5
|
+
import { createHttpRequest } from "./worker-http.js";
|
|
6
|
+
import { Writable } from "node:stream";
|
|
7
|
+
import {
|
|
8
|
+
CALL_MASK,
|
|
9
|
+
CALL_SHIFT,
|
|
10
|
+
CALL_TYPE_MASK,
|
|
11
|
+
CLOCKS_DURATION_SUBSCRIBE,
|
|
12
|
+
CLOCKS_INSTANT_SUBSCRIBE,
|
|
13
|
+
CLOCKS_NOW,
|
|
14
|
+
FUTURE_DISPOSE,
|
|
15
|
+
FUTURE_GET_VALUE_AND_DISPOSE,
|
|
16
|
+
HTTP_CREATE_REQUEST,
|
|
17
|
+
HTTP_OUTPUT_STREAM_FINISH,
|
|
18
|
+
INPUT_STREAM_BLOCKING_READ,
|
|
19
|
+
INPUT_STREAM_BLOCKING_SKIP,
|
|
20
|
+
INPUT_STREAM_CREATE,
|
|
21
|
+
INPUT_STREAM_DISPOSE,
|
|
22
|
+
INPUT_STREAM_READ,
|
|
23
|
+
INPUT_STREAM_SKIP,
|
|
24
|
+
INPUT_STREAM_SUBSCRIBE,
|
|
25
|
+
OUTPUT_STREAM_BLOCKING_FLUSH,
|
|
26
|
+
OUTPUT_STREAM_BLOCKING_SPLICE,
|
|
27
|
+
OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH,
|
|
28
|
+
OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH,
|
|
29
|
+
OUTPUT_STREAM_CHECK_WRITE,
|
|
30
|
+
OUTPUT_STREAM_CREATE,
|
|
31
|
+
OUTPUT_STREAM_DISPOSE,
|
|
32
|
+
OUTPUT_STREAM_FLUSH,
|
|
33
|
+
OUTPUT_STREAM_SPLICE,
|
|
34
|
+
OUTPUT_STREAM_SUBSCRIBE,
|
|
35
|
+
OUTPUT_STREAM_WRITE_ZEROES,
|
|
36
|
+
OUTPUT_STREAM_WRITE,
|
|
37
|
+
POLL_POLL_LIST,
|
|
38
|
+
POLL_POLLABLE_BLOCK,
|
|
39
|
+
POLL_POLLABLE_READY,
|
|
40
|
+
SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST,
|
|
41
|
+
SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST,
|
|
42
|
+
SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST,
|
|
43
|
+
FILE,
|
|
44
|
+
HTTP,
|
|
45
|
+
SOCKET,
|
|
46
|
+
STDERR,
|
|
47
|
+
STDIN,
|
|
48
|
+
STDOUT,
|
|
49
|
+
} from "./calls.js";
|
|
50
|
+
|
|
51
|
+
let streamCnt = 0,
|
|
52
|
+
pollCnt = 0;
|
|
53
|
+
|
|
54
|
+
/** @type {Map<number, Promise<void>>} */
|
|
55
|
+
export const unfinishedPolls = new Map();
|
|
56
|
+
|
|
57
|
+
/** @type {Map<number, { flushPromise: Promise<void> | null, stream: NodeJS.ReadableStream | NodeJS.WritableStream }>} */
|
|
58
|
+
export const unfinishedStreams = new Map();
|
|
59
|
+
|
|
60
|
+
/** @type {Map<number, { value: any, error: bool }>} */
|
|
61
|
+
export const unfinishedFutures = new Map();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
|
|
65
|
+
*/
|
|
66
|
+
export function createStream(nodeStream, flushPromise) {
|
|
67
|
+
unfinishedStreams.set(++streamCnt, {
|
|
68
|
+
flushPromise,
|
|
69
|
+
stream: nodeStream,
|
|
70
|
+
});
|
|
71
|
+
return streamCnt;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Stdio
|
|
75
|
+
createStream(process.stdin, Promise.resolve());
|
|
76
|
+
createStream(process.stdout, Promise.resolve());
|
|
77
|
+
createStream(process.stderr, Promise.resolve());
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {number} streamId
|
|
81
|
+
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
|
|
82
|
+
*/
|
|
83
|
+
function streamError(streamId, stream, err) {
|
|
84
|
+
if (stream.end) stream.end();
|
|
85
|
+
// we delete the stream from unfinishedStreams as it is now "finished" (closed)
|
|
86
|
+
unfinishedStreams.delete(streamId);
|
|
87
|
+
return { tag: "last-operation-failed", val: err };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {number} streamId
|
|
92
|
+
* @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream, flushPromise: Promise<void> | null }}
|
|
93
|
+
*/
|
|
94
|
+
export function getStreamOrThrow(streamId) {
|
|
95
|
+
const stream = unfinishedStreams.get(streamId);
|
|
96
|
+
// not in unfinished streams <=> closed
|
|
97
|
+
if (!stream) throw { tag: "closed" };
|
|
98
|
+
if (stream.stream.errored)
|
|
99
|
+
throw streamError(streamId, stream, stream.stream.errored);
|
|
100
|
+
if (stream.stream.closed) {
|
|
101
|
+
unfinishedStreams.delete(streamId);
|
|
102
|
+
throw { tag: "closed" };
|
|
103
|
+
}
|
|
104
|
+
return stream;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function subscribeInstant(instant) {
|
|
108
|
+
const duration = instant - hrtime.bigint();
|
|
109
|
+
if (duration <= 0) return Promise.resolve();
|
|
110
|
+
return new Promise((resolve) =>
|
|
111
|
+
duration < 10e6
|
|
112
|
+
? setImmediate(resolve)
|
|
113
|
+
: setTimeout(resolve, Number(duration) / 1e6)
|
|
114
|
+
).then(() => {
|
|
115
|
+
if (hrtime.bigint() < instant) return subscribeInstant(instant);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {number} call
|
|
121
|
+
* @param {number | null} id
|
|
122
|
+
* @param {any} payload
|
|
123
|
+
* @returns {Promise<any>}
|
|
124
|
+
*/
|
|
125
|
+
function handle(call, id, payload) {
|
|
126
|
+
switch (call) {
|
|
127
|
+
// Http
|
|
128
|
+
case HTTP_CREATE_REQUEST: {
|
|
129
|
+
const { method, url, headers, body } = payload;
|
|
130
|
+
return createFuture(createHttpRequest(method, url, headers, body));
|
|
131
|
+
}
|
|
132
|
+
case OUTPUT_STREAM_CREATE | HTTP: {
|
|
133
|
+
const webTransformStream = new TransformStream();
|
|
134
|
+
const stream = Writable.fromWeb(webTransformStream.writable);
|
|
135
|
+
// content length is passed as payload
|
|
136
|
+
stream.bytesRemaining = payload;
|
|
137
|
+
stream.readableBodyStream = webTransformStream.readable;
|
|
138
|
+
return createStream(stream);
|
|
139
|
+
}
|
|
140
|
+
case OUTPUT_STREAM_SUBSCRIBE | HTTP:
|
|
141
|
+
case OUTPUT_STREAM_FLUSH | HTTP:
|
|
142
|
+
case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP: {
|
|
143
|
+
// http flush is a noop
|
|
144
|
+
const { stream } = getStreamOrThrow(id);
|
|
145
|
+
// this existing indicates it's still unattached
|
|
146
|
+
// therefore there is no subscribe or backpressure
|
|
147
|
+
if (stream.readableBodyStream) {
|
|
148
|
+
switch (call) {
|
|
149
|
+
case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP:
|
|
150
|
+
return handle(OUTPUT_STREAM_WRITE | HTTP, id, payload);
|
|
151
|
+
case OUTPUT_STREAM_FLUSH | HTTP:
|
|
152
|
+
return;
|
|
153
|
+
case OUTPUT_STREAM_SUBSCRIBE | HTTP:
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (call === (OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP))
|
|
158
|
+
stream.bytesRemaining -= payload.byteLength;
|
|
159
|
+
// otherwise fall through to generic implementation
|
|
160
|
+
return handle(call & ~HTTP, id, payload);
|
|
161
|
+
}
|
|
162
|
+
case OUTPUT_STREAM_DISPOSE | HTTP:
|
|
163
|
+
throw new Error('Internal error: HTTP output stream dispose is bypassed for FINISH');
|
|
164
|
+
case OUTPUT_STREAM_WRITE | HTTP: {
|
|
165
|
+
const { stream } = getStreamOrThrow(id);
|
|
166
|
+
stream.bytesRemaining -= payload.byteLength;
|
|
167
|
+
if (stream.bytesRemaining < 0) {
|
|
168
|
+
throw {
|
|
169
|
+
tag: 'last-operation-failed',
|
|
170
|
+
val: 'too much written to output stream'
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const output = handle(OUTPUT_STREAM_WRITE, id, payload);
|
|
174
|
+
return output;
|
|
175
|
+
}
|
|
176
|
+
case HTTP_OUTPUT_STREAM_FINISH: {
|
|
177
|
+
const { stream } = getStreamOrThrow(id);
|
|
178
|
+
if (stream.bytesRemaining > 0) {
|
|
179
|
+
throw {
|
|
180
|
+
tag: 'internal-error',
|
|
181
|
+
val: 'not enough written to body stream'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (stream.bytesRemaining < 0) {
|
|
185
|
+
throw {
|
|
186
|
+
tag: 'internal-error',
|
|
187
|
+
val: 'too much written to body stream'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
stream.end();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Sockets
|
|
195
|
+
case SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST:
|
|
196
|
+
return createFuture(resolve(payload.hostname));
|
|
197
|
+
case SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST:
|
|
198
|
+
return void unfinishedFutures.delete(id);
|
|
199
|
+
case SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST: {
|
|
200
|
+
const future = unfinishedFutures.get(id);
|
|
201
|
+
if (!future) {
|
|
202
|
+
// future not ready yet
|
|
203
|
+
if (unfinishedPolls.get(id)) {
|
|
204
|
+
throw 'would-block';
|
|
205
|
+
}
|
|
206
|
+
throw new Error("future already got and dropped");
|
|
207
|
+
}
|
|
208
|
+
unfinishedFutures.delete(id);
|
|
209
|
+
return future;
|
|
210
|
+
}
|
|
211
|
+
case OUTPUT_STREAM_CREATE | SOCKET: {
|
|
212
|
+
// TODO: implement
|
|
213
|
+
}
|
|
214
|
+
case INPUT_STREAM_CREATE | SOCKET: {
|
|
215
|
+
// TODO: implement
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Stdio
|
|
219
|
+
case OUTPUT_STREAM_DISPOSE | STDOUT:
|
|
220
|
+
case OUTPUT_STREAM_DISPOSE | STDERR:
|
|
221
|
+
case INPUT_STREAM_DISPOSE | STDIN:
|
|
222
|
+
return;
|
|
223
|
+
|
|
224
|
+
// Clocks
|
|
225
|
+
case CLOCKS_NOW:
|
|
226
|
+
return hrtime.bigint();
|
|
227
|
+
case CLOCKS_DURATION_SUBSCRIBE:
|
|
228
|
+
return createPoll(subscribeInstant(hrtime.bigint() + payload));
|
|
229
|
+
case CLOCKS_INSTANT_SUBSCRIBE:
|
|
230
|
+
return createPoll(subscribeInstant(payload));
|
|
231
|
+
|
|
232
|
+
// Filesystem
|
|
233
|
+
case INPUT_STREAM_CREATE | FILE: {
|
|
234
|
+
const { fd, offset } = payload;
|
|
235
|
+
const stream = createReadStream(null, {
|
|
236
|
+
fd,
|
|
237
|
+
autoClose: false,
|
|
238
|
+
highWaterMark: 64 * 1024,
|
|
239
|
+
start: Number(offset),
|
|
240
|
+
});
|
|
241
|
+
// for some reason fs streams dont emit readable on end
|
|
242
|
+
stream.on("end", () => void stream.emit("readable"));
|
|
243
|
+
return createStream(stream);
|
|
244
|
+
}
|
|
245
|
+
case OUTPUT_STREAM_CREATE | FILE: {
|
|
246
|
+
const { fd, offset } = payload;
|
|
247
|
+
const stream = createWriteStream(null, {
|
|
248
|
+
fd,
|
|
249
|
+
autoClose: false,
|
|
250
|
+
emitClose: false,
|
|
251
|
+
highWaterMark: 64 * 1024,
|
|
252
|
+
start: Number(offset),
|
|
253
|
+
});
|
|
254
|
+
return createStream(stream);
|
|
255
|
+
}
|
|
256
|
+
// Generic call implementations (streams + polls)
|
|
257
|
+
default:
|
|
258
|
+
switch (call & CALL_MASK) {
|
|
259
|
+
case INPUT_STREAM_READ: {
|
|
260
|
+
const { stream } = getStreamOrThrow(id);
|
|
261
|
+
const res = stream.read(Number(payload));
|
|
262
|
+
return res ?? new Uint8Array();
|
|
263
|
+
}
|
|
264
|
+
case INPUT_STREAM_BLOCKING_READ:
|
|
265
|
+
return Promise.resolve(
|
|
266
|
+
unfinishedPolls.get(
|
|
267
|
+
handle(INPUT_STREAM_SUBSCRIBE | (call & CALL_TYPE_MASK), id)
|
|
268
|
+
)
|
|
269
|
+
).then(() =>
|
|
270
|
+
handle(INPUT_STREAM_READ | (call & CALL_TYPE_MASK), id, payload)
|
|
271
|
+
);
|
|
272
|
+
case INPUT_STREAM_SKIP:
|
|
273
|
+
return handle(
|
|
274
|
+
INPUT_STREAM_READ | (call & CALL_TYPE_MASK),
|
|
275
|
+
id,
|
|
276
|
+
new Uint8Array(Number(payload))
|
|
277
|
+
);
|
|
278
|
+
case INPUT_STREAM_BLOCKING_SKIP:
|
|
279
|
+
return handle(
|
|
280
|
+
INPUT_STREAM_BLOCKING_READ | (call & CALL_TYPE_MASK),
|
|
281
|
+
id,
|
|
282
|
+
new Uint8Array(Number(payload))
|
|
283
|
+
);
|
|
284
|
+
case INPUT_STREAM_SUBSCRIBE: {
|
|
285
|
+
const stream = unfinishedStreams.get(id)?.stream;
|
|
286
|
+
if (id === 1) {
|
|
287
|
+
// TODO: stdin subscribe
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
// already closed or errored -> immediately return poll
|
|
291
|
+
// (poll 0 is immediately resolved)
|
|
292
|
+
if (
|
|
293
|
+
!stream ||
|
|
294
|
+
stream.closed ||
|
|
295
|
+
stream.errored ||
|
|
296
|
+
stream.readableLength > 0
|
|
297
|
+
)
|
|
298
|
+
return 0;
|
|
299
|
+
let resolve, reject;
|
|
300
|
+
return createPoll(
|
|
301
|
+
new Promise((_resolve, _reject) => {
|
|
302
|
+
stream
|
|
303
|
+
.once("readable", (resolve = _resolve))
|
|
304
|
+
.once("error", (reject = _reject));
|
|
305
|
+
}).then(
|
|
306
|
+
() => void stream.off("error", reject),
|
|
307
|
+
(err) => {
|
|
308
|
+
stream.off("readable", resolve);
|
|
309
|
+
throw streamError(id, stream.stream, err);
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
case INPUT_STREAM_DISPOSE:
|
|
315
|
+
unfinishedStreams.delete(id);
|
|
316
|
+
return;
|
|
317
|
+
|
|
318
|
+
case OUTPUT_STREAM_CHECK_WRITE: {
|
|
319
|
+
const { stream } = getStreamOrThrow(id);
|
|
320
|
+
return BigInt(stream.writableHighWaterMark - stream.writableLength);
|
|
321
|
+
}
|
|
322
|
+
case OUTPUT_STREAM_WRITE: {
|
|
323
|
+
const { stream } = getStreamOrThrow(id);
|
|
324
|
+
if (
|
|
325
|
+
payload.byteLength >
|
|
326
|
+
stream.writableHighWaterMark - stream.writableLength
|
|
327
|
+
)
|
|
328
|
+
throw new Error("wasi-io error: attempt to write too many bytes");
|
|
329
|
+
return void stream.write(payload);
|
|
330
|
+
}
|
|
331
|
+
case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH: {
|
|
332
|
+
const { stream, flushPromise } = getStreamOrThrow(id);
|
|
333
|
+
// if an existing flush, try again after that
|
|
334
|
+
if (flushPromise)
|
|
335
|
+
return flushPromise.then(() => handle(call, id, payload));
|
|
336
|
+
if (
|
|
337
|
+
payload.byteLength >
|
|
338
|
+
stream.writableHighWaterMark - stream.writableLength
|
|
339
|
+
) {
|
|
340
|
+
throw streamError(
|
|
341
|
+
id,
|
|
342
|
+
stream,
|
|
343
|
+
new Error("Cannot write more than permitted writable length")
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return new Promise((resolve, reject) => {
|
|
347
|
+
stream.write(payload, (err) => {
|
|
348
|
+
if (err) return void reject(streamError(id, stream, err));
|
|
349
|
+
resolve(BigInt(payload.byteLength));
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
case OUTPUT_STREAM_FLUSH: {
|
|
354
|
+
const stream = getStreamOrThrow(id);
|
|
355
|
+
if (stream.flushPromise) return;
|
|
356
|
+
return (stream.flushPromise = new Promise((resolve, reject) => {
|
|
357
|
+
stream.stream.write(new Uint8Array([]), (err) =>
|
|
358
|
+
err ? reject(streamError(id, stream.stream, err)) : resolve()
|
|
359
|
+
);
|
|
360
|
+
}).then(
|
|
361
|
+
() => void (stream.stream.flushPromise = null),
|
|
362
|
+
(err) => {
|
|
363
|
+
stream.stream.flushPromise = null;
|
|
364
|
+
throw streamError(id, stream.stream, err);
|
|
365
|
+
}
|
|
366
|
+
));
|
|
367
|
+
}
|
|
368
|
+
case OUTPUT_STREAM_BLOCKING_FLUSH: {
|
|
369
|
+
const { stream, flushPromise } = getStreamOrThrow(id);
|
|
370
|
+
if (flushPromise) return flushPromise;
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
stream.write(new Uint8Array([]), (err) =>
|
|
373
|
+
err ? reject(streamError(id, stream, err)) : resolve()
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
case OUTPUT_STREAM_WRITE_ZEROES:
|
|
378
|
+
return handle(
|
|
379
|
+
OUTPUT_STREAM_WRITE | (call & CALL_TYPE_MASK),
|
|
380
|
+
id,
|
|
381
|
+
new Uint8Array(Number(payload))
|
|
382
|
+
);
|
|
383
|
+
case OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH:
|
|
384
|
+
return handle(
|
|
385
|
+
OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | (call & CALL_TYPE_MASK),
|
|
386
|
+
id,
|
|
387
|
+
new Uint8Array(Number(payload))
|
|
388
|
+
);
|
|
389
|
+
case OUTPUT_STREAM_SPLICE: {
|
|
390
|
+
const { stream: outputStream } = getStreamOrThrow(id);
|
|
391
|
+
const { stream: inputStream } = getStreamOrThrow(payload.src);
|
|
392
|
+
let bytesRemaining = Number(payload.len);
|
|
393
|
+
let chunk;
|
|
394
|
+
while (
|
|
395
|
+
bytesRemaining > 0 &&
|
|
396
|
+
(chunk = inputStream.read(
|
|
397
|
+
Math.min(
|
|
398
|
+
outputStream.writableHighWaterMark -
|
|
399
|
+
outputStream.writableLength,
|
|
400
|
+
bytesRemaining
|
|
401
|
+
)
|
|
402
|
+
))
|
|
403
|
+
) {
|
|
404
|
+
bytesRemaining -= chunk.byteLength;
|
|
405
|
+
outputStream.write(chunk);
|
|
406
|
+
}
|
|
407
|
+
// TODO: these error handlers should be attached, and only for the duration of the splice flush
|
|
408
|
+
if (inputStream.errored)
|
|
409
|
+
throw streamError(payload.src, inputStream, inputStream.errored);
|
|
410
|
+
if (outputStream.errored)
|
|
411
|
+
throw streamError(id, outputStream, outputStream.errored);
|
|
412
|
+
return payload.len - BigInt(bytesRemaining);
|
|
413
|
+
}
|
|
414
|
+
case OUTPUT_STREAM_SUBSCRIBE: {
|
|
415
|
+
const { stream, flushPromise } = unfinishedStreams.get(id) ?? {};
|
|
416
|
+
if (flushPromise) return flushPromise;
|
|
417
|
+
// not added to unfinishedPolls => it's an immediately resolved poll
|
|
418
|
+
if (
|
|
419
|
+
!stream ||
|
|
420
|
+
stream.closed ||
|
|
421
|
+
stream.errored ||
|
|
422
|
+
!stream.writableNeedDrain
|
|
423
|
+
)
|
|
424
|
+
return 0;
|
|
425
|
+
let resolve, reject;
|
|
426
|
+
return createPoll(
|
|
427
|
+
new Promise((_resolve, _reject) => {
|
|
428
|
+
stream
|
|
429
|
+
.once("drain", (resolve = _resolve))
|
|
430
|
+
.once("error", (reject = _reject));
|
|
431
|
+
}).then(() => void stream.off("error", reject)),
|
|
432
|
+
(err) => {
|
|
433
|
+
stream.off("drain", resolve);
|
|
434
|
+
throw streamError(id, stream, err);
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
case OUTPUT_STREAM_BLOCKING_SPLICE: {
|
|
439
|
+
const { stream: outputStream } = getStreamOrThrow(id);
|
|
440
|
+
let promise = Promise.resolve();
|
|
441
|
+
let resolve, reject;
|
|
442
|
+
if (outputStream.writableNeedDrain) {
|
|
443
|
+
promise = new Promise((_resolve, _reject) => {
|
|
444
|
+
outputStream
|
|
445
|
+
.once("drain", (resolve = _resolve))
|
|
446
|
+
.once("error", (reject = _reject));
|
|
447
|
+
}).then(
|
|
448
|
+
() => {
|
|
449
|
+
outputStream.off("error", reject);
|
|
450
|
+
},
|
|
451
|
+
(err) => {
|
|
452
|
+
outputStream.off("drain", resolve);
|
|
453
|
+
throw streamError(err);
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const { stream: inputStream } = getStreamOrThrow(payload.src);
|
|
458
|
+
if (!inputStream.readable) {
|
|
459
|
+
promise = promise.then(() =>
|
|
460
|
+
new Promise((_resolve, _reject) => {
|
|
461
|
+
inputStream
|
|
462
|
+
.once("readable", (resolve = _resolve))
|
|
463
|
+
.once("error", (reject = _reject));
|
|
464
|
+
}).then(
|
|
465
|
+
() => {
|
|
466
|
+
inputStream.off("error", reject);
|
|
467
|
+
},
|
|
468
|
+
(err) => {
|
|
469
|
+
inputStream.off("readable", resolve);
|
|
470
|
+
throw streamError(err);
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
return promise.then(() => handle(OUTPUT_STREAM_SPLICE, id, payload));
|
|
476
|
+
}
|
|
477
|
+
case OUTPUT_STREAM_DISPOSE: {
|
|
478
|
+
const stream = unfinishedStreams.get(id);
|
|
479
|
+
if (stream) {
|
|
480
|
+
stream.stream.end();
|
|
481
|
+
unfinishedStreams.delete(id);
|
|
482
|
+
}
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case POLL_POLLABLE_READY:
|
|
487
|
+
return !unfinishedPolls.has(id);
|
|
488
|
+
case POLL_POLLABLE_BLOCK:
|
|
489
|
+
payload = [id];
|
|
490
|
+
// [intentional case fall-through]
|
|
491
|
+
case POLL_POLL_LIST: {
|
|
492
|
+
const resolvedList = payload.filter((id) => !unfinishedPolls.has(id));
|
|
493
|
+
if (resolvedList.length > 0) return resolvedList;
|
|
494
|
+
// if all polls are promise type, we just race them
|
|
495
|
+
return Promise.race(
|
|
496
|
+
payload.map((id) => unfinishedPolls.get(id))
|
|
497
|
+
).then(() => {
|
|
498
|
+
const resolvedList = payload.filter(
|
|
499
|
+
(id) => !unfinishedPolls.has(id)
|
|
500
|
+
);
|
|
501
|
+
if (resolvedList.length === 0)
|
|
502
|
+
throw new Error("poll promise did not unregister poll");
|
|
503
|
+
return resolvedList;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
case FUTURE_GET_VALUE_AND_DISPOSE: {
|
|
508
|
+
const future = unfinishedFutures.get(id);
|
|
509
|
+
if (!future) {
|
|
510
|
+
// future not ready yet
|
|
511
|
+
if (unfinishedPolls.get(id)) {
|
|
512
|
+
// if ((call & CALL_TYPE_MASK) ===
|
|
513
|
+
// http futures throw
|
|
514
|
+
throw undefined;
|
|
515
|
+
}
|
|
516
|
+
throw new Error("future already got and dropped");
|
|
517
|
+
}
|
|
518
|
+
unfinishedFutures.delete(id);
|
|
519
|
+
return future;
|
|
520
|
+
}
|
|
521
|
+
case FUTURE_DISPOSE:
|
|
522
|
+
return void unfinishedFutures.delete(id);
|
|
523
|
+
|
|
524
|
+
default:
|
|
525
|
+
throw new Error(
|
|
526
|
+
`Unknown call ${(call & CALL_MASK) >> CALL_SHIFT} with type ${
|
|
527
|
+
call & CALL_TYPE_MASK
|
|
528
|
+
}`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// poll promises must always resolve and never error
|
|
535
|
+
export function createPoll(promise) {
|
|
536
|
+
const pollId = ++pollCnt;
|
|
537
|
+
unfinishedPolls.set(
|
|
538
|
+
pollId,
|
|
539
|
+
promise.then(
|
|
540
|
+
() => void unfinishedPolls.delete(pollId),
|
|
541
|
+
(err) => {
|
|
542
|
+
process._rawDebug("Unexpected poll error");
|
|
543
|
+
process._rawDebug(err);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
return pollId;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function createFuture(promise) {
|
|
552
|
+
const pollId = ++pollCnt;
|
|
553
|
+
unfinishedPolls.set(
|
|
554
|
+
pollId,
|
|
555
|
+
promise.then(
|
|
556
|
+
(value) => {
|
|
557
|
+
unfinishedPolls.delete(pollId);
|
|
558
|
+
unfinishedFutures.set(pollId, { value, error: false });
|
|
559
|
+
},
|
|
560
|
+
(value) => {
|
|
561
|
+
unfinishedPolls.delete(pollId);
|
|
562
|
+
unfinishedFutures.set(pollId, { value, error: true });
|
|
563
|
+
}
|
|
564
|
+
)
|
|
565
|
+
);
|
|
566
|
+
return pollId;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
runAsWorker(handle);
|