@ai-ecoverse/slicc-kernel 1.159.2 → 1.159.4
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 +3 -2
- package/dist/kernel-worker.js +13 -10
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.js +3 -1
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +13 -10
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +1 -1
- package/dist/types/kernel/socket.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -422,6 +422,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
422
422
|
- `asyncContext` (the Node entry only; absent in a browser, so a runtime feature-detects it; #1350, slicc-jsh#375) is `{ run(store, fn, ...args), getStore(), enterWith(store) }`, backed by one host `AsyncLocalStorage` the process worker makes for each program, since a program can't import `node:async_hooks` and the realm has no promise hooks of its own. A store follows the program's native `await` continuations and promise jobs, as an `AsyncLocalStorage` does in Node, so Next.js, OpenTelemetry and request-context loggers keep their context. Each process and each thread has its own. Only these three frozen functions on a frozen null-prototype object reach the program: they hold the host storage in a closure, `run` calls the program's function with its own arguments, and nothing they return or expose is a host object.
|
|
423
423
|
- `sync.runScript(source, { filename })` (#1541, slicc-jsh#977) runs `source` as a classic script in the program's own global scope, synchronously, as Node's `vm.runInThisContext` does: `let`, `const` and `class` land in the global lexical scope, `var` (in strict code too) on the global object, and stack frames are named `filename` (default `[eval]`). On the Node entry it returns `{ value, completion: true }` with the completion value, and an error the script throws is thrown as it is. In a browser, where process workers are module workers with no synchronous classic-script path, it returns `undefined`, so a runtime feature-detects it with `ctx.sync.runScript('1') !== undefined`. A non-string `source` or `filename` is a `TypeError` everywhere. The script sees exactly the globals the program sees, `import()` inside it is refused, and a `var`, function or `let` declaration can't replace a global the program can't assign: a non-configurable one stays as it is, and the only configurable, non-assignable ones (`crypto`, `navigator`) are ones the program can already redefine with `Object.defineProperty`.
|
|
424
424
|
- `sync.promiseState(promise)` (#1531, slicc-jsh#974) tells a native promise's state synchronously, as Node's `util.inspect` does: `{ state: 'pending' | 'fulfilled' | 'rejected', value }`. `value` is the very fulfillment value or rejection reason (`undefined` while pending), and probing doesn't handle a rejection. On the Node entry it reads the promise through an inspector session the process worker opens on first use, one per process and per thread; the probe runs no program code. Anything that isn't a native promise (a thenable, a proxy) is a `TypeError`. It answers `undefined` in a browser, where a worker has no inspector, on a Node built without one, once a program has changed `Object.prototype` or put a `toJSON` on a built-in prototype, and when the global object can't take a new property (`Object.preventExtensions(globalThis)`, or a frozen global).
|
|
425
|
+
- **Kernel frames in a program's stacks** (#1583): on the Node entry, the process worker's bundle names itself `slicc-kernel:/node-process-worker.js` (a `sourceURL`), so the kernel's own frames in an error a program sees (its own errors, a failed `ctx` call, `runScript`) show that name and not the host directory the kernel is installed in. The same holds in `CallSite.toString()` and `getScriptNameOrSourceURL()`, which a program's own `Error.prepareStackTrace` gets; `getFileName()` still returns the bundle's file URL. Line and column numbers are unchanged, so they still point into `dist/node-process-worker.js`. Don't run an embedder with `--enable-source-maps` if program-visible paths matter: Node then maps the kernel's frames back to the absolute paths of its sources. In a browser the frames keep the bundle's URL, because Chrome ignores `sourceURL` in a module worker loaded by URL.
|
|
425
426
|
- `memory()`, synchronous and without a kernel call, measures the calling isolate: each `thread` answers for its own (#1240, slicc-jsh#397). It returns a fresh plain object of numbers in bytes, each field only where it's measured: `heapUsed`, `heapTotal`, `heapLimit` (V8's heap size limit), `external`, `arrayBuffers` and `rss`. On the Node entry they come from V8's heap statistics and Node's memory usage, through functions the worker captured before its lockdown, so nothing reachable from the program leads back to `process` or `v8`. `rss` is the host process's resident set size, as a Node worker thread reports it in `process.memoryUsage().rss`: it isn't per process, but shared by every process and thread of the kernel, and by any other kernel in that host process (#1563, slicc-jsh#539). In a browser `memory()` answers `{}` for now, with no `rss`.
|
|
426
427
|
- **`thread`, kernel threads of the same process** ([#1094](https://github.com/ai-ecoverse/slicc-kernel/issues/1094)), the base for Node's `worker_threads` (tinypool, piscina, vitest's and jest's thread pools, eslint's concurrency). `thread.spawn({ data, transfer })` resolves with `{ tid, port, exited, terminate() }`.
|
|
427
428
|
- **What a thread is:** a new process worker in the **same process**. It has the same pid, descriptor table, cwd, umask, credentials and lockdown, and a sync lane of its own, so `sync` calls in one thread don't hold another.
|
|
@@ -462,7 +463,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
462
463
|
- `wait(pid, { untraced })` resolves with `{ status }` once a child ends, or `{ status: 128 + signal, signal }` when a signal ended it, without blocking the program; with `untraced: true` it also resolves when the child stops, with `{ status: 128 + signal, signal, stopped: true }`, as `waitpid` with `WUNTRACED` (a child's `wait({ untraced })` is the same), and options that aren't an object are `EINVAL`; `kill(pid, signal)` signals any process, by number or as `'SIGTERM'` (the default);
|
|
463
464
|
- `sudo(args)`, what the [`sudo`](#sudo) command calls: `args` are its arguments after `sudo`, and it resolves with `{ pid }`, the command the kernel started as the program's child, to `wait` for; `{ run }`, an argv to run as the caller (with `users: 'off'`); `{ list }`, the text of `sudo -l`; or `{ error, errno }`, the message to print;
|
|
464
465
|
- `crontab({ action, user?, text? })`, what the [`crontab`](#cron) command calls: `action` is `'list'`, `'remove'` or `'install'` (with the table as `text`), on the caller's own table or, for an effective uid of 0, on `user`'s. It resolves with `{ text }` (the table for `list`, `''` otherwise) or `{ error }`, the message to print. A malformed call is `EINVAL`;
|
|
465
|
-
- `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), `close()`, and `reset()`, which closes it with an RST as Node's `resetAndDestroy()` does ([#1530](https://github.com/ai-ecoverse/slicc-kernel/issues/1530)): on the kernel's loopback, the peer
|
|
466
|
+
- `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), `close()`, and `reset()`, which closes it with an RST as Node's `resetAndDestroy()` does ([#1530](https://github.com/ai-ecoverse/slicc-kernel/issues/1530)): on the kernel's loopback, the peer first reads the bytes it has already received, as on Linux and BSD ([#1577](https://github.com/ai-ecoverse/slicc-kernel/issues/1577)), and then its reads fail with `ECONNRESET` instead of seeing the end; its writes fail with `ECONNRESET` at once. A TLS connection (`secure()`, a `tls` connect on the loopback) resets the same way. On an uplink connection `reset()` closes at once with a FIN, as the transport can't send an RST: bytes still buffered for the peer are discarded instead of sent. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). `sync.net.listen` takes the same options and gives the same listener, bound and listening when it returns, so its `port` is known at once, as Node's `listen()` needs ([#1398](https://github.com/ai-ecoverse/slicc-kernel/issues/1398)); it needs the sync lane. A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
|
|
466
467
|
- `net.connect({ host, port, tls: { servername, alpn, rejectUnauthorized, ca, path, cert, key, passphrase, sni, checkHostname, keylog } })` takes one of two paths. By default it's **A1, TLS through the transport** ([#914](https://github.com/ai-ecoverse/slicc-kernel/issues/914)), described first below. For a destination the uplink routes it can also be **A2, a TLS handshake in the kernel** ([#922](https://github.com/ai-ecoverse/slicc-kernel/issues/922)), described after it. `conn.tls.path` (`'transport'` or `'kernel'`) and `conn.tls.substitutes` say which one was taken.
|
|
467
468
|
- **What it does:** there's no handshake in the kernel. The connection carries plain HTTP/1.1 (and WebSocket upgrades) to the realm proxy's HTTP path for the origin `https://<servername or host>[:port]`, exactly as a `CONNECT` through `127.0.0.1:3128` does after the kernel terminates its TLS. The transport (the page's `fetch` or WebSocket, or the uplink's) makes the real TLS connection and verifies it. So `https.request` over a socket, keep-alive agents, `agent-base` and `wss://` clients work with the transport's semantics. That includes sealed placeholders in headers, which reach the transport exactly as from `ctx.fetch`.
|
|
468
469
|
- **The `tls` property:** the connection's `tls` is `{ path: 'transport', substitutes: true, protocol: 'transport', alpnProtocol: 'http/1.1', authorized: true, servername }`. `authorized` means the transport verified the server. There's no peer certificate chain and no TLS version of the kernel's own, so don't read it as a real handshake's result.
|
|
@@ -527,7 +528,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
527
528
|
- A connection's `tls` is `{ path: 'kernel', protocol, cipher ({ name, standardName, version }, as a client's; [#1500](https://github.com/ai-ecoverse/slicc-kernel/issues/1500)), alpnProtocol, servername (the SNI the client sent, or ''), authorized (whether a client certificate verified), authorizationError?, peerCertificates (the client's chain) }`.
|
|
528
529
|
- On a `listen({ tls })` listener, the kernel handshakes accepted connections concurrently, from the first `accept` on, and `accept` (and `for await`) yields each as its handshake finishes, so a client that connects and stays silent doesn't hold up the next. It holds at most `backlog` (up to 64) connections in a handshake or handshaken but not yet taken; beyond that it stops accepting, leaving the rest in the kernel's backlog, until one finishes or is taken. A connection whose handshake fails is closed and never yielded. `secure()` rejects instead.
|
|
529
530
|
- `handshakeTimeout` (milliseconds, 120000 by default, as Node's) ends a handshake that hasn't finished with `ERR_TLS_HANDSHAKE_TIMEOUT`. A listener closes that connection. After `secure()` rejects, the connection stays open and is the program's to answer or close, as with Node's `tlsClientError` ([#1504](https://github.com/ai-ecoverse/slicc-kernel/issues/1504)).
|
|
530
|
-
- A client that sends a plain HTTP request instead of a ClientHello fails the handshake with `ERR_SSL_HTTP_REQUEST`, as in Node. A handshake, server or loopback client, waits without holding the program's other calls, and an aborted `accept({ signal })` or `connect({ signal })` ends it. A signal delivered to the program doesn't interrupt a handshake, as with `SA_RESTART`.
|
|
531
|
+
- A client that sends a plain HTTP request instead of a ClientHello fails the handshake with `ERR_SSL_HTTP_REQUEST`, as in Node. A client that resets during the handshake fails it with `ECONNRESET` and `read ECONNRESET`, as Node's server reports it, and one that closes with `ECONNRESET` and `the client closed the connection during the TLS handshake` ([#1578](https://github.com/ai-ecoverse/slicc-kernel/issues/1578)). A handshake, server or loopback client, waits without holding the program's other calls, and an aborted `accept({ signal })` or `connect({ signal })` ends it. A signal delivered to the program doesn't interrupt a handshake, as with `SA_RESTART`.
|
|
531
532
|
- **Choosing per connection** ([#1501](https://github.com/ai-ecoverse/slicc-kernel/issues/1501), [#1502](https://github.com/ai-ecoverse/slicc-kernel/issues/1502), tls-engine 4.2.0-9's `TLS_SERVER_SELECT`), for Node's `SNICallback`, `addContext` and `ALPNCallback`: `tls.select({ servername, protocols })` on `listen` or `secure` is called once the client's ClientHello is in, with the SNI (or `''`) and the protocols the client offers. It returns (or resolves with) `{ context, alpn }` or `{ refuse }`:
|
|
532
533
|
- `context` is a certificate from `net.tlsContext({ cert, key, passphrase })` (or its `fd`); without it the listener's own `cert` serves, and with `select` that one is optional. `alpn` is one of `protocols`, or none; another is `EINVAL` and refuses the handshake. `select` and a fixed `alpn` list together are `EINVAL` (`ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS`), as in Node.
|
|
533
534
|
- `refuse` sends a fatal alert and ends the handshake: `'unrecognized_name'` (112), `'no_application_protocol'` (120), `'handshake_failure'` (40), `'access_denied'` (49) or `'internal_error'` (80), or the number. A `select` that throws refuses with 40, and one that doesn't answer within `handshakeTimeout` times the handshake out. On a listener the connection is dropped and the listener keeps accepting; `secure()` rejects with the error.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -6243,41 +6243,42 @@ var KernelSocket = class _KernelSocket {
|
|
|
6243
6243
|
}
|
|
6244
6244
|
async read(max, signal) {
|
|
6245
6245
|
const { rx } = this.connection();
|
|
6246
|
-
|
|
6246
|
+
this.drained(rx);
|
|
6247
6247
|
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
6248
6248
|
const bytes = await pipeCall(() => rx.read(max, signal));
|
|
6249
|
-
if (
|
|
6249
|
+
if (bytes.length === 0) this.drained(rx);
|
|
6250
6250
|
if (bytes.length === 0 && this.reset) throw this.reset;
|
|
6251
6251
|
return bytes;
|
|
6252
6252
|
}
|
|
6253
6253
|
async peek(max, signal) {
|
|
6254
6254
|
const { rx } = this.connection();
|
|
6255
|
-
|
|
6255
|
+
this.drained(rx);
|
|
6256
6256
|
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
6257
6257
|
const bytes = await pipeCall(() => rx.peek(max, signal));
|
|
6258
|
-
if (
|
|
6258
|
+
if (bytes.length === 0) this.drained(rx);
|
|
6259
6259
|
if (bytes.length === 0 && this.reset) throw this.reset;
|
|
6260
6260
|
return bytes;
|
|
6261
6261
|
}
|
|
6262
|
+
drained(rx) {
|
|
6263
|
+
if (this.refused && rx.buffered === 0) throw this.refused;
|
|
6264
|
+
}
|
|
6262
6265
|
rightsLink() {
|
|
6263
6266
|
const link2 = this.connection();
|
|
6264
6267
|
if (this.domain !== "unix" || this.switch || this.served) throw new KernelError("EOPNOTSUPP");
|
|
6265
|
-
if (this.refused) throw this.refused;
|
|
6266
6268
|
return link2;
|
|
6267
6269
|
}
|
|
6268
6270
|
sendRights(bytes, mark, nonblock, signal) {
|
|
6269
6271
|
const { tx } = this.rightsLink();
|
|
6272
|
+
if (this.refused) throw this.refused;
|
|
6270
6273
|
if (this.writeShut) throw new KernelError("EPIPE");
|
|
6271
6274
|
return pipeCall(() => tx.writeMarked(bytes, (at) => mark(at, tx.inFlight), nonblock, signal));
|
|
6272
6275
|
}
|
|
6273
6276
|
async receiveRights(max, nonblock, signal) {
|
|
6274
6277
|
const { rx } = this.rightsLink();
|
|
6278
|
+
this.drained(rx);
|
|
6275
6279
|
if (this.readShut || max <= 0) return { bytes: EMPTY, marks: [] };
|
|
6276
6280
|
const got = await pipeCall(() => rx.receive(max, nonblock, signal));
|
|
6277
|
-
if (this.
|
|
6278
|
-
for (const mark of got.marks) mark.drop();
|
|
6279
|
-
throw this.refused;
|
|
6280
|
-
}
|
|
6281
|
+
if (got.bytes.length === 0) this.drained(rx);
|
|
6281
6282
|
return got;
|
|
6282
6283
|
}
|
|
6283
6284
|
get secured() {
|
|
@@ -6446,6 +6447,8 @@ var KernelSocket = class _KernelSocket {
|
|
|
6446
6447
|
remoteAddr: "",
|
|
6447
6448
|
read: async () => {
|
|
6448
6449
|
const bytes = await raw2.rx.read(SERVE_CHUNK, handshaking ? signal : void 0).catch(() => EMPTY);
|
|
6450
|
+
if (bytes.length === 0 && this.refused)
|
|
6451
|
+
throw new KernelError("ECONNRESET", "read ECONNRESET");
|
|
6449
6452
|
return bytes.length > 0 ? bytes : null;
|
|
6450
6453
|
},
|
|
6451
6454
|
write: (bytes) => raw2.tx.write(bytes),
|
|
@@ -8233,7 +8236,7 @@ function satisfies(version, range2) {
|
|
|
8233
8236
|
}
|
|
8234
8237
|
|
|
8235
8238
|
// src/version.ts
|
|
8236
|
-
var KERNEL_VERSION = true ? "1.159.
|
|
8239
|
+
var KERNEL_VERSION = true ? "1.159.4" : "0.0.0-dev";
|
|
8237
8240
|
var OS_RELEASE = "6.0.0-slicc";
|
|
8238
8241
|
function procVersion() {
|
|
8239
8242
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|