@volter/browser-wali 0.1.192 → 0.1.194

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.
@@ -0,0 +1,9 @@
1
+ interface SleepHooks {
2
+ prepare?(): void;
3
+ checkFailure?(): void;
4
+ /** Existing root pthread-failure cadence, not a guest deadline. */
5
+ failureCheckInterval?: number;
6
+ }
7
+ export declare function waliClockNanosleep(memory: () => WebAssembly.Memory, clock: number, flags: number, request: number, remaining: number, hooks?: SleepHooks): bigint;
8
+ export {};
9
+ //# sourceMappingURL=nanosleep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nanosleep.d.ts","sourceRoot":"","sources":["../src/nanosleep.ts"],"names":[],"mappings":"AAQA,UAAU,UAAU;IAClB,OAAO,CAAC,IAAI,IAAI,CAAC;IACjB,YAAY,CAAC,IAAI,IAAI,CAAC;IACtB,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAKD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAChF,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,UAAe,GAAG,MAAM,CAsDnF"}
@@ -0,0 +1,74 @@
1
+ // Clock validation and conversion extend the Emscripten clock rules in
2
+ // clocks.ts (MIT/NCSA, 862dc0d48acc4134a0088d27ac8f5dd18baba1cd).
3
+ // Linux nanosleep/clock_nanosleep interruption and remainder rules live here,
4
+ // behind syscalls; programs keep musl. No Linux implementation is copied.
5
+ import { monotonicMilliseconds, realtimeMilliseconds } from "./monotonic-clock.js";
6
+ import { hostWait } from "./host-idle.js";
7
+ import { waliSignalWaitState } from "./signal-interruption.js";
8
+ let sleepWord;
9
+ const nowNanoseconds = (clock) => BigInt(Math.round((clock === 0 ? realtimeMilliseconds() : monotonicMilliseconds()) * 1_000_000));
10
+ export function waliClockNanosleep(memory, clock, flags, request, remaining, hooks = {}) {
11
+ // RAW/coarse clocks do not provide Linux nsleep. CPU, alarm, boottime and
12
+ // TAI clocks require accounting/permissions or clock state we do not have.
13
+ if (clock !== 0 && clock !== 1)
14
+ return clock >= 2 && clock <= 11 && clock !== 3 ? -95n : -22n;
15
+ const range = (pointer) => Number.isSafeInteger(pointer) && pointer > 0
16
+ && pointer + 16 <= memory().buffer.byteLength;
17
+ if (!range(request))
18
+ return -14n;
19
+ const input = new DataView(memory().buffer);
20
+ const seconds = input.getBigInt64(request, true), nanoseconds = input.getBigInt64(request + 8, true);
21
+ if (seconds < 0n || nanoseconds < 0n || nanoseconds >= 1000000000n)
22
+ return -22n;
23
+ const absolute = Boolean(flags & 1); // Linux nsleep interprets TIMER_ABSTIME.
24
+ const time = seconds * 1000000000n + nanoseconds;
25
+ const elapsedClock = absolute ? clock : 1;
26
+ const deadline = absolute ? time : nowNanoseconds(elapsedClock) + time;
27
+ if (deadline <= nowNanoseconds(elapsedClock))
28
+ return 0n;
29
+ // A page cannot implement a synchronous blocking syscall. Never pretend it
30
+ // slept when the runtime is used directly outside a worker.
31
+ if ("document" in globalThis)
32
+ return -38n;
33
+ if (deadline - nowNanoseconds(elapsedClock) >= 1000000n)
34
+ hooks.prepare?.();
35
+ for (;;) {
36
+ hooks.checkFailure?.();
37
+ const left = deadline - nowNanoseconds(elapsedClock);
38
+ if (left <= 0n)
39
+ return 0n;
40
+ let state = waliSignalWaitState();
41
+ if (state.pending) {
42
+ // Delivery distinguishes a caught signal from a default-ignored one.
43
+ // The interval left is sampled before the handler consumes guest time.
44
+ if (state.interruptSleep?.() ?? true) {
45
+ if (!absolute && remaining !== 0) {
46
+ if (!range(remaining))
47
+ return -14n;
48
+ const output = new DataView(memory().buffer);
49
+ output.setBigInt64(remaining, left / 1000000000n, true);
50
+ output.setBigInt64(remaining + 8, left % 1000000000n, true);
51
+ }
52
+ return -4n;
53
+ }
54
+ state = waliSignalWaitState();
55
+ }
56
+ const signal = state.guards[0];
57
+ const word = signal?.words ?? (sleepWord ??= new Int32Array(new SharedArrayBuffer(4)));
58
+ const index = signal?.index ?? 0, generation = signal?.value ?? Atomics.load(word, index);
59
+ const afterDelivery = deadline - nowNanoseconds(elapsedClock);
60
+ if (afterDelivery <= 0n)
61
+ return 0n;
62
+ const duration = Number(afterDelivery) / 1_000_000;
63
+ // Preserve the existing root fatal-thread observation cadence. Pthreads
64
+ // need no such poll and wait directly for signal generation or deadline.
65
+ const wait = Math.min(duration, hooks.failureCheckInterval ?? Infinity);
66
+ hostWait(word, index, generation, wait, undefined, {
67
+ resource: "clock", deadline: Date.now() + duration,
68
+ guards: state.guards, interrupt: { words: word, index },
69
+ });
70
+ // Generation wakes include ignored/masked signals and owner work; only a
71
+ // handler/default termination interrupts. Spurious wakes recheck deadline.
72
+ }
73
+ }
74
+ //# sourceMappingURL=nanosleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nanosleep.js","sourceRoot":"","sources":["../src/nanosleep.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"network-host.d.ts","sourceRoot":"","sources":["../src/network-host.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,oBAAoB,EAG1B,MAAM,uBAAuB,CAAC;AAmF/B,8EAA8E;AAC9E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG;IAC3E,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC;CAC1D,CA64CA"}
1
+ {"version":3,"file":"network-host.d.ts","sourceRoot":"","sources":["../src/network-host.ts"],"names":[],"mappings":"AAOA,OAAO,EAML,KAAK,oBAAoB,EAG1B,MAAM,uBAAuB,CAAC;AAqF/B,8EAA8E;AAC9E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG;IAC3E,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC;CAC1D,CA47CA"}
@@ -5,7 +5,7 @@
5
5
  // process ownership and port arbitration remain this kernel's adapters.
6
6
  import { WaliEpollSet } from "./epoll-ready.js";
7
7
  import { waliEpollNestingError } from "./epoll-nesting.js";
8
- import { NETWORK_FD_MIN, NETWORK_PAYLOAD_BYTES, NetworkOperation, } from "./network-protocol.js";
8
+ import { NETWORK_FD_MIN, NETWORK_PAYLOAD_BYTES, NETWORK_RESPONSE_FLAGS, NETWORK_RESPONSE_NO_RESTART, NetworkOperation, } from "./network-protocol.js";
9
9
  import { WaliChildProcesses } from "./child-processes.js";
10
10
  import { WaliSignals } from "./signals.js";
11
11
  import { pollWaliSharedPipeState, waliSharedPipeGeneration } from "./shared-pipes.js";
@@ -157,11 +157,13 @@ export function createWaliNetworkSession(reply) {
157
157
  // Reaping and completed I/O are irreversible. Claim blocking requests
158
158
  // before it starts, so transport cancellation cannot lose status or bytes.
159
159
  const operation = Atomics.load(control, 1);
160
- const claimed = [NetworkOperation.ChildWait, NetworkOperation.PollMany, NetworkOperation.EpollWait, NetworkOperation.Receive].includes(operation)
160
+ const claimed = [NetworkOperation.ChildWait, NetworkOperation.PollMany, NetworkOperation.EpollWait, NetworkOperation.Receive, NetworkOperation.Accept].includes(operation)
161
161
  || (operation === NetworkOperation.Send && sockets.get(Atomics.load(control, 5))?.kind === "event");
162
162
  if (claimed && Atomics.compareExchange(control, 0, requestId, -requestId) !== requestId)
163
163
  return;
164
164
  let result = -38;
165
+ let messageFlags = 0;
166
+ let restartable = true;
165
167
  let output = new Uint8Array();
166
168
  let checkedActivity = activity ? Atomics.load(activity, 0) : 0;
167
169
  const parked = (state) => {
@@ -178,7 +180,10 @@ export function createWaliNetworkSession(reply) {
178
180
  const inputLength = Atomics.load(control, 3);
179
181
  const args = Array.from({ length: 10 }, (_, index) => Atomics.load(control, 5 + index));
180
182
  const input = payload.slice(0, inputLength);
181
- ({ result, output } = await operate(operation, args, input, process, parked));
183
+ const response = await operate(operation, args, input, process, parked);
184
+ ({ result, output } = response);
185
+ messageFlags = response.messageFlags ?? 0;
186
+ restartable = response.restartable !== false;
182
187
  if ([NetworkOperation.EpollControl, NetworkOperation.Send, NetworkOperation.Receive,
183
188
  NetworkOperation.Close, NetworkOperation.Shutdown, NetworkOperation.TimerFdSetTime].includes(operation))
184
189
  notifyDescriptors();
@@ -202,6 +207,8 @@ export function createWaliNetworkSession(reply) {
202
207
  payload.set(output);
203
208
  Atomics.store(control, 2, result);
204
209
  Atomics.store(control, 4, output.byteLength);
210
+ Atomics.store(control, NETWORK_RESPONSE_FLAGS, messageFlags);
211
+ Atomics.store(control, NETWORK_RESPONSE_NO_RESTART, restartable ? 0 : 1);
205
212
  Atomics.store(control, 0, requestId + 1);
206
213
  Atomics.notify(control, 0);
207
214
  }
@@ -293,7 +300,7 @@ export function createWaliNetworkSession(reply) {
293
300
  if (operation === NetworkOperation.Listen)
294
301
  return scalar(await listenSocket(socket, args[1]));
295
302
  if (operation === NetworkOperation.Accept)
296
- return acceptSocket(socket, args[1]);
303
+ return acceptSocket(socket, args[1], signalMask(args[2] ?? 0, args[3] ?? 0), parked);
297
304
  if (operation === NetworkOperation.Send) {
298
305
  if ((args[1] & ~(0x40 | 0x4000)) !== 0)
299
306
  return scalar(-95);
@@ -320,8 +327,7 @@ export function createWaliNetworkSession(reply) {
320
327
  if (operation === NetworkOperation.GetSocketOption)
321
328
  return socketOption(socket, args[1], args[2]);
322
329
  if (operation === NetworkOperation.SetSocketOption) {
323
- // Never claim to apply an option that the relay transport cannot honor.
324
- return scalar(socket.kind === "event" || socket.kind === "timer" ? -88 : -92);
330
+ return scalar(setSocketOption(socket, args[1], args[2], input));
325
331
  }
326
332
  if (operation === NetworkOperation.Duplicate)
327
333
  return scalar(duplicateSocket(socket));
@@ -392,7 +398,9 @@ export function createWaliNetworkSession(reply) {
392
398
  const done = () => { if (timer !== undefined)
393
399
  clearTimeout(timer); descriptorWaiters.delete(done); resolve(); };
394
400
  descriptorWaiters.add(done);
395
- const timer = Number.isFinite(delay) ? setTimeout(done, Math.max(0, delay)) : undefined;
401
+ // Browser timers use signed-int32 milliseconds. A long guest deadline is
402
+ // rechecked after each interval; this cap is not an I/O watchdog.
403
+ const timer = Number.isFinite(delay) ? setTimeout(done, Math.min(0x7fff_ffff, Math.max(0, delay))) : undefined;
396
404
  });
397
405
  }
398
406
  function duplicateEpoll(fd, minimum, closeOnExec = false) {
@@ -715,7 +723,6 @@ export function createWaliNetworkSession(reply) {
715
723
  listening: false,
716
724
  backlog: 0,
717
725
  pending: [],
718
- acceptWaiters: [],
719
726
  peerClosed: false,
720
727
  };
721
728
  }
@@ -951,32 +958,48 @@ export function createWaliNetworkSession(reply) {
951
958
  listenersByPort.set(socket.bound.port, socket);
952
959
  return 0;
953
960
  }
954
- async function acceptSocket(socket, flags) {
961
+ async function acceptSocket(socket, flags, mask, parked) {
955
962
  if (socket.kind !== "tcp" || !socket.listening)
956
963
  return scalar(-22);
957
964
  if ((flags & ~0x8_0800) !== 0)
958
965
  return scalar(-22);
959
- let accepted = socket.pending.shift();
960
- if (!accepted && socket.nonblocking)
961
- return scalar(-11);
962
- if (!accepted) {
963
- accepted = await new Promise((resolve) => socket.acceptWaiters.push(resolve));
964
- }
965
- if (!accepted)
966
- return scalar(-9);
967
- accepted.nonblocking = Boolean(flags & 0x800);
968
- let fd;
966
+ const deadline = receiveDeadline(socket);
967
+ activeIO.set(socket, (activeIO.get(socket) ?? 0) + 1);
969
968
  try {
970
- fd = takeFd();
969
+ for (;;) {
970
+ if (disposed || socket.closed)
971
+ return scalar(-9);
972
+ const accepted = socket.pending.shift();
973
+ if (accepted) {
974
+ accepted.nonblocking = Boolean(flags & 0x800);
975
+ let fd;
976
+ try {
977
+ fd = takeFd();
978
+ }
979
+ catch (error) {
980
+ socket.pending.unshift(accepted);
981
+ throw error;
982
+ }
983
+ sockets.set(fd, accepted);
984
+ socketDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
985
+ return { result: fd, output: socketAddress(accepted.domain, accepted.domain === 10 ? "::" : "0.0.0.0", 0) };
986
+ }
987
+ if (socket.nonblocking || monotonicMilliseconds() >= deadline)
988
+ return scalar(-11);
989
+ if (signals && (signals.pending() & ~mask) !== 0n)
990
+ return { ...scalar(-4), restartable: deadline === Infinity };
991
+ parked(2); // listener ingress or configured deadline can wake the call
992
+ try {
993
+ await waitForDescriptorChange(deadline - monotonicMilliseconds());
994
+ }
995
+ finally {
996
+ parked(0);
997
+ }
998
+ }
971
999
  }
972
- catch (error) {
973
- socket.pending.unshift(accepted);
974
- throw error;
1000
+ finally {
1001
+ releaseActiveIO(socket);
975
1002
  }
976
- sockets.set(fd, accepted);
977
- socketDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
978
- // The ingress wire does not yet carry the remote endpoint. Do not invent it.
979
- return { result: fd, output: socketAddress(accepted.domain, accepted.domain === 10 ? "::" : "0.0.0.0", 0) };
980
1003
  }
981
1004
  function requestLoopbackListen(port) {
982
1005
  if (!loopbackPort)
@@ -1058,17 +1081,14 @@ export function createWaliNetworkSession(reply) {
1058
1081
  listening: false,
1059
1082
  backlog: 0,
1060
1083
  pending: [],
1061
- acceptWaiters: [],
1062
1084
  loopbackConnectionId: message.connectionId,
1063
1085
  peerClosed: false,
1064
1086
  ...(listener.bound ? { bound: listener.bound } : {}),
1087
+ // Clone listener options when the connection is created, not at accept.
1088
+ ...(listener.receiveTimeoutMicros !== undefined ? { receiveTimeoutMicros: listener.receiveTimeoutMicros } : {}),
1065
1089
  };
1066
1090
  loopbackConnections.set(message.connectionId, accepted);
1067
- const waiter = listener.acceptWaiters.shift();
1068
- if (waiter)
1069
- waiter(accepted);
1070
- else
1071
- listener.pending.push(accepted);
1091
+ listener.pending.push(accepted);
1072
1092
  notifyDescriptors();
1073
1093
  }
1074
1094
  function postLoopback(message) {
@@ -1191,7 +1211,7 @@ export function createWaliNetworkSession(reply) {
1191
1211
  const MSG_PEEK = 2, MSG_DONTWAIT = 0x40, MSG_WAITALL = 0x100;
1192
1212
  if (maximum < 0)
1193
1213
  return scalar(-22);
1194
- if (flags & ~(MSG_PEEK | MSG_DONTWAIT | MSG_WAITALL))
1214
+ if (flags & ~(MSG_PEEK | MSG_DONTWAIT | MSG_WAITALL | (socket.kind === "dns" ? 0x20 : 0)))
1195
1215
  return scalar(-95);
1196
1216
  const waitAll = Boolean(flags & MSG_WAITALL) && (socket.kind === "tcp" || socket.kind === "local");
1197
1217
  if ((flags & MSG_WAITALL) && socket.kind !== "dns" && !waitAll)
@@ -1218,6 +1238,7 @@ export function createWaliNetworkSession(reply) {
1218
1238
  }
1219
1239
  return { result: collected, output };
1220
1240
  };
1241
+ const deadline = receiveDeadline(socket);
1221
1242
  activeIO.set(socket, (activeIO.get(socket) ?? 0) + 1);
1222
1243
  try {
1223
1244
  for (;;) {
@@ -1252,13 +1273,17 @@ export function createWaliNetworkSession(reply) {
1252
1273
  // WAITALL does not turn a nonblocking descriptor into a blocking one.
1253
1274
  if (socket.nonblocking || Boolean(flags & MSG_DONTWAIT))
1254
1275
  return partial(response);
1255
- if (signals && (signals.pending() & ~mask) !== 0n)
1256
- return partial(scalar(-4));
1257
- let delay = Infinity;
1276
+ if (monotonicMilliseconds() >= deadline)
1277
+ return partial(scalar(-11));
1278
+ if (signals && (signals.pending() & ~mask) !== 0n) {
1279
+ const response = partial(scalar(-4));
1280
+ return response.result === -4 ? { ...response, restartable: deadline === Infinity } : response;
1281
+ }
1282
+ let delay = deadline - monotonicMilliseconds();
1258
1283
  if (socket.kind === "timer" && socket.deadlineNanoseconds !== undefined) {
1259
- delay = Number(timerRemaining(socket)) / 1_000_000;
1284
+ delay = Math.min(delay, Number(timerRemaining(socket)) / 1_000_000);
1260
1285
  }
1261
- parked(socket.kind === "tcp" || socket.kind === "dns"
1286
+ parked(deadline !== Infinity || socket.kind === "tcp" || socket.kind === "dns"
1262
1287
  || (socket.kind === "timer" && socket.deadlineNanoseconds !== undefined) ? 2 : 1);
1263
1288
  // Install the wake before yielding; arrivals cannot slip between the
1264
1289
  // scan and subscription. Signals use the existing caller-mask guard.
@@ -1312,7 +1337,10 @@ export function createWaliNetworkSession(reply) {
1312
1337
  if (!response)
1313
1338
  return { result: -11, output: new Uint8Array() };
1314
1339
  const output = response.subarray(0, maximum);
1315
- return { result: output.byteLength, output };
1340
+ // Linux recv(2) fills Emscripten libsyscall's explicit truncation TODO.
1341
+ // Packet length and copied length are distinct under input MSG_TRUNC.
1342
+ return { result: flags & 0x20 ? response.byteLength : output.byteLength, output,
1343
+ messageFlags: response.byteLength > maximum ? 0x20 : 0 };
1316
1344
  }
1317
1345
  if (maximum === 0)
1318
1346
  return scalar(0);
@@ -1437,6 +1465,30 @@ export function createWaliNetworkSession(reply) {
1437
1465
  socketDescriptorFlags.set(fd, closeOnExec ? 1 : 0);
1438
1466
  return fd;
1439
1467
  }
1468
+ /** Linux socket(7) timeouts use the existing owner, not transport watchdogs. */
1469
+ function receiveDeadline(socket) {
1470
+ const timeout = socket.kind === "event" || socket.kind === "timer" ? undefined : socket.receiveTimeoutMicros;
1471
+ return timeout === undefined ? Infinity : monotonicMilliseconds() + Number(timeout) / 1000;
1472
+ }
1473
+ function setSocketOption(socket, level, name, input) {
1474
+ if (socket.kind === "event" || socket.kind === "timer")
1475
+ return -88;
1476
+ if (level !== 1 || (name !== 20 && name !== 66))
1477
+ return -92;
1478
+ if (input.byteLength < 16)
1479
+ return -22;
1480
+ const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
1481
+ const seconds = view.getBigInt64(0, true), micros = view.getBigInt64(8, true);
1482
+ if (micros < 0n || micros >= 1000000n)
1483
+ return -33; // EDOM, sock_set_timeout
1484
+ if (seconds < 0n)
1485
+ socket.receiveTimeoutMicros = 0n;
1486
+ else if (seconds === 0n && micros === 0n)
1487
+ delete socket.receiveTimeoutMicros;
1488
+ else
1489
+ socket.receiveTimeoutMicros = seconds * 1000000n + micros;
1490
+ return 0;
1491
+ }
1440
1492
  // libsyscall.js only supports SO_ERROR. Additional read-only Linux queries
1441
1493
  // below expose actual owner state, never a fabricated successful setting.
1442
1494
  function socketOption(socket, level, name) {
@@ -1444,6 +1496,13 @@ export function createWaliNetworkSession(reply) {
1444
1496
  return scalar(-88);
1445
1497
  if (level !== 1)
1446
1498
  return scalar(-92);
1499
+ if (name === 20 || name === 66) {
1500
+ const duration = socket.receiveTimeoutMicros ?? 0n;
1501
+ const output = new Uint8Array(16), view = new DataView(output.buffer);
1502
+ view.setBigInt64(0, duration / 1000000n, true);
1503
+ view.setBigInt64(8, duration % 1000000n, true);
1504
+ return { result: 0, output };
1505
+ }
1447
1506
  let value;
1448
1507
  switch (name) {
1449
1508
  case 3:
@@ -1510,8 +1569,6 @@ export function createWaliNetworkSession(reply) {
1510
1569
  if (socket.listening && socket.bound) {
1511
1570
  listenersByPort.delete(socket.bound.port);
1512
1571
  postLoopback({ type: "unlisten", port: socket.bound.port });
1513
- for (const resolve of socket.acceptWaiters.splice(0))
1514
- resolve(undefined);
1515
1572
  for (const accepted of socket.pending.splice(0))
1516
1573
  closeSocket(accepted);
1517
1574
  }