@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-thread.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { resolve } from "node:dns/promises";
|
|
2
1
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
3
|
-
import {
|
|
2
|
+
import { hrtime, stderr, stdout } from "node:process";
|
|
4
3
|
import { PassThrough } from "node:stream";
|
|
4
|
+
import { format } from "node:util";
|
|
5
5
|
import { runAsWorker } from "../synckit/index.js";
|
|
6
6
|
import {
|
|
7
7
|
clearOutgoingResponse,
|
|
@@ -10,21 +10,22 @@ import {
|
|
|
10
10
|
startHttpServer,
|
|
11
11
|
stopHttpServer,
|
|
12
12
|
} from "./worker-http.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import { Readable } from "node:stream";
|
|
14
|
+
import { read } from "node:fs";
|
|
15
|
+
import { nextTick } from "node:process";
|
|
16
16
|
import {
|
|
17
17
|
CALL_MASK,
|
|
18
|
-
CALL_SHIFT,
|
|
19
18
|
CALL_TYPE_MASK,
|
|
20
19
|
CLOCKS_DURATION_SUBSCRIBE,
|
|
21
20
|
CLOCKS_INSTANT_SUBSCRIBE,
|
|
22
21
|
CLOCKS_NOW,
|
|
23
22
|
FILE,
|
|
24
23
|
FUTURE_DISPOSE,
|
|
25
|
-
|
|
24
|
+
FUTURE_SUBSCRIBE,
|
|
25
|
+
FUTURE_TAKE_VALUE,
|
|
26
26
|
HTTP,
|
|
27
27
|
HTTP_CREATE_REQUEST,
|
|
28
|
+
HTTP_OUTGOING_BODY_DISPOSE,
|
|
28
29
|
HTTP_OUTPUT_STREAM_FINISH,
|
|
29
30
|
HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
|
|
30
31
|
HTTP_SERVER_SET_OUTGOING_RESPONSE,
|
|
@@ -50,133 +51,240 @@ import {
|
|
|
50
51
|
OUTPUT_STREAM_WRITE,
|
|
51
52
|
OUTPUT_STREAM_WRITE_ZEROES,
|
|
52
53
|
POLL_POLLABLE_BLOCK,
|
|
54
|
+
POLL_POLLABLE_DISPOSE,
|
|
53
55
|
POLL_POLLABLE_READY,
|
|
54
56
|
POLL_POLL_LIST,
|
|
55
57
|
SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST,
|
|
58
|
+
SOCKET_RESOLVE_ADDRESS_SUBSCRIBE_REQUEST,
|
|
56
59
|
SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
SOCKET_RESOLVE_ADDRESS_TAKE_REQUEST,
|
|
61
|
+
SOCKET_GET_DEFAULT_RECEIVE_BUFFER_SIZE,
|
|
62
|
+
SOCKET_GET_DEFAULT_SEND_BUFFER_SIZE,
|
|
63
|
+
SOCKET_TCP_ACCEPT,
|
|
64
|
+
SOCKET_TCP_BIND_FINISH,
|
|
65
|
+
SOCKET_TCP_BIND_START,
|
|
66
|
+
SOCKET_TCP_CONNECT_FINISH,
|
|
67
|
+
SOCKET_TCP_CONNECT_START,
|
|
60
68
|
SOCKET_TCP_CREATE_HANDLE,
|
|
61
|
-
SOCKET_TCP_CREATE_INPUT_STREAM,
|
|
62
|
-
SOCKET_TCP_CREATE_OUTPUT_STREAM,
|
|
63
69
|
SOCKET_TCP_DISPOSE,
|
|
64
70
|
SOCKET_TCP_GET_LOCAL_ADDRESS,
|
|
65
71
|
SOCKET_TCP_GET_REMOTE_ADDRESS,
|
|
66
|
-
|
|
72
|
+
SOCKET_TCP_IS_LISTENING,
|
|
73
|
+
SOCKET_TCP_LISTEN_FINISH,
|
|
74
|
+
SOCKET_TCP_LISTEN_START,
|
|
67
75
|
SOCKET_TCP_SET_KEEP_ALIVE,
|
|
76
|
+
SOCKET_TCP_SET_LISTEN_BACKLOG_SIZE,
|
|
68
77
|
SOCKET_TCP_SHUTDOWN,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
SOCKET_TCP_SUBSCRIBE,
|
|
79
|
+
SOCKET_UDP_BIND_FINISH,
|
|
80
|
+
SOCKET_UDP_BIND_START,
|
|
72
81
|
SOCKET_UDP_CREATE_HANDLE,
|
|
73
|
-
SOCKET_UDP_DISCONNECT,
|
|
74
82
|
SOCKET_UDP_DISPOSE,
|
|
75
83
|
SOCKET_UDP_GET_LOCAL_ADDRESS,
|
|
76
84
|
SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE,
|
|
77
85
|
SOCKET_UDP_GET_REMOTE_ADDRESS,
|
|
78
86
|
SOCKET_UDP_GET_SEND_BUFFER_SIZE,
|
|
79
|
-
|
|
80
|
-
SOCKET_UDP_SEND,
|
|
87
|
+
SOCKET_UDP_GET_UNICAST_HOP_LIMIT,
|
|
81
88
|
SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE,
|
|
82
89
|
SOCKET_UDP_SET_SEND_BUFFER_SIZE,
|
|
83
90
|
SOCKET_UDP_SET_UNICAST_HOP_LIMIT,
|
|
91
|
+
SOCKET_UDP_STREAM,
|
|
92
|
+
SOCKET_UDP_SUBSCRIBE,
|
|
93
|
+
SOCKET_INCOMING_DATAGRAM_STREAM_RECEIVE,
|
|
94
|
+
SOCKET_OUTGOING_DATAGRAM_STREAM_CHECK_SEND,
|
|
95
|
+
SOCKET_OUTGOING_DATAGRAM_STREAM_SEND,
|
|
96
|
+
SOCKET_DATAGRAM_STREAM_SUBSCRIBE,
|
|
97
|
+
SOCKET_DATAGRAM_STREAM_DISPOSE,
|
|
84
98
|
STDERR,
|
|
85
99
|
STDIN,
|
|
86
100
|
STDOUT,
|
|
101
|
+
reverseMap,
|
|
87
102
|
} from "./calls.js";
|
|
103
|
+
import {
|
|
104
|
+
SOCKET_STATE_BIND,
|
|
105
|
+
SOCKET_STATE_BOUND,
|
|
106
|
+
SOCKET_STATE_CONNECT,
|
|
107
|
+
SOCKET_STATE_CONNECTION,
|
|
108
|
+
SOCKET_STATE_LISTEN,
|
|
109
|
+
SOCKET_STATE_LISTENER,
|
|
110
|
+
socketResolveAddress,
|
|
111
|
+
getDefaultSendBufferSize,
|
|
112
|
+
getDefaultReceiveBufferSize,
|
|
113
|
+
} from "./worker-sockets.js";
|
|
88
114
|
import {
|
|
89
115
|
createTcpSocket,
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
socketTcpAccept,
|
|
117
|
+
socketTcpBindStart,
|
|
118
|
+
socketTcpConnectStart,
|
|
92
119
|
socketTcpDispose,
|
|
120
|
+
socketTcpFinish,
|
|
93
121
|
socketTcpGetLocalAddress,
|
|
94
122
|
socketTcpGetRemoteAddress,
|
|
95
|
-
|
|
123
|
+
socketTcpListenStart,
|
|
96
124
|
socketTcpSetKeepAlive,
|
|
125
|
+
socketTcpSetListenBacklogSize,
|
|
97
126
|
socketTcpShutdown,
|
|
127
|
+
tcpSockets,
|
|
98
128
|
} from "./worker-socket-tcp.js";
|
|
99
129
|
import {
|
|
100
|
-
SocketUdpReceive,
|
|
101
130
|
createUdpSocket,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
datagramStreams,
|
|
132
|
+
socketDatagramStreamDispose,
|
|
133
|
+
socketIncomingDatagramStreamReceive,
|
|
134
|
+
socketOutgoingDatagramStreamCheckSend,
|
|
135
|
+
socketOutgoingDatagramStreamSend,
|
|
136
|
+
socketUdpBindFinish,
|
|
137
|
+
socketUdpBindStart,
|
|
107
138
|
socketUdpDispose,
|
|
108
|
-
|
|
139
|
+
socketUdpGetLocalAddress,
|
|
140
|
+
socketUdpGetReceiveBufferSize,
|
|
141
|
+
socketUdpGetRemoteAddress,
|
|
142
|
+
socketUdpGetSendBufferSize,
|
|
143
|
+
socketUdpGetUnicastHopLimit,
|
|
144
|
+
socketUdpSetReceiveBufferSize,
|
|
145
|
+
socketUdpSetSendBufferSize,
|
|
146
|
+
socketUdpSetUnicastHopLimit,
|
|
147
|
+
socketUdpStream,
|
|
148
|
+
udpSockets,
|
|
109
149
|
} from "./worker-socket-udp.js";
|
|
110
150
|
|
|
111
|
-
|
|
112
|
-
|
|
151
|
+
function log(msg) {
|
|
152
|
+
if (debug) process._rawDebug(msg);
|
|
153
|
+
}
|
|
113
154
|
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
let pollCnt = 0,
|
|
156
|
+
streamCnt = 0,
|
|
157
|
+
futureCnt = 0;
|
|
116
158
|
|
|
117
|
-
/**
|
|
118
|
-
|
|
159
|
+
/**
|
|
160
|
+
* @typedef {{
|
|
161
|
+
* ready: bool,
|
|
162
|
+
* listener: () => void | null,
|
|
163
|
+
* polls: number[],
|
|
164
|
+
* parentStream: null | NodeJS.ReadableStream
|
|
165
|
+
* }} PollState
|
|
166
|
+
*
|
|
167
|
+
* @typedef {{
|
|
168
|
+
* stream: NodeJS.ReadableStream | NodeJS.WritableStream,
|
|
169
|
+
* flushPromise: Promise<void> | null,
|
|
170
|
+
* pollState
|
|
171
|
+
* }} Stream
|
|
172
|
+
*
|
|
173
|
+
* @typedef {{
|
|
174
|
+
* future: {
|
|
175
|
+
* tag: 'ok' | 'err',
|
|
176
|
+
* val: any,
|
|
177
|
+
* },
|
|
178
|
+
* pollState
|
|
179
|
+
* }} Future
|
|
180
|
+
*/
|
|
119
181
|
|
|
120
|
-
/** @type {Map<number,
|
|
121
|
-
export const
|
|
182
|
+
/** @type {Map<number, PollState>} */
|
|
183
|
+
export const polls = new Map();
|
|
184
|
+
|
|
185
|
+
/** @type {Map<number, Stream>} */
|
|
186
|
+
export const streams = new Map();
|
|
187
|
+
|
|
188
|
+
/** @type {Map<number, Future>} */
|
|
189
|
+
export const futures = new Map();
|
|
190
|
+
|
|
191
|
+
export function createReadableStreamPollState(nodeStream) {
|
|
192
|
+
const pollState = {
|
|
193
|
+
ready: true,
|
|
194
|
+
listener: null,
|
|
195
|
+
polls: [],
|
|
196
|
+
parentStream: nodeStream,
|
|
197
|
+
};
|
|
198
|
+
function pollDone() {
|
|
199
|
+
pollStateReady(pollState);
|
|
200
|
+
nodeStream.off("end", pollDone);
|
|
201
|
+
nodeStream.off("close", pollDone);
|
|
202
|
+
nodeStream.off("error", pollDone);
|
|
203
|
+
}
|
|
204
|
+
nodeStream.on("end", pollDone);
|
|
205
|
+
nodeStream.on("close", pollDone);
|
|
206
|
+
nodeStream.on("error", pollDone);
|
|
207
|
+
return pollState;
|
|
208
|
+
}
|
|
122
209
|
|
|
123
210
|
/**
|
|
124
211
|
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
|
|
125
212
|
*/
|
|
126
|
-
export function
|
|
127
|
-
|
|
213
|
+
export function createReadableStream(
|
|
214
|
+
nodeStream,
|
|
215
|
+
pollState = createReadableStreamPollState(nodeStream)
|
|
216
|
+
) {
|
|
217
|
+
const stream = {
|
|
218
|
+
stream: nodeStream,
|
|
128
219
|
flushPromise: null,
|
|
220
|
+
pollState,
|
|
221
|
+
};
|
|
222
|
+
streams.set(++streamCnt, stream);
|
|
223
|
+
return streamCnt;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function createWritableStream(nodeStream) {
|
|
227
|
+
const pollState = {
|
|
228
|
+
ready: true,
|
|
229
|
+
listener: null,
|
|
230
|
+
polls: [],
|
|
231
|
+
parentStream: null,
|
|
232
|
+
};
|
|
233
|
+
const stream = {
|
|
129
234
|
stream: nodeStream,
|
|
130
|
-
|
|
235
|
+
flushPromise: null,
|
|
236
|
+
pollState,
|
|
237
|
+
};
|
|
238
|
+
streams.set(++streamCnt, stream);
|
|
239
|
+
function pollReady() {
|
|
240
|
+
pollStateReady(pollState);
|
|
241
|
+
}
|
|
242
|
+
function pollDone() {
|
|
243
|
+
pollStateReady(pollState);
|
|
244
|
+
nodeStream.off("drain", pollReady);
|
|
245
|
+
nodeStream.off("finish", pollDone);
|
|
246
|
+
nodeStream.off("error", pollDone);
|
|
247
|
+
nodeStream.off("close", pollDone);
|
|
248
|
+
}
|
|
249
|
+
nodeStream.on("drain", pollReady);
|
|
250
|
+
nodeStream.on("finish", pollDone);
|
|
251
|
+
nodeStream.on("error", pollDone);
|
|
252
|
+
nodeStream.on("close", pollDone);
|
|
131
253
|
return streamCnt;
|
|
132
254
|
}
|
|
133
255
|
|
|
134
256
|
// Stdio
|
|
135
257
|
// Stdin created when used
|
|
136
|
-
|
|
137
|
-
|
|
258
|
+
createWritableStream(stdout);
|
|
259
|
+
createWritableStream(stderr);
|
|
138
260
|
|
|
139
261
|
/**
|
|
140
262
|
* @param {number} streamId
|
|
141
263
|
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
|
|
142
264
|
*/
|
|
143
|
-
function streamError(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
265
|
+
function streamError(err) {
|
|
266
|
+
return {
|
|
267
|
+
tag: "last-operation-failed",
|
|
268
|
+
val: { code: err.code, message: err.message, stack: err.stack },
|
|
269
|
+
};
|
|
148
270
|
}
|
|
149
271
|
|
|
150
272
|
/**
|
|
151
273
|
* @param {number} streamId
|
|
152
|
-
* @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream,
|
|
274
|
+
* @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream, polls: number[] }}
|
|
153
275
|
*/
|
|
154
276
|
export function getStreamOrThrow(streamId) {
|
|
155
|
-
if (!streamId) throw new Error("
|
|
156
|
-
const stream =
|
|
277
|
+
if (!streamId) throw new Error("wasi-io trap: no stream id provided");
|
|
278
|
+
const stream = streams.get(streamId);
|
|
157
279
|
// not in unfinished streams <=> closed
|
|
158
280
|
if (!stream) throw { tag: "closed" };
|
|
159
|
-
if (stream.stream.errored)
|
|
160
|
-
throw streamError(streamId, stream, stream.stream.errored);
|
|
281
|
+
if (stream.stream.errored) throw streamError(stream.stream.errored);
|
|
161
282
|
if (stream.stream.closed) {
|
|
162
|
-
unfinishedStreams.delete(streamId);
|
|
163
283
|
throw { tag: "closed" };
|
|
164
284
|
}
|
|
165
285
|
return stream;
|
|
166
286
|
}
|
|
167
287
|
|
|
168
|
-
function subscribeInstant(instant) {
|
|
169
|
-
const duration = instant - hrtime.bigint();
|
|
170
|
-
if (duration <= 0) return Promise.resolve();
|
|
171
|
-
return new Promise((resolve) =>
|
|
172
|
-
duration < 10e6
|
|
173
|
-
? setImmediate(resolve)
|
|
174
|
-
: setTimeout(resolve, Number(duration) / 1e6)
|
|
175
|
-
).then(() => {
|
|
176
|
-
if (hrtime.bigint() < instant) return subscribeInstant(instant);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
288
|
/**
|
|
181
289
|
* @param {number} call
|
|
182
290
|
* @param {number | null} id
|
|
@@ -184,6 +292,7 @@ function subscribeInstant(instant) {
|
|
|
184
292
|
* @returns {Promise<any>}
|
|
185
293
|
*/
|
|
186
294
|
function handle(call, id, payload) {
|
|
295
|
+
if (uncaughtException) throw uncaughtException;
|
|
187
296
|
switch (call) {
|
|
188
297
|
// Http
|
|
189
298
|
case HTTP_CREATE_REQUEST: {
|
|
@@ -217,7 +326,7 @@ function handle(call, id, payload) {
|
|
|
217
326
|
// content length is passed as payload
|
|
218
327
|
stream.contentLength = payload;
|
|
219
328
|
stream.bytesRemaining = payload;
|
|
220
|
-
return
|
|
329
|
+
return createWritableStream(stream);
|
|
221
330
|
}
|
|
222
331
|
case OUTPUT_STREAM_SUBSCRIBE | HTTP:
|
|
223
332
|
case OUTPUT_STREAM_FLUSH | HTTP:
|
|
@@ -239,10 +348,6 @@ function handle(call, id, payload) {
|
|
|
239
348
|
// otherwise fall through to generic implementation
|
|
240
349
|
return handle(call & ~HTTP, id, payload);
|
|
241
350
|
}
|
|
242
|
-
case OUTPUT_STREAM_DISPOSE | HTTP:
|
|
243
|
-
throw new Error(
|
|
244
|
-
"Internal error: HTTP output stream dispose is bypassed for FINISH"
|
|
245
|
-
);
|
|
246
351
|
case OUTPUT_STREAM_WRITE | HTTP: {
|
|
247
352
|
const { stream } = getStreamOrThrow(id);
|
|
248
353
|
stream.bytesRemaining -= payload.byteLength;
|
|
@@ -258,8 +363,20 @@ function handle(call, id, payload) {
|
|
|
258
363
|
const output = handle(OUTPUT_STREAM_WRITE, id, payload);
|
|
259
364
|
return output;
|
|
260
365
|
}
|
|
366
|
+
case OUTPUT_STREAM_DISPOSE | HTTP:
|
|
367
|
+
throw new Error(
|
|
368
|
+
"wasi-io trap: Output stream dispose not implemented as an IO-call for HTTP"
|
|
369
|
+
);
|
|
261
370
|
case HTTP_OUTPUT_STREAM_FINISH: {
|
|
262
|
-
|
|
371
|
+
let stream;
|
|
372
|
+
try {
|
|
373
|
+
({ stream } = getStreamOrThrow(id));
|
|
374
|
+
} catch (e) {
|
|
375
|
+
if (e.tag === "closed")
|
|
376
|
+
throw { tag: "internal-error", val: "stream closed" };
|
|
377
|
+
if (e.tag === "last-operation-failed")
|
|
378
|
+
throw { tag: "internal-error", val: e.val.message };
|
|
379
|
+
}
|
|
263
380
|
if (stream.bytesRemaining > 0) {
|
|
264
381
|
throw {
|
|
265
382
|
tag: "HTTP-request-body-size",
|
|
@@ -273,8 +390,12 @@ function handle(call, id, payload) {
|
|
|
273
390
|
};
|
|
274
391
|
}
|
|
275
392
|
stream.end();
|
|
276
|
-
|
|
393
|
+
return;
|
|
277
394
|
}
|
|
395
|
+
case HTTP_OUTGOING_BODY_DISPOSE:
|
|
396
|
+
if (!streams.delete(id))
|
|
397
|
+
throw new Error("wasi-io trap: stream not found to dispose");
|
|
398
|
+
return;
|
|
278
399
|
case HTTP_SERVER_START:
|
|
279
400
|
return startHttpServer(id, payload);
|
|
280
401
|
case HTTP_SERVER_STOP:
|
|
@@ -284,139 +405,99 @@ function handle(call, id, payload) {
|
|
|
284
405
|
case HTTP_SERVER_CLEAR_OUTGOING_RESPONSE:
|
|
285
406
|
return clearOutgoingResponse(id);
|
|
286
407
|
|
|
408
|
+
// Sockets name resolution
|
|
409
|
+
case SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST:
|
|
410
|
+
return createFuture(socketResolveAddress(payload));
|
|
411
|
+
case SOCKET_RESOLVE_ADDRESS_SUBSCRIBE_REQUEST:
|
|
412
|
+
return createPoll(futures.get(id).pollState);
|
|
413
|
+
case SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST:
|
|
414
|
+
return void futureDispose(id, true);
|
|
415
|
+
case SOCKET_RESOLVE_ADDRESS_TAKE_REQUEST: {
|
|
416
|
+
const val = futureTakeValue(id);
|
|
417
|
+
if (val === undefined) throw "would-block";
|
|
418
|
+
// double take avoidance is ensured
|
|
419
|
+
return val.val;
|
|
420
|
+
}
|
|
421
|
+
|
|
287
422
|
// Sockets TCP
|
|
423
|
+
case SOCKET_TCP_ACCEPT:
|
|
424
|
+
return socketTcpAccept(id);
|
|
288
425
|
case SOCKET_TCP_CREATE_HANDLE:
|
|
289
|
-
return
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
case
|
|
295
|
-
return
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
426
|
+
return createTcpSocket();
|
|
427
|
+
case SOCKET_TCP_BIND_START:
|
|
428
|
+
return socketTcpBindStart(id, payload.localAddress, payload.family);
|
|
429
|
+
case SOCKET_TCP_BIND_FINISH:
|
|
430
|
+
return socketTcpFinish(id, SOCKET_STATE_BIND, SOCKET_STATE_BOUND);
|
|
431
|
+
case SOCKET_TCP_CONNECT_START:
|
|
432
|
+
return socketTcpConnectStart(id, payload.remoteAddress, payload.family);
|
|
433
|
+
case SOCKET_TCP_CONNECT_FINISH:
|
|
434
|
+
return socketTcpFinish(id, SOCKET_STATE_CONNECT, SOCKET_STATE_CONNECTION);
|
|
435
|
+
case SOCKET_TCP_LISTEN_START:
|
|
436
|
+
return socketTcpListenStart(id);
|
|
437
|
+
case SOCKET_TCP_LISTEN_FINISH:
|
|
438
|
+
return socketTcpFinish(id, SOCKET_STATE_LISTEN, SOCKET_STATE_LISTENER);
|
|
439
|
+
case SOCKET_TCP_IS_LISTENING:
|
|
440
|
+
return tcpSockets.get(id).state === SOCKET_STATE_LISTENER;
|
|
441
|
+
case SOCKET_GET_DEFAULT_SEND_BUFFER_SIZE:
|
|
442
|
+
return getDefaultSendBufferSize(id);
|
|
443
|
+
case SOCKET_GET_DEFAULT_RECEIVE_BUFFER_SIZE:
|
|
444
|
+
return getDefaultReceiveBufferSize(id);
|
|
445
|
+
case SOCKET_TCP_SET_LISTEN_BACKLOG_SIZE:
|
|
446
|
+
return socketTcpSetListenBacklogSize(id);
|
|
300
447
|
case SOCKET_TCP_GET_LOCAL_ADDRESS:
|
|
301
448
|
return socketTcpGetLocalAddress(id);
|
|
302
|
-
|
|
303
449
|
case SOCKET_TCP_GET_REMOTE_ADDRESS:
|
|
304
450
|
return socketTcpGetRemoteAddress(id);
|
|
305
|
-
|
|
306
451
|
case SOCKET_TCP_SHUTDOWN:
|
|
307
452
|
return socketTcpShutdown(id, payload);
|
|
308
|
-
|
|
453
|
+
case SOCKET_TCP_SUBSCRIBE:
|
|
454
|
+
return createPoll(tcpSockets.get(id).pollState);
|
|
309
455
|
case SOCKET_TCP_SET_KEEP_ALIVE:
|
|
310
456
|
return socketTcpSetKeepAlive(id, payload);
|
|
311
|
-
|
|
312
457
|
case SOCKET_TCP_DISPOSE:
|
|
313
458
|
return socketTcpDispose(id);
|
|
314
459
|
|
|
315
|
-
case SOCKET_TCP_CREATE_INPUT_STREAM:
|
|
316
|
-
case SOCKET_TCP_CREATE_OUTPUT_STREAM:
|
|
317
|
-
return createStream(new PassThrough());
|
|
318
|
-
|
|
319
460
|
// Sockets UDP
|
|
320
|
-
case SOCKET_UDP_CREATE_HANDLE:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
case
|
|
329
|
-
return
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
case
|
|
335
|
-
return
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
case
|
|
341
|
-
return
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
case SOCKET_UDP_GET_REMOTE_ADDRESS: {
|
|
349
|
-
const socket = getSocketOrThrow(id);
|
|
350
|
-
return Promise.resolve(socket.remoteAddress());
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
case SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST:
|
|
354
|
-
return createFuture(resolve(payload.hostname));
|
|
355
|
-
|
|
356
|
-
case SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST:
|
|
357
|
-
return void unfinishedFutures.delete(id);
|
|
358
|
-
|
|
359
|
-
case SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST: {
|
|
360
|
-
const future = unfinishedFutures.get(id);
|
|
361
|
-
if (!future) {
|
|
362
|
-
// future not ready yet
|
|
363
|
-
if (unfinishedPolls.get(id)) {
|
|
364
|
-
throw "would-block";
|
|
365
|
-
}
|
|
366
|
-
throw new Error("future already got and dropped");
|
|
367
|
-
}
|
|
368
|
-
unfinishedFutures.delete(id);
|
|
369
|
-
return future;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
case SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE: {
|
|
373
|
-
const socket = getSocketOrThrow(id);
|
|
374
|
-
try {
|
|
375
|
-
return socket.getRecvBufferSize();
|
|
376
|
-
} catch (err) {
|
|
377
|
-
return err.errno;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
case SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE: {
|
|
382
|
-
const socket = getSocketOrThrow(id);
|
|
383
|
-
try {
|
|
384
|
-
return socket.setRecvBufferSize(65537);
|
|
385
|
-
} catch (err) {
|
|
386
|
-
return err.errno;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
case SOCKET_UDP_GET_SEND_BUFFER_SIZE: {
|
|
391
|
-
const socket = getSocketOrThrow(id);
|
|
392
|
-
try {
|
|
393
|
-
return socket.getSendBufferSize();
|
|
394
|
-
} catch (err) {
|
|
395
|
-
return err.errno;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
case SOCKET_UDP_SET_SEND_BUFFER_SIZE: {
|
|
400
|
-
const socket = getSocketOrThrow(id);
|
|
401
|
-
try {
|
|
402
|
-
return socket.setSendBufferSize(payload.value);
|
|
403
|
-
} catch (err) {
|
|
404
|
-
return err.errno;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
case SOCKET_UDP_SET_UNICAST_HOP_LIMIT: {
|
|
409
|
-
const socket = getSocketOrThrow(id);
|
|
410
|
-
try {
|
|
411
|
-
return socket.setTTL(payload.value);
|
|
412
|
-
} catch (err) {
|
|
413
|
-
return err.errno;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
461
|
+
case SOCKET_UDP_CREATE_HANDLE:
|
|
462
|
+
return createUdpSocket(payload);
|
|
463
|
+
case SOCKET_UDP_BIND_START:
|
|
464
|
+
return socketUdpBindStart(id, payload.localAddress, payload.family);
|
|
465
|
+
case SOCKET_UDP_BIND_FINISH:
|
|
466
|
+
return socketUdpBindFinish(id);
|
|
467
|
+
case SOCKET_UDP_STREAM:
|
|
468
|
+
return socketUdpStream(id, payload);
|
|
469
|
+
case SOCKET_UDP_SUBSCRIBE:
|
|
470
|
+
return createPoll(udpSockets.get(id).pollState);
|
|
471
|
+
case SOCKET_UDP_GET_LOCAL_ADDRESS:
|
|
472
|
+
return socketUdpGetLocalAddress(id);
|
|
473
|
+
case SOCKET_UDP_GET_REMOTE_ADDRESS:
|
|
474
|
+
return socketUdpGetRemoteAddress(id);
|
|
475
|
+
case SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE:
|
|
476
|
+
return socketUdpSetReceiveBufferSize(id, payload);
|
|
477
|
+
case SOCKET_UDP_SET_SEND_BUFFER_SIZE:
|
|
478
|
+
return socketUdpSetSendBufferSize(id, payload);
|
|
479
|
+
case SOCKET_UDP_SET_UNICAST_HOP_LIMIT:
|
|
480
|
+
return socketUdpSetUnicastHopLimit(id, payload);
|
|
481
|
+
case SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE:
|
|
482
|
+
return socketUdpGetReceiveBufferSize(id);
|
|
483
|
+
case SOCKET_UDP_GET_SEND_BUFFER_SIZE:
|
|
484
|
+
return socketUdpGetSendBufferSize(id);
|
|
485
|
+
case SOCKET_UDP_GET_UNICAST_HOP_LIMIT:
|
|
486
|
+
return socketUdpGetUnicastHopLimit(id);
|
|
417
487
|
case SOCKET_UDP_DISPOSE:
|
|
418
488
|
return socketUdpDispose(id);
|
|
419
489
|
|
|
490
|
+
case SOCKET_INCOMING_DATAGRAM_STREAM_RECEIVE:
|
|
491
|
+
return socketIncomingDatagramStreamReceive(id, payload);
|
|
492
|
+
case SOCKET_OUTGOING_DATAGRAM_STREAM_CHECK_SEND:
|
|
493
|
+
return socketOutgoingDatagramStreamCheckSend(id);
|
|
494
|
+
case SOCKET_OUTGOING_DATAGRAM_STREAM_SEND:
|
|
495
|
+
return socketOutgoingDatagramStreamSend(id, payload);
|
|
496
|
+
case SOCKET_DATAGRAM_STREAM_SUBSCRIBE:
|
|
497
|
+
return createPoll(datagramStreams.get(id).pollState);
|
|
498
|
+
case SOCKET_DATAGRAM_STREAM_DISPOSE:
|
|
499
|
+
return socketDatagramStreamDispose(id);
|
|
500
|
+
|
|
420
501
|
// Stdio
|
|
421
502
|
case OUTPUT_STREAM_BLOCKING_FLUSH | STDOUT:
|
|
422
503
|
case OUTPUT_STREAM_BLOCKING_FLUSH | STDERR:
|
|
@@ -426,22 +507,50 @@ function handle(call, id, payload) {
|
|
|
426
507
|
case OUTPUT_STREAM_DISPOSE | STDERR:
|
|
427
508
|
return;
|
|
428
509
|
case INPUT_STREAM_CREATE | STDIN: {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
510
|
+
return createReadableStream(
|
|
511
|
+
new Readable({
|
|
512
|
+
read(n) {
|
|
513
|
+
if (n <= 0) return void this.push(null);
|
|
514
|
+
let buf = Buffer.allocUnsafeSlow(n);
|
|
515
|
+
read(0, buf, 0, n, null, (err, bytesRead) => {
|
|
516
|
+
if (err) {
|
|
517
|
+
if (err.code === "EAGAIN") {
|
|
518
|
+
nextTick(() => void this._read(n));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
this.destroy(err);
|
|
522
|
+
} else if (bytesRead > 0) {
|
|
523
|
+
if (bytesRead !== buf.length) {
|
|
524
|
+
const dst = Buffer.allocUnsafeSlow(bytesRead);
|
|
525
|
+
buf.copy(dst, 0, 0, bytesRead);
|
|
526
|
+
buf = dst;
|
|
527
|
+
}
|
|
528
|
+
this.push(buf);
|
|
529
|
+
} else {
|
|
530
|
+
this.push(null);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
},
|
|
534
|
+
})
|
|
535
|
+
);
|
|
436
536
|
}
|
|
437
537
|
|
|
438
538
|
// Clocks
|
|
439
539
|
case CLOCKS_NOW:
|
|
440
540
|
return hrtime.bigint();
|
|
441
541
|
case CLOCKS_DURATION_SUBSCRIBE:
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
542
|
+
payload = hrtime.bigint() + payload;
|
|
543
|
+
// fallthrough
|
|
544
|
+
case CLOCKS_INSTANT_SUBSCRIBE: {
|
|
545
|
+
const pollState = {
|
|
546
|
+
ready: false,
|
|
547
|
+
listener: null,
|
|
548
|
+
polls: [],
|
|
549
|
+
parentStream: null,
|
|
550
|
+
};
|
|
551
|
+
subscribeInstant(pollState, payload);
|
|
552
|
+
return createPoll(pollState);
|
|
553
|
+
}
|
|
445
554
|
|
|
446
555
|
// Filesystem
|
|
447
556
|
case INPUT_STREAM_CREATE | FILE: {
|
|
@@ -452,9 +561,7 @@ function handle(call, id, payload) {
|
|
|
452
561
|
highWaterMark: 64 * 1024,
|
|
453
562
|
start: Number(offset),
|
|
454
563
|
});
|
|
455
|
-
|
|
456
|
-
stream.on("end", () => void stream.emit("readable"));
|
|
457
|
-
return createStream(stream);
|
|
564
|
+
return createReadableStream(stream);
|
|
458
565
|
}
|
|
459
566
|
case OUTPUT_STREAM_CREATE | FILE: {
|
|
460
567
|
const { fd, offset } = payload;
|
|
@@ -465,311 +572,392 @@ function handle(call, id, payload) {
|
|
|
465
572
|
highWaterMark: 64 * 1024,
|
|
466
573
|
start: Number(offset),
|
|
467
574
|
});
|
|
468
|
-
return
|
|
575
|
+
return createWritableStream(stream);
|
|
469
576
|
}
|
|
470
|
-
|
|
471
|
-
default:
|
|
472
|
-
switch (call & CALL_MASK) {
|
|
473
|
-
case INPUT_STREAM_READ: {
|
|
474
|
-
const { stream } = getStreamOrThrow(id);
|
|
475
|
-
const res = stream.read(
|
|
476
|
-
Math.min(stream.readableLength, Number(payload))
|
|
477
|
-
);
|
|
478
|
-
return res ?? new Uint8Array();
|
|
479
|
-
}
|
|
480
|
-
case INPUT_STREAM_BLOCKING_READ:
|
|
481
|
-
return Promise.resolve(
|
|
482
|
-
unfinishedPolls.get(
|
|
483
|
-
handle(INPUT_STREAM_SUBSCRIBE | (call & CALL_TYPE_MASK), id)
|
|
484
|
-
)
|
|
485
|
-
).then(() =>
|
|
486
|
-
handle(INPUT_STREAM_READ | (call & CALL_TYPE_MASK), id, payload)
|
|
487
|
-
);
|
|
488
|
-
case INPUT_STREAM_SKIP:
|
|
489
|
-
return handle(
|
|
490
|
-
INPUT_STREAM_READ | (call & CALL_TYPE_MASK),
|
|
491
|
-
id,
|
|
492
|
-
new Uint8Array(Number(payload))
|
|
493
|
-
);
|
|
494
|
-
case INPUT_STREAM_BLOCKING_SKIP:
|
|
495
|
-
return handle(
|
|
496
|
-
INPUT_STREAM_BLOCKING_READ | (call & CALL_TYPE_MASK),
|
|
497
|
-
id,
|
|
498
|
-
new Uint8Array(Number(payload))
|
|
499
|
-
);
|
|
500
|
-
case INPUT_STREAM_SUBSCRIBE: {
|
|
501
|
-
const stream = unfinishedStreams.get(id)?.stream;
|
|
502
|
-
// already closed or errored -> immediately return poll
|
|
503
|
-
// (poll 0 is immediately resolved)
|
|
504
|
-
if (
|
|
505
|
-
!stream ||
|
|
506
|
-
stream.closed ||
|
|
507
|
-
stream.errored ||
|
|
508
|
-
stream.readableLength > 0
|
|
509
|
-
)
|
|
510
|
-
return 0;
|
|
511
|
-
let resolve, reject;
|
|
512
|
-
return createPoll(
|
|
513
|
-
new Promise((_resolve, _reject) => {
|
|
514
|
-
stream
|
|
515
|
-
.once("readable", (resolve = _resolve))
|
|
516
|
-
.once("error", (reject = _reject));
|
|
517
|
-
}).then(
|
|
518
|
-
() => void stream.off("error", reject),
|
|
519
|
-
// error is read of stream itself when later accessed
|
|
520
|
-
(_err) => void stream.off("readable", resolve)
|
|
521
|
-
)
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
case INPUT_STREAM_DISPOSE:
|
|
525
|
-
unfinishedStreams.delete(id);
|
|
526
|
-
return;
|
|
577
|
+
}
|
|
527
578
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
579
|
+
// Generic call implementations (streams + polls)
|
|
580
|
+
switch (call & CALL_MASK) {
|
|
581
|
+
case INPUT_STREAM_READ: {
|
|
582
|
+
const stream = getStreamOrThrow(id);
|
|
583
|
+
if (!stream.pollState.ready) return new Uint8Array();
|
|
584
|
+
const res = stream.stream.read(
|
|
585
|
+
Math.min(stream.stream.readableLength, Number(payload))
|
|
586
|
+
);
|
|
587
|
+
if (res) return res;
|
|
588
|
+
if (stream.stream.readableEnded) return { tag: "closed" };
|
|
589
|
+
return new Uint8Array();
|
|
590
|
+
}
|
|
591
|
+
case INPUT_STREAM_BLOCKING_READ: {
|
|
592
|
+
const { pollState } = streams.get(id);
|
|
593
|
+
pollStateCheck(pollState);
|
|
594
|
+
if (pollState.ready)
|
|
595
|
+
return handle(INPUT_STREAM_READ | (call & CALL_TYPE_MASK), id, payload);
|
|
596
|
+
return new Promise((resolve) => void (pollState.listener = resolve)).then(
|
|
597
|
+
() => handle(INPUT_STREAM_READ | (call & CALL_TYPE_MASK), id, payload)
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
case INPUT_STREAM_SKIP:
|
|
601
|
+
return handle(
|
|
602
|
+
INPUT_STREAM_READ | (call & CALL_TYPE_MASK),
|
|
603
|
+
id,
|
|
604
|
+
new Uint8Array(Number(payload))
|
|
605
|
+
);
|
|
606
|
+
case INPUT_STREAM_BLOCKING_SKIP:
|
|
607
|
+
return handle(
|
|
608
|
+
INPUT_STREAM_BLOCKING_READ | (call & CALL_TYPE_MASK),
|
|
609
|
+
id,
|
|
610
|
+
new Uint8Array(Number(payload))
|
|
611
|
+
);
|
|
612
|
+
case INPUT_STREAM_SUBSCRIBE:
|
|
613
|
+
return createPoll(streams.get(id).pollState);
|
|
614
|
+
case INPUT_STREAM_DISPOSE: {
|
|
615
|
+
const stream = streams.get(id);
|
|
616
|
+
verifyPollsDroppedForDrop(stream.pollState, "input stream");
|
|
617
|
+
streams.delete(id);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
case OUTPUT_STREAM_CHECK_WRITE: {
|
|
621
|
+
const { stream, pollState } = getStreamOrThrow(id);
|
|
622
|
+
const bytes = stream.writableHighWaterMark - stream.writableLength;
|
|
623
|
+
if (bytes === 0) pollState.ready = false;
|
|
624
|
+
return BigInt(bytes);
|
|
625
|
+
}
|
|
626
|
+
case OUTPUT_STREAM_WRITE: {
|
|
627
|
+
const { stream } = getStreamOrThrow(id);
|
|
628
|
+
if (
|
|
629
|
+
payload.byteLength >
|
|
630
|
+
stream.writableHighWaterMark - stream.writableLength
|
|
631
|
+
)
|
|
632
|
+
throw new Error("wasi-io trap: attempt to write too many bytes");
|
|
633
|
+
return void stream.write(payload);
|
|
634
|
+
}
|
|
635
|
+
case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH: {
|
|
636
|
+
const stream = getStreamOrThrow(id);
|
|
637
|
+
// if an existing flush, try again after that
|
|
638
|
+
if (stream.flushPromise)
|
|
639
|
+
return stream.flushPromise.then(() => handle(call, id, payload));
|
|
640
|
+
if (
|
|
641
|
+
payload.byteLength >
|
|
642
|
+
stream.stream.writableHighWaterMark - stream.stream.writableLength
|
|
643
|
+
) {
|
|
644
|
+
new Error(
|
|
645
|
+
"wasi-io trap: Cannot write more than permitted writable length"
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
stream.pollState.ready = false;
|
|
649
|
+
return (stream.flushPromise = new Promise((resolve, reject) => {
|
|
650
|
+
stream.stream.write(payload, (err) => {
|
|
651
|
+
stream.flushPromise = null;
|
|
652
|
+
pollStateReady(stream.pollState);
|
|
653
|
+
if (err) return void reject(streamError(err));
|
|
654
|
+
resolve(BigInt(payload.byteLength));
|
|
655
|
+
});
|
|
656
|
+
}));
|
|
657
|
+
}
|
|
658
|
+
case OUTPUT_STREAM_FLUSH: {
|
|
659
|
+
const stream = getStreamOrThrow(id);
|
|
660
|
+
if (stream.flushPromise) return;
|
|
661
|
+
stream.pollState.ready = false;
|
|
662
|
+
return (stream.flushPromise = new Promise((resolve, reject) => {
|
|
663
|
+
stream.stream.write(new Uint8Array([]), (err) => {
|
|
664
|
+
stream.flushPromise = null;
|
|
665
|
+
pollStateReady(stream.pollState);
|
|
666
|
+
if (err) return void reject(streamError(err));
|
|
667
|
+
resolve();
|
|
668
|
+
});
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
671
|
+
case OUTPUT_STREAM_BLOCKING_FLUSH: {
|
|
672
|
+
const stream = getStreamOrThrow(id);
|
|
673
|
+
if (stream.flushPromise) return stream.flushPromise;
|
|
674
|
+
return new Promise((resolve, reject) => {
|
|
675
|
+
stream.stream.write(new Uint8Array([]), (err) =>
|
|
676
|
+
err ? reject(streamError(err)) : resolve()
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
case OUTPUT_STREAM_WRITE_ZEROES:
|
|
681
|
+
return handle(
|
|
682
|
+
OUTPUT_STREAM_WRITE | (call & CALL_TYPE_MASK),
|
|
683
|
+
id,
|
|
684
|
+
new Uint8Array(Number(payload))
|
|
685
|
+
);
|
|
686
|
+
case OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH:
|
|
687
|
+
return handle(
|
|
688
|
+
OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | (call & CALL_TYPE_MASK),
|
|
689
|
+
id,
|
|
690
|
+
new Uint8Array(Number(payload))
|
|
691
|
+
);
|
|
692
|
+
case OUTPUT_STREAM_SPLICE: {
|
|
693
|
+
const outputStream = getStreamOrThrow(id);
|
|
694
|
+
const inputStream = getStreamOrThrow(payload.src);
|
|
695
|
+
let bytesRemaining = Number(payload.len);
|
|
696
|
+
let chunk;
|
|
697
|
+
while (
|
|
698
|
+
bytesRemaining > 0 &&
|
|
699
|
+
(chunk = inputStream.stream.read(
|
|
700
|
+
Math.min(
|
|
701
|
+
outputStream.writableHighWaterMark - outputStream.writableLength,
|
|
702
|
+
bytesRemaining
|
|
537
703
|
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
704
|
+
))
|
|
705
|
+
) {
|
|
706
|
+
bytesRemaining -= chunk.byteLength;
|
|
707
|
+
outputStream.stream.write(chunk);
|
|
708
|
+
}
|
|
709
|
+
if (inputStream.stream.errored)
|
|
710
|
+
throw streamError(inputStream.stream.errored);
|
|
711
|
+
if (outputStream.stream.errored)
|
|
712
|
+
throw streamError(outputStream.stream.errored);
|
|
713
|
+
return payload.len - BigInt(bytesRemaining);
|
|
714
|
+
}
|
|
715
|
+
case OUTPUT_STREAM_SUBSCRIBE:
|
|
716
|
+
return createPoll(streams.get(id).pollState);
|
|
717
|
+
case OUTPUT_STREAM_BLOCKING_SPLICE: {
|
|
718
|
+
const outputStream = getStreamOrThrow(id);
|
|
719
|
+
let promise = Promise.resolve();
|
|
720
|
+
let resolve, reject;
|
|
721
|
+
if (outputStream.stream.writableNeedDrain) {
|
|
722
|
+
promise = new Promise((_resolve, _reject) => {
|
|
723
|
+
outputStream.stream
|
|
724
|
+
.once("drain", (resolve = _resolve))
|
|
725
|
+
.once("error", (reject = _reject));
|
|
726
|
+
}).then(
|
|
727
|
+
() => {
|
|
728
|
+
outputStream.stream.off("error", reject);
|
|
729
|
+
},
|
|
730
|
+
(err) => {
|
|
731
|
+
outputStream.stream.off("drain", resolve);
|
|
732
|
+
throw streamError(err);
|
|
555
733
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
case OUTPUT_STREAM_FLUSH: {
|
|
566
|
-
const stream = getStreamOrThrow(id);
|
|
567
|
-
if (stream.flushPromise) return;
|
|
568
|
-
return (stream.flushPromise = new Promise((resolve, reject) => {
|
|
569
|
-
stream.stream.write(new Uint8Array([]), (err) =>
|
|
570
|
-
err ? reject(streamError(id, stream.stream, err)) : resolve()
|
|
571
|
-
);
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const inputStream = getStreamOrThrow(payload.src);
|
|
737
|
+
if (!inputStream.stream.readable) {
|
|
738
|
+
promise = promise.then(() =>
|
|
739
|
+
new Promise((_resolve, _reject) => {
|
|
740
|
+
inputStream.stream
|
|
741
|
+
.once("readable", (resolve = _resolve))
|
|
742
|
+
.once("error", (reject = _reject));
|
|
572
743
|
}).then(
|
|
573
|
-
() =>
|
|
744
|
+
() => {
|
|
745
|
+
inputStream.stream.off("error", reject);
|
|
746
|
+
},
|
|
574
747
|
(err) => {
|
|
575
|
-
|
|
576
|
-
throw streamError(
|
|
748
|
+
inputStream.stream.off("readable", resolve);
|
|
749
|
+
throw streamError(err);
|
|
577
750
|
}
|
|
578
|
-
)
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
return handle(
|
|
591
|
-
OUTPUT_STREAM_WRITE | (call & CALL_TYPE_MASK),
|
|
592
|
-
id,
|
|
593
|
-
new Uint8Array(Number(payload))
|
|
594
|
-
);
|
|
595
|
-
case OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH:
|
|
596
|
-
return handle(
|
|
597
|
-
OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | (call & CALL_TYPE_MASK),
|
|
598
|
-
id,
|
|
599
|
-
new Uint8Array(Number(payload))
|
|
600
|
-
);
|
|
601
|
-
case OUTPUT_STREAM_SPLICE: {
|
|
602
|
-
const { stream: outputStream } = getStreamOrThrow(id);
|
|
603
|
-
const { stream: inputStream } = getStreamOrThrow(payload.src);
|
|
604
|
-
let bytesRemaining = Number(payload.len);
|
|
605
|
-
let chunk;
|
|
606
|
-
while (
|
|
607
|
-
bytesRemaining > 0 &&
|
|
608
|
-
(chunk = inputStream.read(
|
|
609
|
-
Math.min(
|
|
610
|
-
outputStream.writableHighWaterMark -
|
|
611
|
-
outputStream.writableLength,
|
|
612
|
-
bytesRemaining
|
|
613
|
-
)
|
|
614
|
-
))
|
|
615
|
-
) {
|
|
616
|
-
bytesRemaining -= chunk.byteLength;
|
|
617
|
-
outputStream.write(chunk);
|
|
618
|
-
}
|
|
619
|
-
// TODO: these error handlers should be attached, and only for the duration of the splice flush
|
|
620
|
-
if (inputStream.errored)
|
|
621
|
-
throw streamError(payload.src, inputStream, inputStream.errored);
|
|
622
|
-
if (outputStream.errored)
|
|
623
|
-
throw streamError(id, outputStream, outputStream.errored);
|
|
624
|
-
return payload.len - BigInt(bytesRemaining);
|
|
625
|
-
}
|
|
626
|
-
case OUTPUT_STREAM_SUBSCRIBE: {
|
|
627
|
-
const { stream, flushPromise } = unfinishedStreams.get(id) ?? {};
|
|
628
|
-
if (flushPromise)
|
|
629
|
-
return flushPromise.then(() => handle(call, id, payload));
|
|
630
|
-
// not added to unfinishedPolls => it's an immediately resolved poll
|
|
631
|
-
if (!stream || stream.closed || stream.errored) return 0;
|
|
632
|
-
if (!stream.writableNeedDrain)
|
|
633
|
-
return createPoll(new Promise((resolve) => setTimeout(resolve)));
|
|
634
|
-
let resolve, reject;
|
|
635
|
-
return createPoll(
|
|
636
|
-
new Promise((_resolve, _reject) => {
|
|
637
|
-
stream
|
|
638
|
-
.once("drain", (resolve = _resolve))
|
|
639
|
-
.once("error", (reject = _reject));
|
|
640
|
-
}).then(() => void stream.off("error", reject)),
|
|
641
|
-
// error is read off stream itself when later accessed
|
|
642
|
-
(_err) => void stream.off("drain", resolve)
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
case OUTPUT_STREAM_BLOCKING_SPLICE: {
|
|
646
|
-
const { stream: outputStream } = getStreamOrThrow(id);
|
|
647
|
-
let promise = Promise.resolve();
|
|
648
|
-
let resolve, reject;
|
|
649
|
-
if (outputStream.writableNeedDrain) {
|
|
650
|
-
promise = new Promise((_resolve, _reject) => {
|
|
651
|
-
outputStream
|
|
652
|
-
.once("drain", (resolve = _resolve))
|
|
653
|
-
.once("error", (reject = _reject));
|
|
654
|
-
}).then(
|
|
655
|
-
() => {
|
|
656
|
-
outputStream.off("error", reject);
|
|
657
|
-
},
|
|
658
|
-
(err) => {
|
|
659
|
-
outputStream.off("drain", resolve);
|
|
660
|
-
throw streamError(err);
|
|
661
|
-
}
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
|
-
const { stream: inputStream } = getStreamOrThrow(payload.src);
|
|
665
|
-
if (!inputStream.readable) {
|
|
666
|
-
promise = promise.then(() =>
|
|
667
|
-
new Promise((_resolve, _reject) => {
|
|
668
|
-
inputStream
|
|
669
|
-
.once("readable", (resolve = _resolve))
|
|
670
|
-
.once("error", (reject = _reject));
|
|
671
|
-
}).then(
|
|
672
|
-
() => {
|
|
673
|
-
inputStream.off("error", reject);
|
|
674
|
-
},
|
|
675
|
-
(err) => {
|
|
676
|
-
inputStream.off("readable", resolve);
|
|
677
|
-
throw streamError(err);
|
|
678
|
-
}
|
|
679
|
-
)
|
|
680
|
-
);
|
|
681
|
-
}
|
|
682
|
-
return promise.then(() => handle(OUTPUT_STREAM_SPLICE, id, payload));
|
|
683
|
-
}
|
|
684
|
-
case OUTPUT_STREAM_DISPOSE: {
|
|
685
|
-
const stream = unfinishedStreams.get(id);
|
|
686
|
-
if (stream) {
|
|
687
|
-
stream.stream.end();
|
|
688
|
-
unfinishedStreams.delete(id);
|
|
689
|
-
}
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
751
|
+
)
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return promise.then(() => handle(OUTPUT_STREAM_SPLICE, id, payload));
|
|
755
|
+
}
|
|
756
|
+
case OUTPUT_STREAM_DISPOSE: {
|
|
757
|
+
const stream = streams.get(id);
|
|
758
|
+
verifyPollsDroppedForDrop(stream.pollState, "output stream");
|
|
759
|
+
stream.stream.end();
|
|
760
|
+
streams.delete(id);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
692
763
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
764
|
+
case POLL_POLLABLE_READY:
|
|
765
|
+
return polls.get(id).ready;
|
|
766
|
+
case POLL_POLLABLE_BLOCK:
|
|
767
|
+
payload = [id];
|
|
768
|
+
// [intentional case fall-through]
|
|
769
|
+
case POLL_POLL_LIST: {
|
|
770
|
+
if (payload.length === 0)
|
|
771
|
+
throw new Error("wasi-io trap: attempt to poll on empty list");
|
|
772
|
+
const doneList = [];
|
|
773
|
+
const pollList = payload.map((pollId) => polls.get(pollId));
|
|
774
|
+
for (const [idx, pollState] of pollList.entries()) {
|
|
775
|
+
pollStateCheck(pollState);
|
|
776
|
+
if (pollState.ready) doneList.push(idx);
|
|
777
|
+
}
|
|
778
|
+
if (doneList.length > 0) return new Uint32Array(doneList);
|
|
779
|
+
let readyPromiseResolve;
|
|
780
|
+
const readyPromise = new Promise(
|
|
781
|
+
(resolve) => void (readyPromiseResolve = resolve)
|
|
782
|
+
);
|
|
783
|
+
for (const poll of pollList) {
|
|
784
|
+
poll.listener = readyPromiseResolve;
|
|
785
|
+
}
|
|
786
|
+
return readyPromise.then(() => {
|
|
787
|
+
for (const [idx, pollState] of pollList.entries()) {
|
|
788
|
+
pollState.listener = null;
|
|
789
|
+
if (pollState.ready) doneList.push(idx);
|
|
715
790
|
}
|
|
791
|
+
return new Uint32Array(doneList);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
case POLL_POLLABLE_DISPOSE:
|
|
795
|
+
if (!polls.delete(id))
|
|
796
|
+
throw new Error(
|
|
797
|
+
`wasi-io trap: Disposed a poll ${id} that does not exist`
|
|
798
|
+
);
|
|
799
|
+
return;
|
|
716
800
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}`
|
|
735
|
-
);
|
|
736
|
-
}
|
|
801
|
+
case FUTURE_TAKE_VALUE:
|
|
802
|
+
return futureTakeValue(id);
|
|
803
|
+
|
|
804
|
+
case FUTURE_SUBSCRIBE: {
|
|
805
|
+
const { pollState } = futures.get(id);
|
|
806
|
+
const pollId = ++pollCnt;
|
|
807
|
+
polls.set(pollId, pollState);
|
|
808
|
+
return pollId;
|
|
809
|
+
}
|
|
810
|
+
case FUTURE_DISPOSE:
|
|
811
|
+
return void futureDispose(id, true);
|
|
812
|
+
default:
|
|
813
|
+
throw new Error(
|
|
814
|
+
`wasi-io trap: Unknown call ${call} (${reverseMap[call]}) with type ${
|
|
815
|
+
reverseMap[call & CALL_TYPE_MASK]
|
|
816
|
+
}`
|
|
817
|
+
);
|
|
737
818
|
}
|
|
738
819
|
}
|
|
739
820
|
|
|
740
|
-
|
|
741
|
-
|
|
821
|
+
/**
|
|
822
|
+
* @param {PollState} pollState
|
|
823
|
+
*/
|
|
824
|
+
function createPoll(pollState) {
|
|
742
825
|
const pollId = ++pollCnt;
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
promise.then(
|
|
746
|
-
() => void unfinishedPolls.delete(pollId),
|
|
747
|
-
(err) => {
|
|
748
|
-
_rawDebug("Unexpected poll error");
|
|
749
|
-
_rawDebug(err);
|
|
750
|
-
exit(1);
|
|
751
|
-
}
|
|
752
|
-
)
|
|
753
|
-
);
|
|
826
|
+
pollState.polls.push(pollId);
|
|
827
|
+
polls.set(pollId, pollState);
|
|
754
828
|
return pollId;
|
|
755
829
|
}
|
|
756
830
|
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
831
|
+
function subscribeInstant(pollState, instant) {
|
|
832
|
+
const duration = instant - hrtime.bigint();
|
|
833
|
+
if (duration <= 0) return pollStateReady(pollState);
|
|
834
|
+
function cb() {
|
|
835
|
+
if (hrtime.bigint() < instant) return subscribeInstant(pollState, instant);
|
|
836
|
+
pollStateReady(pollState);
|
|
837
|
+
}
|
|
838
|
+
if (duration < 10e6) setImmediate(cb);
|
|
839
|
+
else setTimeout(cb, Number(duration) / 1e6);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* @param {PollState} pollState
|
|
844
|
+
* @param {string} polledResourceDebugName
|
|
845
|
+
*/
|
|
846
|
+
export function verifyPollsDroppedForDrop(pollState, polledResourceDebugName) {
|
|
847
|
+
for (const pollId of pollState.polls) {
|
|
848
|
+
const poll = polls.get(pollId);
|
|
849
|
+
if (poll)
|
|
850
|
+
throw new Error(
|
|
851
|
+
`wasi-io trap: Cannot drop ${polledResourceDebugName} as it has a child poll resource which has not yet been dropped`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* @param {PollState} pollState
|
|
858
|
+
* @param {bool} finished
|
|
859
|
+
*/
|
|
860
|
+
export function pollStateReady(pollState) {
|
|
861
|
+
if (pollState.ready && pollState.listener) {
|
|
862
|
+
uncaughtException = new Error(
|
|
863
|
+
"wasi-io trap: poll already ready with listener attached"
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
pollState.ready = true;
|
|
867
|
+
if (pollState.listener) {
|
|
868
|
+
pollState.listener();
|
|
869
|
+
pollState.listener = null;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* @param {PollState} pollState
|
|
875
|
+
*/
|
|
876
|
+
function pollStateCheck(pollState) {
|
|
877
|
+
if (pollState.ready && pollState.parentStream) {
|
|
878
|
+
// stream ONLY applies to readable streams here
|
|
879
|
+
const stream = pollState.parentStream;
|
|
880
|
+
const res = stream.read(0);
|
|
881
|
+
if (res !== null) {
|
|
882
|
+
throw new Error("wasi-io trap: got data for a null read");
|
|
883
|
+
}
|
|
884
|
+
if (
|
|
885
|
+
pollState.ready &&
|
|
886
|
+
stream.readableLength === 0 &&
|
|
887
|
+
!stream.readableEnded &&
|
|
888
|
+
!stream.errored
|
|
889
|
+
) {
|
|
890
|
+
pollState.ready = false;
|
|
891
|
+
stream.once("readable", () => {
|
|
892
|
+
pollStateReady(pollState);
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
*
|
|
900
|
+
* @param {Promise<any>} promise
|
|
901
|
+
* @param {PollState | undefined} pollState
|
|
902
|
+
* @returns {number}
|
|
903
|
+
*/
|
|
904
|
+
export function createFuture(promise, pollState) {
|
|
905
|
+
const futureId = ++futureCnt;
|
|
906
|
+
if (pollState) {
|
|
907
|
+
pollState.ready = false;
|
|
908
|
+
} else {
|
|
909
|
+
pollState = {
|
|
910
|
+
ready: false,
|
|
911
|
+
listener: null,
|
|
912
|
+
polls: [],
|
|
913
|
+
parent: null,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
const future = { tag: "ok", val: null };
|
|
917
|
+
futures.set(futureId, { future, pollState });
|
|
918
|
+
promise.then(
|
|
919
|
+
(value) => {
|
|
920
|
+
pollStateReady(pollState);
|
|
921
|
+
future.val = value;
|
|
922
|
+
},
|
|
923
|
+
(value) => {
|
|
924
|
+
pollStateReady(pollState);
|
|
925
|
+
future.tag = "err";
|
|
926
|
+
future.val = value;
|
|
927
|
+
}
|
|
771
928
|
);
|
|
772
|
-
return
|
|
929
|
+
return futureId;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* @param {number} id
|
|
934
|
+
* @returns {undefined | { tag: 'ok', val: any } | { tag: 'err', val: undefined }}
|
|
935
|
+
* @throws {undefined}
|
|
936
|
+
*/
|
|
937
|
+
export function futureTakeValue(id) {
|
|
938
|
+
const future = futures.get(id);
|
|
939
|
+
// Not ready = return undefined
|
|
940
|
+
if (!future.pollState.ready) return undefined;
|
|
941
|
+
// Ready but already taken = return { tag: 'err', val: undefined }
|
|
942
|
+
if (!future.future) return { tag: "err", val: undefined };
|
|
943
|
+
const out = { tag: "ok", val: future.future };
|
|
944
|
+
future.future = null;
|
|
945
|
+
return out;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export function futureDispose(id, ownsState) {
|
|
949
|
+
const { pollState } = futures.get(id);
|
|
950
|
+
if (ownsState) verifyPollsDroppedForDrop(pollState, "future");
|
|
951
|
+
return void futures.delete(id);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
let uncaughtException;
|
|
955
|
+
process.on("uncaughtException", (err) => (uncaughtException = err));
|
|
956
|
+
|
|
957
|
+
// eslint-disable-next-line no-unused-vars
|
|
958
|
+
function trace(msg) {
|
|
959
|
+
const tmpErr = new Error(format(msg));
|
|
960
|
+
log(tmpErr.stack);
|
|
773
961
|
}
|
|
774
962
|
|
|
775
|
-
runAsWorker(handle);
|
|
963
|
+
const debug = runAsWorker(handle);
|