@bytecodealliance/preview2-shim 0.14.1 → 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/README.md +2 -2
- package/lib/browser/filesystem.js +6 -5
- package/lib/browser/random.js +1 -1
- package/lib/browser/sockets.js +0 -14
- package/lib/io/calls.js +80 -16
- package/lib/io/worker-http.js +164 -67
- package/lib/io/worker-io.js +207 -68
- package/lib/io/worker-socket-tcp.js +285 -0
- package/lib/io/worker-socket-udp.js +576 -0
- package/lib/io/worker-sockets.js +371 -0
- package/lib/io/worker-thread.js +793 -399
- package/lib/nodejs/cli.js +29 -13
- package/lib/nodejs/clocks.js +9 -6
- package/lib/nodejs/filesystem.js +170 -57
- package/lib/nodejs/http.js +662 -531
- package/lib/nodejs/index.js +0 -3
- package/lib/nodejs/sockets.js +571 -11
- package/lib/synckit/index.js +25 -41
- package/package.json +2 -2
- package/types/interfaces/wasi-http-types.d.ts +53 -41
- package/types/interfaces/wasi-sockets-tcp.d.ts +5 -0
- package/lib/common/assert.js +0 -7
- package/lib/nodejs/sockets/socket-common.js +0 -116
- package/lib/nodejs/sockets/socketopts-bindings.js +0 -94
- package/lib/nodejs/sockets/tcp-socket-impl.js +0 -794
- package/lib/nodejs/sockets/udp-socket-impl.js +0 -628
- package/lib/nodejs/sockets/wasi-sockets.js +0 -320
- package/lib/synckit/index.d.ts +0 -71
package/lib/io/worker-thread.js
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import { resolve } from "node:dns/promises";
|
|
2
1
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
3
|
-
import { hrtime } from "node:process";
|
|
2
|
+
import { hrtime, stderr, stdout } from "node:process";
|
|
3
|
+
import { PassThrough } from "node:stream";
|
|
4
|
+
import { format } from "node:util";
|
|
4
5
|
import { runAsWorker } from "../synckit/index.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
6
|
+
import {
|
|
7
|
+
clearOutgoingResponse,
|
|
8
|
+
createHttpRequest,
|
|
9
|
+
setOutgoingResponse,
|
|
10
|
+
startHttpServer,
|
|
11
|
+
stopHttpServer,
|
|
12
|
+
} from "./worker-http.js";
|
|
13
|
+
import { Readable } from "node:stream";
|
|
14
|
+
import { read } from "node:fs";
|
|
15
|
+
import { nextTick } from "node:process";
|
|
7
16
|
import {
|
|
8
17
|
CALL_MASK,
|
|
9
|
-
CALL_SHIFT,
|
|
10
18
|
CALL_TYPE_MASK,
|
|
11
19
|
CLOCKS_DURATION_SUBSCRIBE,
|
|
12
20
|
CLOCKS_INSTANT_SUBSCRIBE,
|
|
13
21
|
CLOCKS_NOW,
|
|
22
|
+
FILE,
|
|
14
23
|
FUTURE_DISPOSE,
|
|
15
|
-
|
|
24
|
+
FUTURE_SUBSCRIBE,
|
|
25
|
+
FUTURE_TAKE_VALUE,
|
|
26
|
+
HTTP,
|
|
16
27
|
HTTP_CREATE_REQUEST,
|
|
28
|
+
HTTP_OUTGOING_BODY_DISPOSE,
|
|
17
29
|
HTTP_OUTPUT_STREAM_FINISH,
|
|
30
|
+
HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
|
|
31
|
+
HTTP_SERVER_SET_OUTGOING_RESPONSE,
|
|
32
|
+
HTTP_SERVER_START,
|
|
33
|
+
HTTP_SERVER_STOP,
|
|
18
34
|
INPUT_STREAM_BLOCKING_READ,
|
|
19
35
|
INPUT_STREAM_BLOCKING_SKIP,
|
|
20
36
|
INPUT_STREAM_CREATE,
|
|
@@ -32,90 +48,243 @@ import {
|
|
|
32
48
|
OUTPUT_STREAM_FLUSH,
|
|
33
49
|
OUTPUT_STREAM_SPLICE,
|
|
34
50
|
OUTPUT_STREAM_SUBSCRIBE,
|
|
35
|
-
OUTPUT_STREAM_WRITE_ZEROES,
|
|
36
51
|
OUTPUT_STREAM_WRITE,
|
|
37
|
-
|
|
52
|
+
OUTPUT_STREAM_WRITE_ZEROES,
|
|
38
53
|
POLL_POLLABLE_BLOCK,
|
|
54
|
+
POLL_POLLABLE_DISPOSE,
|
|
39
55
|
POLL_POLLABLE_READY,
|
|
56
|
+
POLL_POLL_LIST,
|
|
40
57
|
SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST,
|
|
58
|
+
SOCKET_RESOLVE_ADDRESS_SUBSCRIBE_REQUEST,
|
|
41
59
|
SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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,
|
|
68
|
+
SOCKET_TCP_CREATE_HANDLE,
|
|
69
|
+
SOCKET_TCP_DISPOSE,
|
|
70
|
+
SOCKET_TCP_GET_LOCAL_ADDRESS,
|
|
71
|
+
SOCKET_TCP_GET_REMOTE_ADDRESS,
|
|
72
|
+
SOCKET_TCP_IS_LISTENING,
|
|
73
|
+
SOCKET_TCP_LISTEN_FINISH,
|
|
74
|
+
SOCKET_TCP_LISTEN_START,
|
|
75
|
+
SOCKET_TCP_SET_KEEP_ALIVE,
|
|
76
|
+
SOCKET_TCP_SET_LISTEN_BACKLOG_SIZE,
|
|
77
|
+
SOCKET_TCP_SHUTDOWN,
|
|
78
|
+
SOCKET_TCP_SUBSCRIBE,
|
|
79
|
+
SOCKET_UDP_BIND_FINISH,
|
|
80
|
+
SOCKET_UDP_BIND_START,
|
|
81
|
+
SOCKET_UDP_CREATE_HANDLE,
|
|
82
|
+
SOCKET_UDP_DISPOSE,
|
|
83
|
+
SOCKET_UDP_GET_LOCAL_ADDRESS,
|
|
84
|
+
SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE,
|
|
85
|
+
SOCKET_UDP_GET_REMOTE_ADDRESS,
|
|
86
|
+
SOCKET_UDP_GET_SEND_BUFFER_SIZE,
|
|
87
|
+
SOCKET_UDP_GET_UNICAST_HOP_LIMIT,
|
|
88
|
+
SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE,
|
|
89
|
+
SOCKET_UDP_SET_SEND_BUFFER_SIZE,
|
|
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,
|
|
46
98
|
STDERR,
|
|
47
99
|
STDIN,
|
|
48
100
|
STDOUT,
|
|
101
|
+
reverseMap,
|
|
49
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";
|
|
114
|
+
import {
|
|
115
|
+
createTcpSocket,
|
|
116
|
+
socketTcpAccept,
|
|
117
|
+
socketTcpBindStart,
|
|
118
|
+
socketTcpConnectStart,
|
|
119
|
+
socketTcpDispose,
|
|
120
|
+
socketTcpFinish,
|
|
121
|
+
socketTcpGetLocalAddress,
|
|
122
|
+
socketTcpGetRemoteAddress,
|
|
123
|
+
socketTcpListenStart,
|
|
124
|
+
socketTcpSetKeepAlive,
|
|
125
|
+
socketTcpSetListenBacklogSize,
|
|
126
|
+
socketTcpShutdown,
|
|
127
|
+
tcpSockets,
|
|
128
|
+
} from "./worker-socket-tcp.js";
|
|
129
|
+
import {
|
|
130
|
+
createUdpSocket,
|
|
131
|
+
datagramStreams,
|
|
132
|
+
socketDatagramStreamDispose,
|
|
133
|
+
socketIncomingDatagramStreamReceive,
|
|
134
|
+
socketOutgoingDatagramStreamCheckSend,
|
|
135
|
+
socketOutgoingDatagramStreamSend,
|
|
136
|
+
socketUdpBindFinish,
|
|
137
|
+
socketUdpBindStart,
|
|
138
|
+
socketUdpDispose,
|
|
139
|
+
socketUdpGetLocalAddress,
|
|
140
|
+
socketUdpGetReceiveBufferSize,
|
|
141
|
+
socketUdpGetRemoteAddress,
|
|
142
|
+
socketUdpGetSendBufferSize,
|
|
143
|
+
socketUdpGetUnicastHopLimit,
|
|
144
|
+
socketUdpSetReceiveBufferSize,
|
|
145
|
+
socketUdpSetSendBufferSize,
|
|
146
|
+
socketUdpSetUnicastHopLimit,
|
|
147
|
+
socketUdpStream,
|
|
148
|
+
udpSockets,
|
|
149
|
+
} from "./worker-socket-udp.js";
|
|
50
150
|
|
|
51
|
-
|
|
52
|
-
|
|
151
|
+
function log(msg) {
|
|
152
|
+
if (debug) process._rawDebug(msg);
|
|
153
|
+
}
|
|
53
154
|
|
|
54
|
-
|
|
55
|
-
|
|
155
|
+
let pollCnt = 0,
|
|
156
|
+
streamCnt = 0,
|
|
157
|
+
futureCnt = 0;
|
|
56
158
|
|
|
57
|
-
/**
|
|
58
|
-
|
|
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
|
+
*/
|
|
59
181
|
|
|
60
|
-
/** @type {Map<number,
|
|
61
|
-
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
|
+
}
|
|
62
209
|
|
|
63
210
|
/**
|
|
64
211
|
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
|
|
65
212
|
*/
|
|
66
|
-
export function
|
|
67
|
-
|
|
68
|
-
|
|
213
|
+
export function createReadableStream(
|
|
214
|
+
nodeStream,
|
|
215
|
+
pollState = createReadableStreamPollState(nodeStream)
|
|
216
|
+
) {
|
|
217
|
+
const stream = {
|
|
218
|
+
stream: nodeStream,
|
|
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 = {
|
|
69
234
|
stream: nodeStream,
|
|
70
|
-
|
|
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);
|
|
71
253
|
return streamCnt;
|
|
72
254
|
}
|
|
73
255
|
|
|
74
256
|
// Stdio
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
257
|
+
// Stdin created when used
|
|
258
|
+
createWritableStream(stdout);
|
|
259
|
+
createWritableStream(stderr);
|
|
78
260
|
|
|
79
261
|
/**
|
|
80
262
|
* @param {number} streamId
|
|
81
263
|
* @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
|
|
82
264
|
*/
|
|
83
|
-
function streamError(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
265
|
+
function streamError(err) {
|
|
266
|
+
return {
|
|
267
|
+
tag: "last-operation-failed",
|
|
268
|
+
val: { code: err.code, message: err.message, stack: err.stack },
|
|
269
|
+
};
|
|
88
270
|
}
|
|
89
271
|
|
|
90
272
|
/**
|
|
91
273
|
* @param {number} streamId
|
|
92
|
-
* @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream,
|
|
274
|
+
* @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream, polls: number[] }}
|
|
93
275
|
*/
|
|
94
276
|
export function getStreamOrThrow(streamId) {
|
|
95
|
-
|
|
277
|
+
if (!streamId) throw new Error("wasi-io trap: no stream id provided");
|
|
278
|
+
const stream = streams.get(streamId);
|
|
96
279
|
// not in unfinished streams <=> closed
|
|
97
280
|
if (!stream) throw { tag: "closed" };
|
|
98
|
-
if (stream.stream.errored)
|
|
99
|
-
throw streamError(streamId, stream, stream.stream.errored);
|
|
281
|
+
if (stream.stream.errored) throw streamError(stream.stream.errored);
|
|
100
282
|
if (stream.stream.closed) {
|
|
101
|
-
unfinishedStreams.delete(streamId);
|
|
102
283
|
throw { tag: "closed" };
|
|
103
284
|
}
|
|
104
285
|
return stream;
|
|
105
286
|
}
|
|
106
287
|
|
|
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
288
|
/**
|
|
120
289
|
* @param {number} call
|
|
121
290
|
* @param {number | null} id
|
|
@@ -123,111 +292,265 @@ function subscribeInstant(instant) {
|
|
|
123
292
|
* @returns {Promise<any>}
|
|
124
293
|
*/
|
|
125
294
|
function handle(call, id, payload) {
|
|
295
|
+
if (uncaughtException) throw uncaughtException;
|
|
126
296
|
switch (call) {
|
|
127
297
|
// Http
|
|
128
298
|
case HTTP_CREATE_REQUEST: {
|
|
129
|
-
const {
|
|
130
|
-
|
|
299
|
+
const {
|
|
300
|
+
method,
|
|
301
|
+
scheme,
|
|
302
|
+
authority,
|
|
303
|
+
pathWithQuery,
|
|
304
|
+
headers,
|
|
305
|
+
body,
|
|
306
|
+
connectTimeout,
|
|
307
|
+
betweenBytesTimeout,
|
|
308
|
+
firstByteTimeout,
|
|
309
|
+
} = payload;
|
|
310
|
+
return createFuture(
|
|
311
|
+
createHttpRequest(
|
|
312
|
+
method,
|
|
313
|
+
scheme,
|
|
314
|
+
authority,
|
|
315
|
+
pathWithQuery,
|
|
316
|
+
headers,
|
|
317
|
+
body,
|
|
318
|
+
connectTimeout,
|
|
319
|
+
betweenBytesTimeout,
|
|
320
|
+
firstByteTimeout
|
|
321
|
+
)
|
|
322
|
+
);
|
|
131
323
|
}
|
|
132
324
|
case OUTPUT_STREAM_CREATE | HTTP: {
|
|
133
|
-
const
|
|
134
|
-
const stream = Writable.fromWeb(webTransformStream.writable);
|
|
325
|
+
const stream = new PassThrough();
|
|
135
326
|
// content length is passed as payload
|
|
327
|
+
stream.contentLength = payload;
|
|
136
328
|
stream.bytesRemaining = payload;
|
|
137
|
-
stream
|
|
138
|
-
return createStream(stream);
|
|
329
|
+
return createWritableStream(stream);
|
|
139
330
|
}
|
|
140
331
|
case OUTPUT_STREAM_SUBSCRIBE | HTTP:
|
|
141
332
|
case OUTPUT_STREAM_FLUSH | HTTP:
|
|
142
333
|
case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP: {
|
|
143
334
|
// http flush is a noop
|
|
144
335
|
const { stream } = getStreamOrThrow(id);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
336
|
+
if (call === (OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP)) {
|
|
337
|
+
stream.bytesRemaining -= payload.byteLength;
|
|
338
|
+
if (stream.bytesRemaining < 0) {
|
|
339
|
+
throw {
|
|
340
|
+
tag: "last-operation-failed",
|
|
341
|
+
val: {
|
|
342
|
+
tag: "HTTP-request-body-size",
|
|
343
|
+
val: stream.contentLength - stream.bytesRemaining,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
155
346
|
}
|
|
156
347
|
}
|
|
157
|
-
if (call === (OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP))
|
|
158
|
-
stream.bytesRemaining -= payload.byteLength;
|
|
159
348
|
// otherwise fall through to generic implementation
|
|
160
349
|
return handle(call & ~HTTP, id, payload);
|
|
161
350
|
}
|
|
162
|
-
case OUTPUT_STREAM_DISPOSE | HTTP:
|
|
163
|
-
throw new Error('Internal error: HTTP output stream dispose is bypassed for FINISH');
|
|
164
351
|
case OUTPUT_STREAM_WRITE | HTTP: {
|
|
165
352
|
const { stream } = getStreamOrThrow(id);
|
|
166
353
|
stream.bytesRemaining -= payload.byteLength;
|
|
167
354
|
if (stream.bytesRemaining < 0) {
|
|
168
355
|
throw {
|
|
169
|
-
tag:
|
|
170
|
-
val:
|
|
356
|
+
tag: "last-operation-failed",
|
|
357
|
+
val: {
|
|
358
|
+
tag: "HTTP-request-body-size",
|
|
359
|
+
val: stream.contentLength - stream.bytesRemaining,
|
|
360
|
+
},
|
|
171
361
|
};
|
|
172
362
|
}
|
|
173
363
|
const output = handle(OUTPUT_STREAM_WRITE, id, payload);
|
|
174
364
|
return output;
|
|
175
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
|
+
);
|
|
176
370
|
case HTTP_OUTPUT_STREAM_FINISH: {
|
|
177
|
-
|
|
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
|
+
}
|
|
178
380
|
if (stream.bytesRemaining > 0) {
|
|
179
381
|
throw {
|
|
180
|
-
tag:
|
|
181
|
-
val:
|
|
382
|
+
tag: "HTTP-request-body-size",
|
|
383
|
+
val: stream.contentLength - stream.bytesRemaining,
|
|
182
384
|
};
|
|
183
385
|
}
|
|
184
386
|
if (stream.bytesRemaining < 0) {
|
|
185
387
|
throw {
|
|
186
|
-
tag:
|
|
187
|
-
val:
|
|
388
|
+
tag: "HTTP-request-body-size",
|
|
389
|
+
val: stream.contentLength - stream.bytesRemaining,
|
|
188
390
|
};
|
|
189
391
|
}
|
|
190
392
|
stream.end();
|
|
191
|
-
|
|
393
|
+
return;
|
|
192
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;
|
|
399
|
+
case HTTP_SERVER_START:
|
|
400
|
+
return startHttpServer(id, payload);
|
|
401
|
+
case HTTP_SERVER_STOP:
|
|
402
|
+
return stopHttpServer(id);
|
|
403
|
+
case HTTP_SERVER_SET_OUTGOING_RESPONSE:
|
|
404
|
+
return setOutgoingResponse(id, payload);
|
|
405
|
+
case HTTP_SERVER_CLEAR_OUTGOING_RESPONSE:
|
|
406
|
+
return clearOutgoingResponse(id);
|
|
193
407
|
|
|
194
|
-
// Sockets
|
|
408
|
+
// Sockets name resolution
|
|
195
409
|
case SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST:
|
|
196
|
-
return createFuture(
|
|
410
|
+
return createFuture(socketResolveAddress(payload));
|
|
411
|
+
case SOCKET_RESOLVE_ADDRESS_SUBSCRIBE_REQUEST:
|
|
412
|
+
return createPoll(futures.get(id).pollState);
|
|
197
413
|
case SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST:
|
|
198
|
-
return void
|
|
199
|
-
case
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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;
|
|
216
420
|
}
|
|
217
421
|
|
|
422
|
+
// Sockets TCP
|
|
423
|
+
case SOCKET_TCP_ACCEPT:
|
|
424
|
+
return socketTcpAccept(id);
|
|
425
|
+
case SOCKET_TCP_CREATE_HANDLE:
|
|
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);
|
|
447
|
+
case SOCKET_TCP_GET_LOCAL_ADDRESS:
|
|
448
|
+
return socketTcpGetLocalAddress(id);
|
|
449
|
+
case SOCKET_TCP_GET_REMOTE_ADDRESS:
|
|
450
|
+
return socketTcpGetRemoteAddress(id);
|
|
451
|
+
case SOCKET_TCP_SHUTDOWN:
|
|
452
|
+
return socketTcpShutdown(id, payload);
|
|
453
|
+
case SOCKET_TCP_SUBSCRIBE:
|
|
454
|
+
return createPoll(tcpSockets.get(id).pollState);
|
|
455
|
+
case SOCKET_TCP_SET_KEEP_ALIVE:
|
|
456
|
+
return socketTcpSetKeepAlive(id, payload);
|
|
457
|
+
case SOCKET_TCP_DISPOSE:
|
|
458
|
+
return socketTcpDispose(id);
|
|
459
|
+
|
|
460
|
+
// Sockets UDP
|
|
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);
|
|
487
|
+
case SOCKET_UDP_DISPOSE:
|
|
488
|
+
return socketUdpDispose(id);
|
|
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
|
+
|
|
218
501
|
// Stdio
|
|
502
|
+
case OUTPUT_STREAM_BLOCKING_FLUSH | STDOUT:
|
|
503
|
+
case OUTPUT_STREAM_BLOCKING_FLUSH | STDERR:
|
|
504
|
+
// no blocking flush for stdio in Node.js
|
|
505
|
+
return;
|
|
219
506
|
case OUTPUT_STREAM_DISPOSE | STDOUT:
|
|
220
507
|
case OUTPUT_STREAM_DISPOSE | STDERR:
|
|
221
|
-
case INPUT_STREAM_DISPOSE | STDIN:
|
|
222
508
|
return;
|
|
509
|
+
case INPUT_STREAM_CREATE | STDIN: {
|
|
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
|
+
);
|
|
536
|
+
}
|
|
223
537
|
|
|
224
538
|
// Clocks
|
|
225
539
|
case CLOCKS_NOW:
|
|
226
540
|
return hrtime.bigint();
|
|
227
541
|
case CLOCKS_DURATION_SUBSCRIBE:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
}
|
|
231
554
|
|
|
232
555
|
// Filesystem
|
|
233
556
|
case INPUT_STREAM_CREATE | FILE: {
|
|
@@ -238,9 +561,7 @@ function handle(call, id, payload) {
|
|
|
238
561
|
highWaterMark: 64 * 1024,
|
|
239
562
|
start: Number(offset),
|
|
240
563
|
});
|
|
241
|
-
|
|
242
|
-
stream.on("end", () => void stream.emit("readable"));
|
|
243
|
-
return createStream(stream);
|
|
564
|
+
return createReadableStream(stream);
|
|
244
565
|
}
|
|
245
566
|
case OUTPUT_STREAM_CREATE | FILE: {
|
|
246
567
|
const { fd, offset } = payload;
|
|
@@ -251,319 +572,392 @@ function handle(call, id, payload) {
|
|
|
251
572
|
highWaterMark: 64 * 1024,
|
|
252
573
|
start: Number(offset),
|
|
253
574
|
});
|
|
254
|
-
return
|
|
575
|
+
return createWritableStream(stream);
|
|
255
576
|
}
|
|
256
|
-
|
|
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;
|
|
577
|
+
}
|
|
317
578
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
327
703
|
)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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);
|
|
345
733
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
);
|
|
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));
|
|
360
743
|
}).then(
|
|
361
|
-
() =>
|
|
744
|
+
() => {
|
|
745
|
+
inputStream.stream.off("error", reject);
|
|
746
|
+
},
|
|
362
747
|
(err) => {
|
|
363
|
-
|
|
364
|
-
throw streamError(
|
|
748
|
+
inputStream.stream.off("readable", resolve);
|
|
749
|
+
throw streamError(err);
|
|
365
750
|
}
|
|
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
751
|
)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
}
|
|
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
|
+
}
|
|
485
763
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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);
|
|
505
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;
|
|
506
800
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
}
|
|
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
|
+
);
|
|
531
818
|
}
|
|
532
819
|
}
|
|
533
820
|
|
|
534
|
-
|
|
535
|
-
|
|
821
|
+
/**
|
|
822
|
+
* @param {PollState} pollState
|
|
823
|
+
*/
|
|
824
|
+
function createPoll(pollState) {
|
|
536
825
|
const pollId = ++pollCnt;
|
|
537
|
-
|
|
538
|
-
|
|
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
|
-
);
|
|
826
|
+
pollState.polls.push(pollId);
|
|
827
|
+
polls.set(pollId, pollState);
|
|
548
828
|
return pollId;
|
|
549
829
|
}
|
|
550
830
|
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
+
}
|
|
565
928
|
);
|
|
566
|
-
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);
|
|
567
961
|
}
|
|
568
962
|
|
|
569
|
-
runAsWorker(handle);
|
|
963
|
+
const debug = runAsWorker(handle);
|