@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.
@@ -1,7 +1,7 @@
1
- import { resolve } from "node:dns/promises";
2
1
  import { createReadStream, createWriteStream } from "node:fs";
3
- import { _rawDebug, exit, hrtime, stderr, stdout } from "node:process";
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
- const noop = () => {};
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
- FUTURE_GET_VALUE_AND_DISPOSE,
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
- SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST,
58
- SOCKET_TCP_BIND,
59
- SOCKET_TCP_CONNECT,
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
- SOCKET_TCP_LISTEN,
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
- SOCKET_UDP_BIND,
70
- SOCKET_UDP_CHECK_SEND,
71
- SOCKET_UDP_CONNECT,
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
- SOCKET_UDP_RECEIVE,
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
- socketTcpBind,
91
- socketTcpConnect,
116
+ socketTcpAccept,
117
+ socketTcpBindStart,
118
+ socketTcpConnectStart,
92
119
  socketTcpDispose,
120
+ socketTcpFinish,
93
121
  socketTcpGetLocalAddress,
94
122
  socketTcpGetRemoteAddress,
95
- socketTcpListen,
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
- getSocketOrThrow,
103
- socketUdpBind,
104
- socketUdpCheckSend,
105
- socketUdpConnect,
106
- socketUdpDisconnect,
131
+ datagramStreams,
132
+ socketDatagramStreamDispose,
133
+ socketIncomingDatagramStreamReceive,
134
+ socketOutgoingDatagramStreamCheckSend,
135
+ socketOutgoingDatagramStreamSend,
136
+ socketUdpBindFinish,
137
+ socketUdpBindStart,
107
138
  socketUdpDispose,
108
- socketUdpSend,
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
- let streamCnt = 0,
112
- pollCnt = 0;
151
+ function log(msg) {
152
+ if (debug) process._rawDebug(msg);
153
+ }
113
154
 
114
- /** @type {Map<number, Promise<void>>} */
115
- export const unfinishedPolls = new Map();
155
+ let pollCnt = 0,
156
+ streamCnt = 0,
157
+ futureCnt = 0;
116
158
 
117
- /** @type {Map<number, { flushPromise: Promise<void> | null, stream: NodeJS.ReadableStream | NodeJS.WritableStream }>} */
118
- export const unfinishedStreams = new Map();
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, { value: any, error: bool }>} */
121
- export const unfinishedFutures = new Map();
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 createStream(nodeStream) {
127
- unfinishedStreams.set(++streamCnt, {
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
- createStream(stdout);
137
- createStream(stderr);
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(streamId, stream, err) {
144
- if (typeof stream.end === "function") stream.end();
145
- // we delete the stream from unfinishedStreams as it is now "finished" (closed)
146
- unfinishedStreams.delete(streamId);
147
- return { tag: "last-operation-failed", val: { code: err.code, message: err.message, stack: err.stack } };
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, flushPromise: Promise<void> | null }}
274
+ * @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream, polls: number[] }}
153
275
  */
154
276
  export function getStreamOrThrow(streamId) {
155
- if (!streamId) throw new Error("Internal error: no stream id provided");
156
- const stream = unfinishedStreams.get(streamId);
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 createStream(stream);
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
- const { stream } = getStreamOrThrow(id);
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
- break;
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 createFuture(createTcpSocket());
290
-
291
- case SOCKET_TCP_BIND:
292
- return socketTcpBind(id, payload);
293
-
294
- case SOCKET_TCP_CONNECT:
295
- return socketTcpConnect(id, payload);
296
-
297
- case SOCKET_TCP_LISTEN:
298
- return socketTcpListen(id, payload);
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
- const { addressFamily, reuseAddr } = payload;
322
- return createFuture(createUdpSocket(addressFamily, reuseAddr));
323
- }
324
-
325
- case SOCKET_UDP_BIND:
326
- return socketUdpBind(id, payload);
327
-
328
- case SOCKET_UDP_CHECK_SEND:
329
- return socketUdpCheckSend(id);
330
-
331
- case SOCKET_UDP_SEND:
332
- return socketUdpSend(id, payload);
333
-
334
- case SOCKET_UDP_RECEIVE:
335
- return SocketUdpReceive(id, payload);
336
-
337
- case SOCKET_UDP_CONNECT:
338
- return socketUdpConnect(id, payload);
339
-
340
- case SOCKET_UDP_DISCONNECT:
341
- return socketUdpDisconnect(id);
342
-
343
- case SOCKET_UDP_GET_LOCAL_ADDRESS: {
344
- const socket = getSocketOrThrow(id);
345
- return Promise.resolve(socket.address());
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
- const stream = createReadStream(null, {
430
- fd: 0,
431
- highWaterMark: 64 * 1024,
432
- });
433
- // for some reason fs streams dont emit readable on end
434
- stream.on("end", () => void stream.emit("readable"));
435
- return createStream(stream);
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
- return createPoll(subscribeInstant(hrtime.bigint() + payload));
443
- case CLOCKS_INSTANT_SUBSCRIBE:
444
- return createPoll(subscribeInstant(payload));
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
- // for some reason fs streams dont emit readable on end
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 createStream(stream);
575
+ return createWritableStream(stream);
469
576
  }
470
- // Generic call implementations (streams + polls)
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
- case OUTPUT_STREAM_CHECK_WRITE: {
529
- const { stream } = getStreamOrThrow(id);
530
- return BigInt(stream.writableHighWaterMark - stream.writableLength);
531
- }
532
- case OUTPUT_STREAM_WRITE: {
533
- const { stream } = getStreamOrThrow(id);
534
- if (
535
- payload.byteLength >
536
- stream.writableHighWaterMark - stream.writableLength
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
- throw new Error("wasi-io error: attempt to write too many bytes");
539
- return void stream.write(payload);
540
- }
541
- case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH: {
542
- const { stream, flushPromise } = getStreamOrThrow(id);
543
- // if an existing flush, try again after that
544
- if (flushPromise)
545
- return flushPromise.then(() => handle(call, id, payload));
546
- if (
547
- payload.byteLength >
548
- stream.writableHighWaterMark - stream.writableLength
549
- ) {
550
- throw streamError(
551
- id,
552
- stream,
553
- new Error("Cannot write more than permitted writable length")
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
- return new Promise((resolve, reject) => {
557
- stream.once("error", noop);
558
- stream.write(payload, (err) => {
559
- if (err) return void reject(streamError(id, stream, err));
560
- stream.off("error", noop);
561
- resolve(BigInt(payload.byteLength));
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
- () => void (stream.stream.flushPromise = null),
744
+ () => {
745
+ inputStream.stream.off("error", reject);
746
+ },
574
747
  (err) => {
575
- stream.stream.flushPromise = null;
576
- throw streamError(id, stream.stream, err);
748
+ inputStream.stream.off("readable", resolve);
749
+ throw streamError(err);
577
750
  }
578
- ));
579
- }
580
- case OUTPUT_STREAM_BLOCKING_FLUSH: {
581
- const { stream, flushPromise } = getStreamOrThrow(id);
582
- if (flushPromise) return flushPromise;
583
- return new Promise((resolve, reject) => {
584
- stream.write(new Uint8Array([]), (err) =>
585
- err ? reject(streamError(id, stream, err)) : resolve()
586
- );
587
- });
588
- }
589
- case OUTPUT_STREAM_WRITE_ZEROES:
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
- case POLL_POLLABLE_READY:
694
- return !unfinishedPolls.has(id);
695
- case POLL_POLLABLE_BLOCK:
696
- payload = [id];
697
- // [intentional case fall-through]
698
- case POLL_POLL_LIST: {
699
- const doneList = [];
700
- for (const [idx, id] of payload.entries()) {
701
- if (!unfinishedPolls.has(id)) doneList.push(idx);
702
- }
703
- if (doneList.length > 0) return new Uint32Array(doneList);
704
- // if all polls are promise type, we just race them
705
- return Promise.race(
706
- payload.map((id) => unfinishedPolls.get(id))
707
- ).then(() => {
708
- for (const [idx, id] of payload.entries()) {
709
- if (!unfinishedPolls.has(id)) doneList.push(idx);
710
- }
711
- if (doneList.length === 0)
712
- throw new Error("poll promise did not unregister poll");
713
- return new Uint32Array(doneList);
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
- case FUTURE_GET_VALUE_AND_DISPOSE: {
718
- const future = unfinishedFutures.get(id);
719
- if (!future) {
720
- // future not ready yet
721
- if (unfinishedPolls.get(id)) throw undefined;
722
- throw new Error("future already got and dropped");
723
- }
724
- unfinishedFutures.delete(id);
725
- return future;
726
- }
727
- case FUTURE_DISPOSE:
728
- return void unfinishedFutures.delete(id);
729
-
730
- default:
731
- throw new Error(
732
- `Unknown call ${(call & CALL_MASK) >> CALL_SHIFT} with type ${
733
- call & CALL_TYPE_MASK
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
- // poll promises must always resolve and never error
741
- export function createPoll(promise) {
821
+ /**
822
+ * @param {PollState} pollState
823
+ */
824
+ function createPoll(pollState) {
742
825
  const pollId = ++pollCnt;
743
- unfinishedPolls.set(
744
- pollId,
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
- export function createFuture(promise) {
758
- const pollId = ++pollCnt;
759
- unfinishedPolls.set(
760
- pollId,
761
- promise.then(
762
- (value) => {
763
- unfinishedPolls.delete(pollId);
764
- unfinishedFutures.set(pollId, { value, error: false });
765
- },
766
- (value) => {
767
- unfinishedPolls.delete(pollId);
768
- unfinishedFutures.set(pollId, { value, error: true });
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 pollId;
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);