@volter/browser-wali 0.1.191 → 0.1.193
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/dist/nanosleep.d.ts +9 -0
- package/dist/nanosleep.d.ts.map +1 -0
- package/dist/nanosleep.js +74 -0
- package/dist/nanosleep.js.map +1 -0
- package/dist/network-host.d.ts.map +1 -1
- package/dist/network-host.js +154 -53
- package/dist/network-host.js.map +1 -1
- package/dist/network-protocol.d.ts +1 -0
- package/dist/network-protocol.d.ts.map +1 -1
- package/dist/network-protocol.js +3 -0
- package/dist/network-protocol.js.map +1 -1
- package/dist/network.d.ts +3 -0
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +25 -6
- package/dist/network.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +5 -2
- package/dist/runtime.js.map +1 -1
- package/dist/signal-interruption.d.ts +1 -0
- package/dist/signal-interruption.d.ts.map +1 -1
- package/dist/signals.d.ts +5 -0
- package/dist/signals.d.ts.map +1 -1
- package/dist/signals.js +9 -0
- package/dist/signals.js.map +1 -1
- package/dist/system.d.ts +0 -1
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +6 -42
- package/dist/system.js.map +1 -1
- package/dist/thread-worker.js +6 -15
- package/dist/thread-worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"network-host.d.ts","sourceRoot":"","sources":["../src/network-host.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,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,CAs7CA"}
|
package/dist/network-host.js
CHANGED
|
@@ -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_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,12 @@ 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 restartable = true;
|
|
165
166
|
let output = new Uint8Array();
|
|
166
167
|
let checkedActivity = activity ? Atomics.load(activity, 0) : 0;
|
|
167
168
|
const parked = (state) => {
|
|
@@ -178,7 +179,9 @@ export function createWaliNetworkSession(reply) {
|
|
|
178
179
|
const inputLength = Atomics.load(control, 3);
|
|
179
180
|
const args = Array.from({ length: 10 }, (_, index) => Atomics.load(control, 5 + index));
|
|
180
181
|
const input = payload.slice(0, inputLength);
|
|
181
|
-
|
|
182
|
+
const response = await operate(operation, args, input, process, parked);
|
|
183
|
+
({ result, output } = response);
|
|
184
|
+
restartable = response.restartable !== false;
|
|
182
185
|
if ([NetworkOperation.EpollControl, NetworkOperation.Send, NetworkOperation.Receive,
|
|
183
186
|
NetworkOperation.Close, NetworkOperation.Shutdown, NetworkOperation.TimerFdSetTime].includes(operation))
|
|
184
187
|
notifyDescriptors();
|
|
@@ -202,6 +205,7 @@ export function createWaliNetworkSession(reply) {
|
|
|
202
205
|
payload.set(output);
|
|
203
206
|
Atomics.store(control, 2, result);
|
|
204
207
|
Atomics.store(control, 4, output.byteLength);
|
|
208
|
+
Atomics.store(control, NETWORK_RESPONSE_NO_RESTART, restartable ? 0 : 1);
|
|
205
209
|
Atomics.store(control, 0, requestId + 1);
|
|
206
210
|
Atomics.notify(control, 0);
|
|
207
211
|
}
|
|
@@ -293,7 +297,7 @@ export function createWaliNetworkSession(reply) {
|
|
|
293
297
|
if (operation === NetworkOperation.Listen)
|
|
294
298
|
return scalar(await listenSocket(socket, args[1]));
|
|
295
299
|
if (operation === NetworkOperation.Accept)
|
|
296
|
-
return acceptSocket(socket, args[1]);
|
|
300
|
+
return acceptSocket(socket, args[1], signalMask(args[2] ?? 0, args[3] ?? 0), parked);
|
|
297
301
|
if (operation === NetworkOperation.Send) {
|
|
298
302
|
if ((args[1] & ~(0x40 | 0x4000)) !== 0)
|
|
299
303
|
return scalar(-95);
|
|
@@ -320,8 +324,7 @@ export function createWaliNetworkSession(reply) {
|
|
|
320
324
|
if (operation === NetworkOperation.GetSocketOption)
|
|
321
325
|
return socketOption(socket, args[1], args[2]);
|
|
322
326
|
if (operation === NetworkOperation.SetSocketOption) {
|
|
323
|
-
|
|
324
|
-
return scalar(socket.kind === "event" || socket.kind === "timer" ? -88 : -92);
|
|
327
|
+
return scalar(setSocketOption(socket, args[1], args[2], input));
|
|
325
328
|
}
|
|
326
329
|
if (operation === NetworkOperation.Duplicate)
|
|
327
330
|
return scalar(duplicateSocket(socket));
|
|
@@ -392,7 +395,9 @@ export function createWaliNetworkSession(reply) {
|
|
|
392
395
|
const done = () => { if (timer !== undefined)
|
|
393
396
|
clearTimeout(timer); descriptorWaiters.delete(done); resolve(); };
|
|
394
397
|
descriptorWaiters.add(done);
|
|
395
|
-
|
|
398
|
+
// Browser timers use signed-int32 milliseconds. A long guest deadline is
|
|
399
|
+
// rechecked after each interval; this cap is not an I/O watchdog.
|
|
400
|
+
const timer = Number.isFinite(delay) ? setTimeout(done, Math.min(0x7fff_ffff, Math.max(0, delay))) : undefined;
|
|
396
401
|
});
|
|
397
402
|
}
|
|
398
403
|
function duplicateEpoll(fd, minimum, closeOnExec = false) {
|
|
@@ -715,7 +720,6 @@ export function createWaliNetworkSession(reply) {
|
|
|
715
720
|
listening: false,
|
|
716
721
|
backlog: 0,
|
|
717
722
|
pending: [],
|
|
718
|
-
acceptWaiters: [],
|
|
719
723
|
peerClosed: false,
|
|
720
724
|
};
|
|
721
725
|
}
|
|
@@ -951,32 +955,48 @@ export function createWaliNetworkSession(reply) {
|
|
|
951
955
|
listenersByPort.set(socket.bound.port, socket);
|
|
952
956
|
return 0;
|
|
953
957
|
}
|
|
954
|
-
async function acceptSocket(socket, flags) {
|
|
958
|
+
async function acceptSocket(socket, flags, mask, parked) {
|
|
955
959
|
if (socket.kind !== "tcp" || !socket.listening)
|
|
956
960
|
return scalar(-22);
|
|
957
961
|
if ((flags & ~0x8_0800) !== 0)
|
|
958
962
|
return scalar(-22);
|
|
959
|
-
|
|
960
|
-
|
|
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;
|
|
963
|
+
const deadline = receiveDeadline(socket);
|
|
964
|
+
activeIO.set(socket, (activeIO.get(socket) ?? 0) + 1);
|
|
969
965
|
try {
|
|
970
|
-
|
|
966
|
+
for (;;) {
|
|
967
|
+
if (disposed || socket.closed)
|
|
968
|
+
return scalar(-9);
|
|
969
|
+
const accepted = socket.pending.shift();
|
|
970
|
+
if (accepted) {
|
|
971
|
+
accepted.nonblocking = Boolean(flags & 0x800);
|
|
972
|
+
let fd;
|
|
973
|
+
try {
|
|
974
|
+
fd = takeFd();
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
socket.pending.unshift(accepted);
|
|
978
|
+
throw error;
|
|
979
|
+
}
|
|
980
|
+
sockets.set(fd, accepted);
|
|
981
|
+
socketDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
|
|
982
|
+
return { result: fd, output: socketAddress(accepted.domain, accepted.domain === 10 ? "::" : "0.0.0.0", 0) };
|
|
983
|
+
}
|
|
984
|
+
if (socket.nonblocking || monotonicMilliseconds() >= deadline)
|
|
985
|
+
return scalar(-11);
|
|
986
|
+
if (signals && (signals.pending() & ~mask) !== 0n)
|
|
987
|
+
return { ...scalar(-4), restartable: deadline === Infinity };
|
|
988
|
+
parked(2); // listener ingress or configured deadline can wake the call
|
|
989
|
+
try {
|
|
990
|
+
await waitForDescriptorChange(deadline - monotonicMilliseconds());
|
|
991
|
+
}
|
|
992
|
+
finally {
|
|
993
|
+
parked(0);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
971
996
|
}
|
|
972
|
-
|
|
973
|
-
socket
|
|
974
|
-
throw error;
|
|
997
|
+
finally {
|
|
998
|
+
releaseActiveIO(socket);
|
|
975
999
|
}
|
|
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
1000
|
}
|
|
981
1001
|
function requestLoopbackListen(port) {
|
|
982
1002
|
if (!loopbackPort)
|
|
@@ -1058,17 +1078,14 @@ export function createWaliNetworkSession(reply) {
|
|
|
1058
1078
|
listening: false,
|
|
1059
1079
|
backlog: 0,
|
|
1060
1080
|
pending: [],
|
|
1061
|
-
acceptWaiters: [],
|
|
1062
1081
|
loopbackConnectionId: message.connectionId,
|
|
1063
1082
|
peerClosed: false,
|
|
1064
1083
|
...(listener.bound ? { bound: listener.bound } : {}),
|
|
1084
|
+
// Clone listener options when the connection is created, not at accept.
|
|
1085
|
+
...(listener.receiveTimeoutMicros !== undefined ? { receiveTimeoutMicros: listener.receiveTimeoutMicros } : {}),
|
|
1065
1086
|
};
|
|
1066
1087
|
loopbackConnections.set(message.connectionId, accepted);
|
|
1067
|
-
|
|
1068
|
-
if (waiter)
|
|
1069
|
-
waiter(accepted);
|
|
1070
|
-
else
|
|
1071
|
-
listener.pending.push(accepted);
|
|
1088
|
+
listener.pending.push(accepted);
|
|
1072
1089
|
notifyDescriptors();
|
|
1073
1090
|
}
|
|
1074
1091
|
function postLoopback(message) {
|
|
@@ -1183,35 +1200,90 @@ export function createWaliNetworkSession(reply) {
|
|
|
1183
1200
|
}
|
|
1184
1201
|
}
|
|
1185
1202
|
}
|
|
1186
|
-
/**
|
|
1203
|
+
/** Linux recv(2) WAITALL supplements the Emscripten socket rules (L/M).
|
|
1204
|
+
* A readiness wait, not a polling loop or a transport watchdog. Only one
|
|
1205
|
+
* pinned description participates, including while an alias is closed.
|
|
1206
|
+
*/
|
|
1187
1207
|
async function receiveSocketBlocking(socket, maximum, flags, mask, parked) {
|
|
1208
|
+
const MSG_PEEK = 2, MSG_DONTWAIT = 0x40, MSG_WAITALL = 0x100;
|
|
1188
1209
|
if (maximum < 0)
|
|
1189
1210
|
return scalar(-22);
|
|
1190
|
-
|
|
1191
|
-
|
|
1211
|
+
if (flags & ~(MSG_PEEK | MSG_DONTWAIT | MSG_WAITALL))
|
|
1212
|
+
return scalar(-95);
|
|
1213
|
+
const waitAll = Boolean(flags & MSG_WAITALL) && (socket.kind === "tcp" || socket.kind === "local");
|
|
1214
|
+
if ((flags & MSG_WAITALL) && socket.kind !== "dns" && !waitAll)
|
|
1192
1215
|
return scalar(-95);
|
|
1193
|
-
|
|
1194
|
-
|
|
1216
|
+
if (waitAll && maximum > NETWORK_PAYLOAD_BYTES)
|
|
1217
|
+
return scalar(-95);
|
|
1218
|
+
const target = Math.min(maximum, NETWORK_PAYLOAD_BYTES);
|
|
1219
|
+
const peek = Boolean(flags & MSG_PEEK);
|
|
1220
|
+
const chunks = waitAll && !peek ? [] : undefined;
|
|
1221
|
+
let collected = 0;
|
|
1222
|
+
let peeked;
|
|
1223
|
+
const partial = (fallback) => {
|
|
1224
|
+
if (peeked)
|
|
1225
|
+
return peeked;
|
|
1226
|
+
if (!collected)
|
|
1227
|
+
return fallback;
|
|
1228
|
+
if (chunks.length === 1)
|
|
1229
|
+
return { result: collected, output: chunks[0] };
|
|
1230
|
+
const output = new Uint8Array(collected);
|
|
1231
|
+
let offset = 0;
|
|
1232
|
+
for (const chunk of chunks) {
|
|
1233
|
+
output.set(chunk, offset);
|
|
1234
|
+
offset += chunk.byteLength;
|
|
1235
|
+
}
|
|
1236
|
+
return { result: collected, output };
|
|
1237
|
+
};
|
|
1238
|
+
const deadline = receiveDeadline(socket);
|
|
1195
1239
|
activeIO.set(socket, (activeIO.get(socket) ?? 0) + 1);
|
|
1196
1240
|
try {
|
|
1197
1241
|
for (;;) {
|
|
1198
1242
|
if (disposed)
|
|
1199
|
-
return scalar(-9);
|
|
1200
|
-
//
|
|
1201
|
-
//
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1207
|
-
|
|
1243
|
+
return partial(scalar(-9));
|
|
1244
|
+
// Return bytes already read without clearing a later socket error.
|
|
1245
|
+
// The next receive/SO_ERROR must still be able to observe that error.
|
|
1246
|
+
if (collected > 0 && socket.kind === "tcp" && socket.error)
|
|
1247
|
+
return partial(scalar(0));
|
|
1248
|
+
const response = receiveSocket(socket, target - collected, flags);
|
|
1249
|
+
if (response.result > 0) {
|
|
1250
|
+
if (!waitAll)
|
|
1251
|
+
return response;
|
|
1252
|
+
const ended = socket.kind === "local" ? socket.closed || socket.readShutdown
|
|
1253
|
+
: socket.kind === "tcp" && (socket.closed || socket.peerClosed || socket.error !== 0);
|
|
1254
|
+
if (peek) {
|
|
1255
|
+
// Resample the queue from its beginning; never concatenate a second
|
|
1256
|
+
// copy of bytes a previous peek deliberately left queued.
|
|
1257
|
+
peeked = response;
|
|
1258
|
+
if (response.result >= target || ended)
|
|
1259
|
+
return response;
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
chunks.push(response.output);
|
|
1263
|
+
collected += response.result;
|
|
1264
|
+
if (collected >= target || ended)
|
|
1265
|
+
return partial(response);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
else if (response.result !== -11)
|
|
1269
|
+
return partial(response);
|
|
1270
|
+
// WAITALL does not turn a nonblocking descriptor into a blocking one.
|
|
1271
|
+
if (socket.nonblocking || Boolean(flags & MSG_DONTWAIT))
|
|
1272
|
+
return partial(response);
|
|
1273
|
+
if (monotonicMilliseconds() >= deadline)
|
|
1274
|
+
return partial(scalar(-11));
|
|
1275
|
+
if (signals && (signals.pending() & ~mask) !== 0n) {
|
|
1276
|
+
const response = partial(scalar(-4));
|
|
1277
|
+
return response.result === -4 ? { ...response, restartable: deadline === Infinity } : response;
|
|
1278
|
+
}
|
|
1279
|
+
let delay = deadline - monotonicMilliseconds();
|
|
1208
1280
|
if (socket.kind === "timer" && socket.deadlineNanoseconds !== undefined) {
|
|
1209
|
-
delay = Number(timerRemaining(socket)) / 1_000_000;
|
|
1281
|
+
delay = Math.min(delay, Number(timerRemaining(socket)) / 1_000_000);
|
|
1210
1282
|
}
|
|
1211
|
-
parked(socket.kind === "tcp" || socket.kind === "dns"
|
|
1283
|
+
parked(deadline !== Infinity || socket.kind === "tcp" || socket.kind === "dns"
|
|
1212
1284
|
|| (socket.kind === "timer" && socket.deadlineNanoseconds !== undefined) ? 2 : 1);
|
|
1213
|
-
//
|
|
1214
|
-
//
|
|
1285
|
+
// Install the wake before yielding; arrivals cannot slip between the
|
|
1286
|
+
// scan and subscription. Signals use the existing caller-mask guard.
|
|
1215
1287
|
try {
|
|
1216
1288
|
await waitForDescriptorChange(delay);
|
|
1217
1289
|
}
|
|
@@ -1387,6 +1459,30 @@ export function createWaliNetworkSession(reply) {
|
|
|
1387
1459
|
socketDescriptorFlags.set(fd, closeOnExec ? 1 : 0);
|
|
1388
1460
|
return fd;
|
|
1389
1461
|
}
|
|
1462
|
+
/** Linux socket(7) timeouts use the existing owner, not transport watchdogs. */
|
|
1463
|
+
function receiveDeadline(socket) {
|
|
1464
|
+
const timeout = socket.kind === "event" || socket.kind === "timer" ? undefined : socket.receiveTimeoutMicros;
|
|
1465
|
+
return timeout === undefined ? Infinity : monotonicMilliseconds() + Number(timeout) / 1000;
|
|
1466
|
+
}
|
|
1467
|
+
function setSocketOption(socket, level, name, input) {
|
|
1468
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
1469
|
+
return -88;
|
|
1470
|
+
if (level !== 1 || (name !== 20 && name !== 66))
|
|
1471
|
+
return -92;
|
|
1472
|
+
if (input.byteLength < 16)
|
|
1473
|
+
return -22;
|
|
1474
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
1475
|
+
const seconds = view.getBigInt64(0, true), micros = view.getBigInt64(8, true);
|
|
1476
|
+
if (micros < 0n || micros >= 1000000n)
|
|
1477
|
+
return -33; // EDOM, sock_set_timeout
|
|
1478
|
+
if (seconds < 0n)
|
|
1479
|
+
socket.receiveTimeoutMicros = 0n;
|
|
1480
|
+
else if (seconds === 0n && micros === 0n)
|
|
1481
|
+
delete socket.receiveTimeoutMicros;
|
|
1482
|
+
else
|
|
1483
|
+
socket.receiveTimeoutMicros = seconds * 1000000n + micros;
|
|
1484
|
+
return 0;
|
|
1485
|
+
}
|
|
1390
1486
|
// libsyscall.js only supports SO_ERROR. Additional read-only Linux queries
|
|
1391
1487
|
// below expose actual owner state, never a fabricated successful setting.
|
|
1392
1488
|
function socketOption(socket, level, name) {
|
|
@@ -1394,6 +1490,13 @@ export function createWaliNetworkSession(reply) {
|
|
|
1394
1490
|
return scalar(-88);
|
|
1395
1491
|
if (level !== 1)
|
|
1396
1492
|
return scalar(-92);
|
|
1493
|
+
if (name === 20 || name === 66) {
|
|
1494
|
+
const duration = socket.receiveTimeoutMicros ?? 0n;
|
|
1495
|
+
const output = new Uint8Array(16), view = new DataView(output.buffer);
|
|
1496
|
+
view.setBigInt64(0, duration / 1000000n, true);
|
|
1497
|
+
view.setBigInt64(8, duration % 1000000n, true);
|
|
1498
|
+
return { result: 0, output };
|
|
1499
|
+
}
|
|
1397
1500
|
let value;
|
|
1398
1501
|
switch (name) {
|
|
1399
1502
|
case 3:
|
|
@@ -1460,8 +1563,6 @@ export function createWaliNetworkSession(reply) {
|
|
|
1460
1563
|
if (socket.listening && socket.bound) {
|
|
1461
1564
|
listenersByPort.delete(socket.bound.port);
|
|
1462
1565
|
postLoopback({ type: "unlisten", port: socket.bound.port });
|
|
1463
|
-
for (const resolve of socket.acceptWaiters.splice(0))
|
|
1464
|
-
resolve(undefined);
|
|
1465
1566
|
for (const accepted of socket.pending.splice(0))
|
|
1466
1567
|
closeSocket(accepted);
|
|
1467
1568
|
}
|