@ai-ecoverse/slicc-kernel 1.100.1 → 1.100.3
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 +11 -4
- package/dist/kernel-worker.js +23 -5
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +23 -5
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -298,6 +298,7 @@ On top of preview1 a WASI program gets:
|
|
|
298
298
|
- **Waiting:**
|
|
299
299
|
- `how` 0 blocks; 1 tries (`EAGAIN`); 2 waits until `deadline_ns` on `clock` (0 `CLOCK_REALTIME`, as `sem_timedwait`; 1 `CLOCK_MONOTONIC`, as `sem_clockwait`).
|
|
300
300
|
- A timed wait gives `ETIMEDOUT`, never early, also when the deadline has already passed. A negative deadline is `EINVAL`.
|
|
301
|
+
- A wait stopped by job control (`SIGSTOP`, `^Z`) goes on after `SIGCONT`, and a timed one still ends at its deadline: the time spent stopped counts, and the wait isn't restarted with the whole timeout.
|
|
301
302
|
- A wait blocks only its own thread, and a signal handler ends it with `EINTR`.
|
|
302
303
|
- **Posting:** `sem_post` gives the unit to the longest-waiting thread, or adds it to the value; at the maximum it's `EOVERFLOW`.
|
|
303
304
|
- **Lifetime:**
|
|
@@ -309,7 +310,7 @@ On top of preview1 a WASI program gets:
|
|
|
309
310
|
- A kernel holds at most 4,096 named ones (`ENOSPC`), and a process at most 256 open handles (`EMFILE`; a refused `O_CREAT` leaves no name behind).
|
|
310
311
|
- `MAP_SHARED` mappings aren't shared between processes, so unnamed semaphores (`sem_init` with `pshared`) work only between a process's threads, and `multiprocessing.shared_memory` doesn't work.
|
|
311
312
|
- **Foreign calls** (libffi's WASIX backend, so Python's `ctypes`): `call_dynamic` calls a function through the program's indirect function table (a static module's exported `__indirect_function_table`, or the dynamic linker's), its arguments and results packed in memory one after another (`i32`/`f32` 4 bytes, `i64`/`f64` 8). With `strict` set, the buffers must fit the function exactly (else `EINVAL`, before the function is called); without it, missing arguments are zero (one cut short keeps the bytes given, with zeros above them) and results that do not fit are left out. A trap or exception of the callee goes through to the caller. JS cannot ask a function for its type, so the kernel reads the types from the module files (the program's and each side module's, once, when a program first calls through `call_dynamic`): a table slot that is empty, past the end, holds a function of no module the kernel knows of, or takes or returns a `v128`, gives `EINVAL`. For closures (callbacks such as `CFUNCTYPE`), a dynamically linked program gets table slots from `closure_allocate`, and `closure_free` gives them back for reuse and empties the slot in every thread; a static program gets `ENOTSUP`. `closure_prepare` puts a trampoline with the requested signature (any of `i32`, `i64`, `f32`, `f64`, `v128`) into such a slot, in every thread: each call stores its arguments below the stack pointer and calls the backing function as `backing(values, results, user_data)`, then returns what it wrote to `results`. The trampolines are small wasm modules made and compiled at run time, once per signature in each thread (an instance per closure), so a page whose Content Security Policy forbids compiling WebAssembly (no `'wasm-unsafe-eval'`) cannot run them; it cannot run the program either. A `fork` or a `longjmp` (both through Asyncify) inside a function reached through `call_dynamic` cannot unwind through it.
|
|
312
|
-
- **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. An Emscripten program stops at its next kernel call as well, and at its next write, read or sleep that its glue answers without the kernel (a write to its own `/dev/null`, as in `yes > /dev/null`), so a stopped one keeps no core busy unless it computes without any of these calls. A stopped program doesn't move on while stopped: a `sleep` (or a blocked read) that would end meanwhile ends after `SIGCONT`. A signal for such a program is also delivered at those glue-answered calls, so a handler runs in a loop that only writes to `/dev/null`. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A pending signal is delivered at the program's next WASI or WASIX call, including the calls its worker answers without the kernel (`random_get`, `args_get`, …), except the exempt ones: `clock_time_get` (a tight loop of it measured up to 30% slower), `proc_exit`, `proc_exit2`, `thread_exit`, `thread_spawn_v2` and `wasi.thread-spawn` (wasi-threads), the calls that install handlers (`callback_signal`, `slicc.sigaction_set`), and calls made while Asyncify unwinds or rewinds the stack for `fork` or `longjmp`. A signal that arrives while a thread computes is thus delivered at its next call; when that call is a wait (`poll_oneoff` with a timeout or a descriptor, so `nanosleep` and `poll`/`select` too, `sigmask_poll`, or a futex wait), the handler has run during the call, so the wait ends at once: `EINTR` for a libc of -22 or later (an older one sees its clocks fire, as for any interruption), and a futex wait returns woken. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names. A sleep, or a clock in `poll_oneoff`, never ends before the time asked (it can end later, never earlier). A signal that ends a sleep or a `poll`/`select` with a timeout makes the call fail with `EINTR` in a program whose libc says it is wasix-sysroot 2025.9.30-22 or later (its `slicc.libc` custom section). Older libcs turn that answer into `ENOTSUP` from `nanosleep` (up to -18), so for a program without the section the wait ends as if it had timed out, unless it imports `fd_fdflags_set` (then `EINTR`, as before).
|
|
313
|
+
- **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. An Emscripten program stops at its next kernel call as well, and at its next write, read or sleep that its glue answers without the kernel (a write to its own `/dev/null`, as in `yes > /dev/null`), so a stopped one keeps no core busy unless it computes without any of these calls. A stopped program doesn't move on while stopped: a `sleep` (or a blocked read) that would end meanwhile ends after `SIGCONT`. A call that the stop interrupted runs again after `SIGCONT`, as on Linux. One with a timeout (`select`, `poll`, `ppoll`, `pselect`, `nanosleep`, a timed semaphore wait) waits only for the time that's left, counted from when the kernel first got the call, so it still ends at its deadline. `epoll_wait` and `epoll_pwait` aren't run again: they fail with `EINTR`, even without a handler (signal(7)). A signal for such a program is also delivered at those glue-answered calls, so a handler runs in a loop that only writes to `/dev/null`. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A pending signal is delivered at the program's next WASI or WASIX call, including the calls its worker answers without the kernel (`random_get`, `args_get`, …), except the exempt ones: `clock_time_get` (a tight loop of it measured up to 30% slower), `proc_exit`, `proc_exit2`, `thread_exit`, `thread_spawn_v2` and `wasi.thread-spawn` (wasi-threads), the calls that install handlers (`callback_signal`, `slicc.sigaction_set`), and calls made while Asyncify unwinds or rewinds the stack for `fork` or `longjmp`. A signal that arrives while a thread computes is thus delivered at its next call; when that call is a wait (`poll_oneoff` with a timeout or a descriptor, so `nanosleep` and `poll`/`select` too, `sigmask_poll`, or a futex wait), the handler has run during the call, so the wait ends at once: `EINTR` for a libc of -22 or later (an older one sees its clocks fire, as for any interruption), and a futex wait returns woken. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names. A sleep, or a clock in `poll_oneoff`, never ends before the time asked (it can end later, never earlier). A signal that ends a sleep or a `poll`/`select` with a timeout makes the call fail with `EINTR` in a program whose libc says it is wasix-sysroot 2025.9.30-22 or later (its `slicc.libc` custom section). Older libcs turn that answer into `ENOTSUP` from `nanosleep` (up to -18), so for a program without the section the wait ends as if it had timed out, unless it imports `fd_fdflags_set` (then `EINTR`, as before).
|
|
313
314
|
- **Blocked signals** (`sigprocmask`, `pthread_sigmask`, `sigpending`): a program whose libc imports `slicc.sigmask_set(how, set_lo, set_hi, old_ptr)` and `slicc.sigpending_get(ptr)` (wasix-sysroot 2025.9.30-28 and later) has a blocked mask per thread. `how` is musl's `SIG_BLOCK` 0, `SIG_UNBLOCK` 1 or `SIG_SETMASK` 2, the set is the 64-bit `sigset_t` (bit `sig - 1`), and the old mask is written as a u64 at `old_ptr`; any other `how` is `EINVAL`. `SIGKILL` and `SIGSTOP` cannot be blocked. A blocked signal is not delivered, does not interrupt a sleep or a blocked call, and its default action (terminate or stop) waits: it stays pending, `sigpending` shows it (the thread's pending signals and the process's), and it is delivered when it is unblocked, before `sigmask_set` returns. A signal ignored with `SIG_IGN` is discarded even while blocked. A forked child (with the mask of the thread that forked), the program an `exec` starts and a spawned child keep the mask, all 64 bits of it, and so does a new thread; `/proc/<pid>/status` shows the main thread's as `SigBlk`. The mask is kept in a word of the worker's shared header, so changing it costs no kernel call. A program without the import (an older libc) has no mask and gets every signal at once, as before, and one it starts by `exec` with a mask set starts with none. A signal for the whole process goes to the main thread, even if the main thread blocks it and another thread does not. A signal sent to one thread (`pthread_kill`) that it blocks waits for that thread, and if it has no handler, the thread's unblock ends the whole process by the signal, whatever the other threads block. `slicc.sigmask_poll(set_lo, set_hi, in_ptr, out_ptr, nsubscriptions, nevents_ptr)` is `pselect`, `ppoll` and `sigsuspend`: it swaps the thread's mask to the given set for the call and puts the old one back before it returns. It delivers a pending signal the new mask unblocks, without polling, or polls as `poll_oneoff` does, under the new mask, so a signal it unblocks interrupts the wait with no window between the swap and the wait; with no subscriptions it waits until a signal is delivered. An interrupted call returns `EINTR` with no events, after the handler has run.
|
|
314
315
|
- **Pipes** have no file type for WASI and WASIX programs' `fstat` (`st_mode` type 0), because WASI has no FIFO file type. A socket type, as kernels 1.47.3 to 1.48.1 gave them, made Ruby treat a `popen` pipe as a socket (`close_write` then never ends the child's input). The kernel's `slicc_fs.fd_mode` answers `S_IFIFO | 0600` (Linux numbering) for a pipe, and wasix-sysroot 2025.9.30-21 and later take the type from it, so programs built on it see a FIFO (`S_ISFIFO`); older libcs keep only the permission bits. Until then Python's asyncio refuses a pipe for a subprocess (`Pipe transport is only for pipes, sockets and character devices`). They are read and written as pipes; socket calls on them fail with `ENOTSOCK`, and `isatty()` is false. Emscripten programs, bash included, see a FIFO. The same holds for any kernel descriptor that is neither seekable nor a terminal or device, such as captured output.
|
|
315
316
|
- **Sockets**: the WASIX socket calls (`sock_open`, `sock_pair` for an `AF_UNIX` stream pair, `bind`, `listen`, `connect`, `accept`, options, local and peer names) are the kernel's loopback sockets, and preview1's `sock_recv` / `sock_send` / `sock_shutdown` work on them and on inherited ones. Writing to a socket whose peer is gone fails with `EPIPE`, through `fd_write` as through `sock_send`, as if `MSG_NOSIGNAL` were set, since a WASI program cannot ignore `SIGPIPE`; a pipe or terminal whose reader is gone still ends the program with status 141. HTTP and HTTPS leave through the realm proxy that `https_proxy` names, as for any other program; `resolve` answers `/etc/hosts` names and literal addresses, and the [uplink](#uplink)'s names. `sock_open(SOCK_DGRAM)` on `AF_INET`/`AF_INET6` gives a [datagram socket](#datagram-sockets-udp); `sock_send_to` and `sock_recv_from` carry its addresses, and `RECV_DATA_TRUNCATED` is set in `ro_flags` when a datagram did not fit.
|
|
@@ -897,7 +898,7 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
|
|
|
897
898
|
|
|
898
899
|
- **plan:** works out the version, the previous tag, the commit and the base (`base.mjs`). The base is the newest earlier release whose `cert-review` commit status is `success`, or, until there is one, the release `base.json` names (the cert lane's last line). CI never reads the npm `certified` dist-tag.
|
|
899
900
|
- **delta** (`delta.mjs`): lists the pull requests merged between the two tags (their merge commits, plus `pr` for a planted branch). Each gets its conventional-commit kind (`fix`, `feat`, `perf` and `revert` are behaviour changes) and the integration test files and titles it adds or changes. The titles are read from the pull request's patch: the added `test(…)` lines and the hunk headers git puts above changed lines. Escapes are decoded as JavaScript does, and a title git cut short, or a template literal, matches as a prefix.
|
|
900
|
-
- **suite** (`suite.mjs`, `reporter.mjs`) runs on the bench pool, without secrets. It takes the tag's tree with its own dependencies and Chromium, and replaces `dist/` with the **published npm tarball** (for a planted branch, its own `npm pack`).
|
|
901
|
+
- **suite** (`suite.mjs`, `reporter.mjs`) runs on the bench pool, without secrets. It takes the tag's tree with its own dependencies and Chromium, and replaces `dist/` with the **published npm tarball** (for a planted branch, its own `npm pack`). The test command runs in a session of its own (`detached`), and `suite.mjs` notes a `SIGINT` (`ignored` in its summary) instead of dying of it. That way a stray signal to the step's process group can't cut the run short, while a `SIGTERM` (a real cancel) still ends it.
|
|
901
902
|
- The release job runs the release's whole `test/integration/` suite, then the delta's test files again on the **previous release's dist** (the NEGATIVE).
|
|
902
903
|
- The base job runs the base release's own suite on its own published dist.
|
|
903
904
|
- Each run uses the tree's own `npm test` command, with a reporter that records every test and subtest with its status and error.
|
|
@@ -1006,16 +1007,22 @@ Two builds of one C program (`forktls.c` with `tlsvars.c`), a position-independe
|
|
|
1006
1007
|
|
|
1007
1008
|
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with homescoop's pinned `wasixcc` 0.4.7 and binaryen's `wasm-opt` 133 on `PATH` (the same toolchain rebuilds `bin/piefork.wasm` byte for byte). `bin/forktls-pie.wasm` is 816,066 bytes, sha256 `c5ed6b06036f4d3c42c660fc13ca1a995d647f11021829f2169567574254fd9e`; `bin/forktls-static.wasm` is 45,564 bytes, sha256 `5df019d5f2a4432132c8b7c6f5992d18ecf9d8e4c4be21351acd59c46c3ca48d`; `lib/libtlsside.so` is 736 bytes, sha256 `ecd32e561c051576a99e87caee5e1aca5238c4055e2e551065fa10fe7476d0b5`.
|
|
1008
1009
|
|
|
1010
|
+
### `stopwait`
|
|
1011
|
+
|
|
1012
|
+
A WASIX C program for `test/integration/stop-restart.test.mjs` (#738). `stopwait CALL` forks a child that waits 300 ms in `CALL` (`epoll`, `poll`, `ppoll`, `select`, `pselect`, `nanosleep` or `clock_nanosleep`) on a pipe that never gets data. The parent stops the child 50 ms after it's ready and continues it 200 ms later. The child prints the result, the error and whether the wait ended before the deadline, at it (300–450 ms) or late.
|
|
1013
|
+
|
|
1014
|
+
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (legacy exceptions) and binaryen's `wasm-opt` (`--asyncify` for `proc_fork`) on `PATH`. `bin/stopwait.wasm` is 50,567 bytes, sha256 `570cb3e29cfdc083bbdc0d20707f2c07b087fe23cef13a8861a4a42b02d20f5f`.
|
|
1015
|
+
|
|
1009
1016
|
### `semtest`
|
|
1010
1017
|
|
|
1011
1018
|
A WASIX C program for `test/integration/semaphore.test.mjs` (#717). It calls the `slicc.sem_*` imports directly (`import_module("slicc")`), so the rows don't wait for the libc side. `semtest MODE` runs one of:
|
|
1012
1019
|
- `handoff`, `inherit`, `spawn` (a spawned and an exec'd image open the semaphore by name; exec drops the handles);
|
|
1013
|
-
- `try`, `timed`, `eintr
|
|
1020
|
+
- `try`, `timed`, `eintr`, `stopped` (a forked child's 300 ms timed wait is stopped for 200 ms and continued, and reports whether it ended at the deadline);
|
|
1014
1021
|
- `threads` (FIFO, one thread blocked at a time);
|
|
1015
1022
|
- `errors`, `unlink`, `owners setup|user`;
|
|
1016
1023
|
- `killed` (a waiter killed before posts loses no unit), `exited`, `cap`, `handles`.
|
|
1017
1024
|
|
|
1018
|
-
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (legacy exceptions, `-pthread`) and binaryen's `wasm-opt` (version 132, `--asyncify` for `proc_fork`) on `PATH`. `bin/semtest.wasm` is 68,
|
|
1025
|
+
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (legacy exceptions, `-pthread`) and binaryen's `wasm-opt` (version 132, `--asyncify` for `proc_fork`) on `PATH`. `bin/semtest.wasm` is 68,438 bytes, sha256 `af3d38d87b63fa219bae90b1225376882ba20ace423c5ce24ef57935e80e2fde`.
|
|
1019
1026
|
|
|
1020
1027
|
### `wlocktest`
|
|
1021
1028
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -12114,7 +12114,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
|
12114
12114
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12115
12115
|
|
|
12116
12116
|
// src/version.ts
|
|
12117
|
-
var KERNEL_VERSION = true ? "1.100.
|
|
12117
|
+
var KERNEL_VERSION = true ? "1.100.3" : "0.0.0-dev";
|
|
12118
12118
|
var OS_RELEASE = "6.0.0-slicc";
|
|
12119
12119
|
function procVersion() {
|
|
12120
12120
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -14589,6 +14589,13 @@ var SEM_NAMES_MAX = 4096;
|
|
|
14589
14589
|
var SEM_HANDLES_MAX = 256;
|
|
14590
14590
|
var NAME_MAX = 251;
|
|
14591
14591
|
var TIMER_MAX_MS = 2147483647;
|
|
14592
|
+
var deadlines = /* @__PURE__ */ new WeakMap();
|
|
14593
|
+
function timeLeft(req, timeoutMs) {
|
|
14594
|
+
const now = performance.now();
|
|
14595
|
+
const deadline = deadlines.get(req) ?? now + timeoutMs;
|
|
14596
|
+
deadlines.set(req, deadline);
|
|
14597
|
+
return Math.max(0, deadline - now);
|
|
14598
|
+
}
|
|
14592
14599
|
var SEM_OPS = [
|
|
14593
14600
|
"sem-open",
|
|
14594
14601
|
"sem-close",
|
|
@@ -14775,8 +14782,9 @@ async function semSyscall(req, proc) {
|
|
|
14775
14782
|
const sem = handles.get(req.handle);
|
|
14776
14783
|
if (sem.take()) return ok2();
|
|
14777
14784
|
if (req.how === "try") throw new KernelError("EAGAIN");
|
|
14778
|
-
|
|
14779
|
-
|
|
14785
|
+
const timeoutMs = req.how === "block" ? -1 : timeLeft(req, req.timeoutMs);
|
|
14786
|
+
if (timeoutMs === 0) throw new KernelError("ETIMEDOUT");
|
|
14787
|
+
const got = await sem.wait(proc.blocking(), timeoutMs);
|
|
14780
14788
|
if (!got) throw new KernelError("ETIMEDOUT");
|
|
14781
14789
|
return ok2();
|
|
14782
14790
|
}
|
|
@@ -15799,6 +15807,10 @@ function identityKey(st, path) {
|
|
|
15799
15807
|
}
|
|
15800
15808
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
15801
15809
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
15810
|
+
function timeLeft2(req, started) {
|
|
15811
|
+
if (!("timeoutMs" in req) || typeof req.timeoutMs !== "number" || req.timeoutMs < 0) return req;
|
|
15812
|
+
return { ...req, timeoutMs: Math.max(0, req.timeoutMs - (performance.now() - started)) };
|
|
15813
|
+
}
|
|
15802
15814
|
var WasmProcess = class {
|
|
15803
15815
|
exited = false;
|
|
15804
15816
|
children;
|
|
@@ -15959,22 +15971,28 @@ var WasmProcess = class {
|
|
|
15959
15971
|
blocks = /* @__PURE__ */ new WeakMap();
|
|
15960
15972
|
async syscall(req, cancelled, block) {
|
|
15961
15973
|
this.options.onSyscall?.(req);
|
|
15974
|
+
const started = performance.now();
|
|
15975
|
+
let restarted = false;
|
|
15962
15976
|
for (; ; ) {
|
|
15963
15977
|
await this.resumed;
|
|
15964
15978
|
if (this.dying) return DYING;
|
|
15979
|
+
const current = restarted ? timeLeft2(req, started) : req;
|
|
15965
15980
|
const stops = this.stops;
|
|
15966
15981
|
const scope = new AbortController();
|
|
15967
15982
|
const forward = () => scope.abort();
|
|
15968
15983
|
if (cancelled?.aborted) scope.abort();
|
|
15969
15984
|
cancelled?.addEventListener("abort", forward, { once: true });
|
|
15970
15985
|
if (block) this.blocks.set(scope.signal, block);
|
|
15971
|
-
const result = await this.dispatch(
|
|
15986
|
+
const result = await this.dispatch(current, scope.signal).finally(() => {
|
|
15972
15987
|
cancelled?.removeEventListener("abort", forward);
|
|
15973
15988
|
this.waiting.delete(scope.signal);
|
|
15974
15989
|
});
|
|
15975
15990
|
if (this.dying) return DYING;
|
|
15976
15991
|
const restart = !result.ok && result.errno === "EINTR" && this.stops !== stops;
|
|
15977
|
-
if (restart && !this.options.hasPending?.())
|
|
15992
|
+
if (restart && !this.options.hasPending?.() && req.op !== "epoll-wait") {
|
|
15993
|
+
restarted = true;
|
|
15994
|
+
continue;
|
|
15995
|
+
}
|
|
15978
15996
|
await this.resumed;
|
|
15979
15997
|
return result;
|
|
15980
15998
|
}
|