@bytecodealliance/preview2-shim 0.14.2 → 0.15.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/lib/browser/sockets.js +0 -14
- package/lib/io/calls.js +72 -135
- package/lib/io/worker-http.js +21 -18
- package/lib/io/worker-io.js +186 -44
- package/lib/io/worker-socket-tcp.js +250 -96
- package/lib/io/worker-socket-udp.js +524 -167
- package/lib/io/worker-sockets.js +371 -0
- package/lib/io/worker-thread.js +677 -489
- package/lib/nodejs/cli.js +3 -3
- package/lib/nodejs/clocks.js +9 -6
- package/lib/nodejs/filesystem.js +87 -25
- package/lib/nodejs/http.js +106 -96
- package/lib/nodejs/index.js +0 -2
- package/lib/nodejs/sockets.js +563 -17
- package/lib/synckit/index.js +4 -3
- package/package.json +2 -2
- package/lib/common/assert.js +0 -7
- package/lib/nodejs/sockets/socket-common.js +0 -129
- package/lib/nodejs/sockets/socketopts-bindings.js +0 -94
- package/lib/nodejs/sockets/tcp-socket-impl.js +0 -885
- package/lib/nodejs/sockets/udp-socket-impl.js +0 -768
- package/lib/nodejs/sockets/wasi-sockets.js +0 -341
- package/lib/synckit/index.d.ts +0 -71
package/lib/io/worker-io.js
CHANGED
|
@@ -3,6 +3,9 @@ import { createSyncFn } from "../synckit/index.js";
|
|
|
3
3
|
import {
|
|
4
4
|
CALL_MASK,
|
|
5
5
|
CALL_TYPE_MASK,
|
|
6
|
+
FILE,
|
|
7
|
+
HTTP_SERVER_INCOMING_HANDLER,
|
|
8
|
+
HTTP,
|
|
6
9
|
INPUT_STREAM_BLOCKING_READ,
|
|
7
10
|
INPUT_STREAM_BLOCKING_SKIP,
|
|
8
11
|
INPUT_STREAM_DISPOSE,
|
|
@@ -22,50 +25,62 @@ import {
|
|
|
22
25
|
OUTPUT_STREAM_WRITE,
|
|
23
26
|
POLL_POLL_LIST,
|
|
24
27
|
POLL_POLLABLE_BLOCK,
|
|
28
|
+
POLL_POLLABLE_DISPOSE,
|
|
25
29
|
POLL_POLLABLE_READY,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
SOCKET_TCP,
|
|
31
|
+
STDERR,
|
|
32
|
+
STDIN,
|
|
33
|
+
STDOUT,
|
|
34
|
+
reverseMap,
|
|
29
35
|
} from "./calls.js";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
const DEBUG = false;
|
|
36
|
+
import { _rawDebug, exit, stderr, stdout, env } from "node:process";
|
|
33
37
|
|
|
34
38
|
const workerPath = fileURLToPath(
|
|
35
39
|
new URL("./worker-thread.js", import.meta.url)
|
|
36
40
|
);
|
|
37
41
|
|
|
38
42
|
const httpIncomingHandlers = new Map();
|
|
39
|
-
export function registerIncomingHttpHandler
|
|
43
|
+
export function registerIncomingHttpHandler(id, handler) {
|
|
40
44
|
httpIncomingHandlers.set(id, handler);
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
const instanceId = Math.round(Math.random() * 1000).toString();
|
|
48
|
+
const DEBUG_DEFAULT = false;
|
|
49
|
+
const DEBUG =
|
|
50
|
+
env.PREVIEW2_SHIM_DEBUG === "0"
|
|
51
|
+
? false
|
|
52
|
+
: env.PREVIEW2_SHIM_DEBUG === "1"
|
|
53
|
+
? true
|
|
54
|
+
: DEBUG_DEFAULT;
|
|
44
55
|
|
|
45
56
|
/**
|
|
46
57
|
* @type {(call: number, id: number | null, payload: any) -> any}
|
|
47
58
|
*/
|
|
48
|
-
export let ioCall = createSyncFn(workerPath, (type, id, payload) => {
|
|
59
|
+
export let ioCall = createSyncFn(workerPath, DEBUG, (type, id, payload) => {
|
|
49
60
|
// 'callbacks' from the worker
|
|
50
61
|
// ONLY happens for an http server incoming handler, and NOTHING else (not even sockets, since accept is sync!)
|
|
51
62
|
if (type !== HTTP_SERVER_INCOMING_HANDLER)
|
|
52
|
-
throw new Error(
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Internal error: only incoming handler callback is permitted"
|
|
65
|
+
);
|
|
53
66
|
const handler = httpIncomingHandlers.get(id);
|
|
54
67
|
if (!handler)
|
|
55
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Internal error: no incoming handler registered for server ${id}`
|
|
70
|
+
);
|
|
56
71
|
handler(payload);
|
|
57
72
|
});
|
|
58
73
|
if (DEBUG) {
|
|
59
74
|
const _ioCall = ioCall;
|
|
60
75
|
ioCall = function ioCall(num, id, payload) {
|
|
61
|
-
if (typeof id !==
|
|
62
|
-
throw new Error(
|
|
76
|
+
if (typeof id !== "number" && id !== null)
|
|
77
|
+
throw new Error("id must be a number or null");
|
|
63
78
|
let ret;
|
|
64
79
|
try {
|
|
65
|
-
|
|
80
|
+
_rawDebug(
|
|
66
81
|
instanceId,
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
reverseMap[num & CALL_MASK],
|
|
83
|
+
reverseMap[num & CALL_TYPE_MASK],
|
|
69
84
|
id,
|
|
70
85
|
payload
|
|
71
86
|
);
|
|
@@ -75,16 +90,53 @@ if (DEBUG) {
|
|
|
75
90
|
ret = e;
|
|
76
91
|
throw ret;
|
|
77
92
|
} finally {
|
|
78
|
-
|
|
93
|
+
_rawDebug(instanceId, "->", ret);
|
|
79
94
|
}
|
|
80
95
|
};
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
84
99
|
|
|
100
|
+
const finalizationRegistry = new FinalizationRegistry(
|
|
101
|
+
(dispose) => void dispose()
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const dummySymbol = Symbol();
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
*
|
|
108
|
+
* @param {any} resource
|
|
109
|
+
* @param {any} parentResource
|
|
110
|
+
* @param {number} id
|
|
111
|
+
* @param {(number) => void} disposeFn
|
|
112
|
+
*/
|
|
113
|
+
export function registerDispose(resource, parentResource, id, disposeFn) {
|
|
114
|
+
// While strictly speaking all components should handle their disposal,
|
|
115
|
+
// this acts as a last-resort to catch all missed drops through the JS GC.
|
|
116
|
+
// Mainly for two cases - (1) components which are long lived, that get shut
|
|
117
|
+
// down and (2) users that interface with low-level WASI APIs directly in JS
|
|
118
|
+
// for various reasons may end up leaning on JS GC inadvertantly.
|
|
119
|
+
function finalizer() {
|
|
120
|
+
// This has no functional purpose other than to pin a strong reference
|
|
121
|
+
// from the child resource's finalizer to the parent resource, to ensure
|
|
122
|
+
// that we can never finalize a parent resource before a child resource.
|
|
123
|
+
// This makes the generational JS GC become piecewise over child resource
|
|
124
|
+
// graphs (generational at each resource hierarchy level at least).
|
|
125
|
+
if (parentResource?.[dummySymbol]) return;
|
|
126
|
+
disposeFn(id);
|
|
127
|
+
}
|
|
128
|
+
finalizationRegistry.register(resource, finalizer, finalizer);
|
|
129
|
+
return finalizer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function earlyDispose(finalizer) {
|
|
133
|
+
finalizationRegistry.unregister(finalizer);
|
|
134
|
+
finalizer();
|
|
135
|
+
}
|
|
136
|
+
|
|
85
137
|
const _Error = Error;
|
|
86
138
|
const IoError = class Error extends _Error {
|
|
87
|
-
constructor
|
|
139
|
+
constructor(payload) {
|
|
88
140
|
super(payload);
|
|
89
141
|
this.payload = payload;
|
|
90
142
|
}
|
|
@@ -104,16 +156,14 @@ function streamIoErrorCall(call, id, payload) {
|
|
|
104
156
|
}
|
|
105
157
|
// any invalid error is a trap
|
|
106
158
|
console.trace(e);
|
|
107
|
-
|
|
159
|
+
exit(1);
|
|
108
160
|
}
|
|
109
161
|
}
|
|
110
162
|
|
|
111
163
|
class InputStream {
|
|
112
164
|
#id;
|
|
113
165
|
#streamType;
|
|
114
|
-
|
|
115
|
-
return this.#id;
|
|
116
|
-
}
|
|
166
|
+
#finalizer;
|
|
117
167
|
read(len) {
|
|
118
168
|
return streamIoErrorCall(
|
|
119
169
|
INPUT_STREAM_READ | this.#streamType,
|
|
@@ -144,24 +194,65 @@ class InputStream {
|
|
|
144
194
|
}
|
|
145
195
|
subscribe() {
|
|
146
196
|
return pollableCreate(
|
|
147
|
-
ioCall(INPUT_STREAM_SUBSCRIBE | this.#streamType, this.#id)
|
|
197
|
+
ioCall(INPUT_STREAM_SUBSCRIBE | this.#streamType, this.#id),
|
|
198
|
+
this
|
|
148
199
|
);
|
|
149
200
|
}
|
|
150
|
-
[symbolDispose]() {
|
|
151
|
-
ioCall(INPUT_STREAM_DISPOSE | this.#streamType, this.#id);
|
|
152
|
-
}
|
|
153
201
|
static _id(stream) {
|
|
154
202
|
return stream.#id;
|
|
155
203
|
}
|
|
156
204
|
/**
|
|
157
|
-
* @param {
|
|
205
|
+
* @param {FILE | SOCKET_TCP | STDIN | HTTP} streamType
|
|
158
206
|
*/
|
|
159
207
|
static _create(streamType, id) {
|
|
160
208
|
const stream = new InputStream();
|
|
161
209
|
stream.#id = id;
|
|
162
210
|
stream.#streamType = streamType;
|
|
211
|
+
let disposeFn;
|
|
212
|
+
switch (streamType) {
|
|
213
|
+
case FILE:
|
|
214
|
+
disposeFn = fileInputStreamDispose;
|
|
215
|
+
break;
|
|
216
|
+
case SOCKET_TCP:
|
|
217
|
+
disposeFn = socketTcpInputStreamDispose;
|
|
218
|
+
break;
|
|
219
|
+
case STDIN:
|
|
220
|
+
disposeFn = stdinInputStreamDispose;
|
|
221
|
+
break;
|
|
222
|
+
case HTTP:
|
|
223
|
+
disposeFn = httpInputStreamDispose;
|
|
224
|
+
break;
|
|
225
|
+
default:
|
|
226
|
+
throw new Error(
|
|
227
|
+
"wasi-io trap: Dispose function not created for stream type " +
|
|
228
|
+
reverseMap[streamType]
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
stream.#finalizer = registerDispose(stream, null, id, disposeFn);
|
|
163
232
|
return stream;
|
|
164
233
|
}
|
|
234
|
+
[symbolDispose]() {
|
|
235
|
+
if (this.#finalizer) {
|
|
236
|
+
earlyDispose(this.#finalizer);
|
|
237
|
+
this.#finalizer = null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function fileInputStreamDispose(id) {
|
|
243
|
+
ioCall(INPUT_STREAM_DISPOSE | FILE, id, null);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function socketTcpInputStreamDispose(id) {
|
|
247
|
+
ioCall(INPUT_STREAM_DISPOSE | SOCKET_TCP, id, null);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function stdinInputStreamDispose(id) {
|
|
251
|
+
ioCall(INPUT_STREAM_DISPOSE | STDIN, id, null);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function httpInputStreamDispose(id) {
|
|
255
|
+
ioCall(INPUT_STREAM_DISPOSE | HTTP, id, null);
|
|
165
256
|
}
|
|
166
257
|
|
|
167
258
|
export const inputStreamCreate = InputStream._create;
|
|
@@ -173,9 +264,7 @@ delete InputStream._id;
|
|
|
173
264
|
class OutputStream {
|
|
174
265
|
#id;
|
|
175
266
|
#streamType;
|
|
176
|
-
|
|
177
|
-
return this.#id;
|
|
178
|
-
}
|
|
267
|
+
#finalizer;
|
|
179
268
|
checkWrite(len) {
|
|
180
269
|
return streamIoErrorCall(
|
|
181
270
|
OUTPUT_STREAM_CHECK_WRITE | this.#streamType,
|
|
@@ -193,8 +282,7 @@ class OutputStream {
|
|
|
193
282
|
}
|
|
194
283
|
blockingWriteAndFlush(buf) {
|
|
195
284
|
if (this.#streamType <= STDERR) {
|
|
196
|
-
const stream =
|
|
197
|
-
this.#streamType === STDERR ? process.stderr : process.stdout;
|
|
285
|
+
const stream = this.#streamType === STDERR ? stderr : stdout;
|
|
198
286
|
return void stream.write(buf);
|
|
199
287
|
}
|
|
200
288
|
return streamIoErrorCall(
|
|
@@ -245,9 +333,6 @@ class OutputStream {
|
|
|
245
333
|
ioCall(OUTPUT_STREAM_SUBSCRIBE | this.#streamType, this.#id)
|
|
246
334
|
);
|
|
247
335
|
}
|
|
248
|
-
[symbolDispose]() {
|
|
249
|
-
ioCall(OUTPUT_STREAM_DISPOSE | this.#streamType, this.#id);
|
|
250
|
-
}
|
|
251
336
|
|
|
252
337
|
static _id(outputStream) {
|
|
253
338
|
return outputStream.#id;
|
|
@@ -260,8 +345,54 @@ class OutputStream {
|
|
|
260
345
|
const stream = new OutputStream();
|
|
261
346
|
stream.#id = id;
|
|
262
347
|
stream.#streamType = streamType;
|
|
348
|
+
let disposeFn;
|
|
349
|
+
switch (streamType) {
|
|
350
|
+
case STDOUT:
|
|
351
|
+
disposeFn = stdoutOutputStreamDispose;
|
|
352
|
+
break;
|
|
353
|
+
case STDERR:
|
|
354
|
+
disposeFn = stderrOutputStreamDispose;
|
|
355
|
+
break;
|
|
356
|
+
case SOCKET_TCP:
|
|
357
|
+
disposeFn = socketTcpOutputStreamDispose;
|
|
358
|
+
break;
|
|
359
|
+
case FILE:
|
|
360
|
+
disposeFn = fileOutputStreamDispose;
|
|
361
|
+
break;
|
|
362
|
+
case HTTP:
|
|
363
|
+
return stream;
|
|
364
|
+
default:
|
|
365
|
+
throw new Error(
|
|
366
|
+
"wasi-io trap: Dispose function not created for stream type " +
|
|
367
|
+
reverseMap[streamType]
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
stream.#finalizer = registerDispose(stream, null, id, disposeFn);
|
|
263
371
|
return stream;
|
|
264
372
|
}
|
|
373
|
+
|
|
374
|
+
[symbolDispose]() {
|
|
375
|
+
if (this.#finalizer) {
|
|
376
|
+
earlyDispose(this.#finalizer);
|
|
377
|
+
this.#finalizer = null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function stdoutOutputStreamDispose(id) {
|
|
383
|
+
ioCall(OUTPUT_STREAM_DISPOSE | STDOUT, id);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function stderrOutputStreamDispose(id) {
|
|
387
|
+
ioCall(OUTPUT_STREAM_DISPOSE | STDERR, id);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function socketTcpOutputStreamDispose(id) {
|
|
391
|
+
ioCall(OUTPUT_STREAM_DISPOSE | SOCKET_TCP, id);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function fileOutputStreamDispose(id) {
|
|
395
|
+
ioCall(OUTPUT_STREAM_DISPOSE | FILE, id);
|
|
265
396
|
}
|
|
266
397
|
|
|
267
398
|
export const outputStreamCreate = OutputStream._create;
|
|
@@ -274,27 +405,42 @@ export const error = { Error: IoError };
|
|
|
274
405
|
|
|
275
406
|
export const streams = { InputStream, OutputStream };
|
|
276
407
|
|
|
408
|
+
function pollableDispose(id) {
|
|
409
|
+
ioCall(POLL_POLLABLE_DISPOSE, id);
|
|
410
|
+
}
|
|
411
|
+
|
|
277
412
|
class Pollable {
|
|
278
413
|
#id;
|
|
279
|
-
|
|
280
|
-
return this.#id;
|
|
281
|
-
}
|
|
414
|
+
#finalizer;
|
|
282
415
|
ready() {
|
|
283
416
|
if (this.#id === 0) return true;
|
|
284
417
|
return ioCall(POLL_POLLABLE_READY, this.#id);
|
|
285
418
|
}
|
|
286
419
|
block() {
|
|
287
|
-
if (this.#id
|
|
288
|
-
|
|
420
|
+
if (this.#id !== 0) {
|
|
421
|
+
ioCall(POLL_POLLABLE_BLOCK, this.#id);
|
|
422
|
+
}
|
|
289
423
|
}
|
|
290
424
|
static _getId(pollable) {
|
|
291
425
|
return pollable.#id;
|
|
292
426
|
}
|
|
293
|
-
static _create(id) {
|
|
427
|
+
static _create(id, parent) {
|
|
294
428
|
const pollable = new Pollable();
|
|
295
429
|
pollable.#id = id;
|
|
430
|
+
pollable.#finalizer = registerDispose(
|
|
431
|
+
pollable,
|
|
432
|
+
parent,
|
|
433
|
+
id,
|
|
434
|
+
pollableDispose
|
|
435
|
+
);
|
|
296
436
|
return pollable;
|
|
297
437
|
}
|
|
438
|
+
[symbolDispose]() {
|
|
439
|
+
if (this.#finalizer) {
|
|
440
|
+
earlyDispose(this.#finalizer);
|
|
441
|
+
this.#finalizer = null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
298
444
|
}
|
|
299
445
|
|
|
300
446
|
export const pollableCreate = Pollable._create;
|
|
@@ -310,10 +456,6 @@ export const poll = {
|
|
|
310
456
|
},
|
|
311
457
|
};
|
|
312
458
|
|
|
313
|
-
export function resolvedPoll() {
|
|
314
|
-
return pollableCreate(0);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
459
|
export function createPoll(call, id, initPayload) {
|
|
318
460
|
return pollableCreate(ioCall(call, id, initPayload));
|
|
319
461
|
}
|