Commit Graph
1194 Commits
Author SHA1 Message Date
Chris Tate 069545a126 Address review: monitorEventLoopDelay spellings and validation, Error 'in' keys, event-loop metrics in libraries
- monitorEventLoopDelay is lowered from the builtin call dispatch, by module and member, so aliased named imports, namespace members and destructured requires all reach it. Shorthand and possibly undefined resolutions take Node's default.
- The runtime validates the resolution as Node does (an integer of at least 1) and throws a catchable RangeError with ERR_OUT_OF_RANGE.
- `'stack' in err` and Object.prototype's keys are true for every Error. The declared-member fold applies only to keys some program class declares and that built-in errors don't carry in runtime slots; other keys stay fenced.
- Library mode's async_free gate refuses the event-loop metrics, which library links leave out. They join the perf_hooks fence family.
2026-10-20 02:07:46 -05:00
Malte UblandClaude Opus 5.5 c17eec0261 Compile event-loop metrics, predicate-narrowed catch bindings and other wrapper-reachable gaps
Once generic wrappers compile (#1413), the code behind them is reachable.
This closes the general gaps found there:

- performance.eventLoopUtilization() and monitorEventLoopDelay()'s
  IntervalHistogram (enable, disable, reset, percentile and the min, max,
  mean, stddev, count and exceeds readings), under the fallback
  declarations and @types/node. A new runtime unit, scr_perf_loop.c, links
  only for programs that read them (the perfLoop feature): an unref'd
  sampling interval records into HdrHistogram-shaped buckets, and
  utilization measures active time as the main thread's CPU time since
  the loop started.
- Catch bindings narrowed by a user type predicate or an assertion read
  their members through the checked-dynamic value.
- `??` with a function default of a different signature takes the
  optional callback's own function type and adapts the default.
- Optional calls on interface-typed receivers dispatch like the plain
  protocol call.
- `any` parameters of function declarations and spelled function types
  are checked-dynamic slots, and a generic instantiated at `any` takes
  `unknown` there.
- `'key' in err` on Error receivers answers from the declaring subclasses.
- Object.entries over a generic record resolves
  `keyof T extends never ? unknown : T[keyof T]` through the binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:04:07 -07:00
Chris Tate edd3874925 Run iterator helpers and destructuring on generators passed through untyped values (#1389) 2026-10-19 07:57:24 -05:00
Chris Tate 8e0af5fc59 Add getTestContext to node:test (#1393) 2026-10-19 07:37:01 -05:00
Chris Tate 098425ec78 Add an experimental young-in nursery for compiled classes and records (#1319) 2026-10-19 07:32:01 -05:00
Chris Tate e82686eab7 Add getTestContext to node:test
node:test changes, matching Node 24.22:

- getTestContext() returns the context of the test, suite or hook whose
  code is running: undefined at module top level, the root's context in
  module-level hooks, the suite's in a suite body and in the suite's
  hooks, and the test's in its body. In the afterEach and t.after hooks
  of a subtest cancelled by its parent's timeout, it returns each hook's
  owner.
- The context rides the AsyncLocalStorage snapshot under a reserved id,
  so it follows a test across its awaits, into the async functions,
  promise callbacks, timers and other callbacks it schedules, and into
  concurrent subtests, which each see their own.
- The root's fullName is "<root>", as its name is.
- Optional chains over a test context, such as getTestContext()?.name,
  lower.

AsyncLocalStorage enter and restore move into the always-linked async
core so the test runner can use them. The snapshot holds the node's
index in a table of live nodes, so a snapshot that outlives its node
answers undefined.

The get-test-context and get-test-context-cancelled fixtures compare
the contexts seen at each point with Node, plain and with SCRIPTC_SAN=1.
2026-10-19 06:29:28 -05:00
Chris Tate 6a8732f57a Run iterator helpers and destructuring on generators passed through untyped values
A compiled generator that reached an untyped value, as in
`function f(it) { return it.map(g) } f(gen())`, refused its iterator
helper calls with SC1071 at run time: the runtime's helpers could not
step a generator's capsule. Array destructuring of untyped values only
handled arrays, strings and bytes, so `const [a, b] = it` threw "is not
iterable" for generators, iterators, Maps and Sets.

Programs that box a synchronous generator and call a helper method or
destructure an untyped value now register, first thing in %main, the
checked getters that read a generator capsule's next and return methods
(scr_dyn_set_generator_members). With them:

- helper calls on a generator capsule run the runtime's helpers, which
  step and close the generator through its own next() and return();
- reads of a helper name answer the runtime's helper function, so
  `typeof it.map` is "function" and `it.reduce.call(it, f, init)` works;
- destructuring of any other iterable steps it through the iterator
  protocol (scr_dyn_iter_take, and rest elements through
  scr_dyn_iter_pack), closing an iterator that has values left.

A return() through an untyped value with no argument, or with undefined,
now completes with undefined as the typed .return() does, instead of
converting undefined to the generator's return type. This also fixes
for-of loops over untyped generators with a typed return value that exit
early.
2026-10-19 05:52:22 -05:00
Chris Tate 20a989e405 Format Dates with Node's local time zone names (#1355) 2026-10-19 04:12:27 -05:00
Chris Tate ee5008c1e5 Inspect class instances as Node does (#1382) 2026-10-19 04:03:05 -05:00
Chris Tate 5e9815759c Commit default-region memory as ProgramLifetime instances use it (#1381) 2026-10-19 03:55:18 -05:00
Chris Tate b9a54a65df Destroy an idle pooled http socket that receives data (#1380) 2026-10-19 03:43:51 -05:00
Chris Tate d823931318 Give matchAll rows and exec results their index, input and groups (#1323) 2026-10-19 03:41:07 -05:00
Chris Tate 0523806107 Inspect class instances with added properties as Node does
console.log and util.inspect of a class instance list the properties
added to it after construction, after its declared fields. JavaScript
classes print their own properties in creation order: a field first
assigned in a method or under a condition appears only once it exists.
Such instances render through their checked-dynamic view when the class
has a property bag or is a JavaScript class; other classes keep the
generated inspect helper.

The runtime inspector detects cycles through the class instances it
reaches through views, so a cycle prints [Circular *N] instead of
expanding to the depth limit. A circular target's <ref *N> prefix counts
toward the 80-column line width, as in Node.

Capsules of a recursive class type carry the class's view operations,
so a nested instance prints with its class name.
2026-10-19 03:37:40 -05:00
Chris Tate d8d48e1637 Commit default-region memory as ProgramLifetime instances use it
A thread's default region asked for huge pages from its first chunk and
gave every class a 16 KiB segment, so in a program that only references
LifetimeRegion each thread committed whole 2 MiB pages and per-class
segment space for a few instances of each class. Default regions now use
small pages, and a class's segments in a default region start at 1 KiB
and double up to 16 KiB. Freeable regions keep their page and segment
policy.
2026-10-19 03:31:48 -05:00
Chris Tate 8802765a1c Destroy an idle pooled http socket that receives data
Node 24's http.Agent installs a read guard on each socket it puts in
freeSockets: bytes that arrive while the socket is idle destroy it, so
they are never read as the response to the next request that reuses the
socket. scriptc discarded the bytes and kept the socket in the pool, so
the next request reused it (reusedSocket true, one connection) where
Node dials a new one.

The client connection's data hook now destroys the socket when it is in
an agent's free list. The socket leaves freeSockets and
totalSocketCount, and the next request through the agent opens a new
connection, as in Node.

Corpus 6200 covers an idle socket that receives a stray response.
2026-10-19 03:19:31 -05:00
Chris Tate 8e9e2136cf Merge main 2026-10-19 03:18:36 -05:00
Chris Tate 4eb3dd8def Merge main 2026-10-19 03:15:05 -05:00
Chris Tate c2b0db5778 Print iterators and iterator helper objects as Node does (#1377) 2026-10-19 03:04:50 -05:00
Chris Tate 192ea32474 Read generator tags through a pointer the generator runtime installs
scr_json.c and scr_inspect.c called scr_gen_tag, which lives in
scr_async.c. Libraries and the runtime's unit tests link those units
without scr_async.c, so their links failed on the undefined symbol.

Both now read the tag through scr_gen_tag_reader, defined in scr_json.c
and set at startup by scr_async.c. A generator capsule only exists when
the generator runtime is linked, so the pointer is always set when read.
2026-10-19 02:40:22 -05:00
Chris Tate e63d0e51c9 Wait before returning parked workers' memory, and discard one waiting arena to claim one
When the last running worker parks, the trim helper now waits
SCR_SA_DISCARD_DELAY_MS (100 by default) before it returns the parked
workers' pages, and stops if a worker starts meanwhile. A program that
exits within the delay, such as a one-shot type check whose checkers finish
just before it reports and exits, no longer returns pages during its last
milliseconds. A thread that finds every arena claimed discards one waiting
arena and claims it, instead of running every waiting discard.
2026-10-19 01:14:20 -05:00
Chris Tate 496cf577c7 Print iterators and iterator helper objects as Node does
console.log and util.inspect of a generator object, an iterator helper,
or an array or string iterator were refused, and printing a Map or Set
iterator threw "util.inspect of a dynamic Array Iterator is not
supported yet".

Compiled generators now carry the tag their object prints with:
"Generator" or "AsyncGenerator" by default, "Iterator Helper" for the
helpers, "Array Iterator" and "String Iterator" for the iterators the
compiler builds as generators. The spawn wrapper sets it through the new
scr_gen_set_tag; an adapter over another generator copies that
generator's tag (scr_gen_copy_tag), and generators that stand in for an
object of another class, such as a Set iterator adapted into an
iterator-typed slot, keep refusing to print. They print as Node's
Object [<tag>] {} at any depth, and Object.prototype.toString answers
[object <tag>].

The runtime's native iterators print as Node does: Object [Array
Iterator] {} and Object [String Iterator] {}, and for Map and Set
iterators the entries the next steps would produce, without advancing,
under [Set Iterator], [Set Entries], [Map Iterator] or [Map Entries],
as [Object [Set Iterator]] past the depth limit and {  } when none are
left. Object.prototype.toString answers [object Set Iterator] and the
other tags.
2026-10-19 01:10:39 -05:00
Chris Tate 96d3a0f5e4 Carry AsyncLocalStorage stores into timer, immediate, tick and microtask callbacks (#1365) 2026-10-19 01:01:02 -05:00
Chris Tate 1b27822d64 Merge main 2026-10-19 00:47:00 -05:00
Chris Tate 05f2dd5859 Walk Map and Set iterators inside iterator helper objects
A helper object over a Map or Set iterator that is stored and consumed
later (const it = set.values().map(f); for (const x of it) ...) stepped
the runtime's checked iterator through an adapter generator, boxing each
value and resuming two generators per element: 5.1x Node's time on a
100k-element Set.

A stage on m.keys(), m.values(), m.entries() (or a Set's), or on
Iterator.from(set) or Iterator.from(map), now walks the collection inside
the helper's own generator. The walk steps the runtime's native iterator
by entry index through the new scr_dyn_collection_iterator_index and
reads each key or value with the typed intrinsics. The native iterator
keeps the walk live and ends the collection's iteration when it is
exhausted or released, so an abandoned or returned helper leaves the
collection free to compact. Other helper sources over a Map or Set, and
Iterator.from over a Set or Map, use a typed generator over the
collection built the same way.
2026-10-19 00:03:25 -05:00
Chris Tate aef5b052b5 Sort the handle tags by name with explicit values (#1362) 2026-10-18 22:33:15 -05:00
Chris Tate 1f7f029a05 Carry AsyncLocalStorage stores into timer, immediate, tick and microtask callbacks
AsyncLocalStorage changes, matching Node 24.22:

- setTimeout, setInterval, setImmediate, process.nextTick and
  queueMicrotask callbacks run with the stores that were active when they
  were scheduled. Before, getStore() returned undefined in them.
- An interval keeps that context on every tick, and a refreshed timer
  keeps it too.
- enterWith inside one of these callbacks lasts until the callback
  returns. An enterWith in top-level code ends when the main module's body
  returns, and one inside a callback scheduled with no store active ends
  when that callback returns, so the store does not reach callbacks that
  were scheduled without it.

Programs that enter stores (store.run, exit, enterWith, or a
diagnostics_channel store binding) schedule through new *_als runtime
entry points, which wrap a callback scheduled with a store active in a
closure that installs the stores around the call. The emitter picks them
from the new asyncContext runtime feature; other programs keep the plain
entry points, so their scheduling is unchanged.

The limitations page notes that completion callbacks of callback-style
I/O APIs still run with no store.

5815-als-callback-context compares the stores each kind of callback sees
with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 22:26:09 -05:00
Chris Tate 708b1350bf Free errors that crossed into checked-dynamic code and attach Node's error properties (#1359) 2026-10-18 22:17:52 -05:00
Chris Tate b9cc0ed820 Sort the handle tags by name with explicit values
Every change that added a checked-dynamic handle kind appended its tag
just before SCR_DYNH_COUNT in scr_dyn.h, so any two such changes in
flight conflicted there. The tags are now sorted by name, each with its
current value written out, so a new tag goes in its sorted place with the
next free value, and changes adding different tags edit different lines.
SCR_DYNH_COUNT, used only by the capacity check, is gone.

The values are unchanged and the runtime artifacts are identical. A new
test checks what the enum's order used to guarantee: values are unique and
below SCR_DYNH_CAPACITY, and the LLVM backend's hardcoded tag numbers
(DYN_HANDLE_TAG_NUM) match the header.
2026-10-18 22:06:48 -05:00
Chris Tate ecff232688 Close Next.js proxy gaps: fetch header and URL-error parity, JSON undefined, and twelve compile gaps (#1341) 2026-10-18 21:54:08 -05:00
Chris Tate 96f59b0f3f Free errors that crossed into checked-dynamic code and attach Node's error properties
The error identity cache (scr_dyn_from_error) keeps one checked-dynamic
node per error so a crossing error compares reference-equal and keeps the
properties set on it. It retained both sides for the whole process, so
every error that crossed a checked-dynamic boundary (a caught error read
through a cast, an error delivered to an untyped callback, a dns failure, a
warning) stayed alive until exit: 100,000 caught AssertionErrors read
through a cast grew the resident set by about 210 MiB.

The cache now sweeps itself. An entry whose error and node are referenced
only by the cache is unreachable (a node holds no reference to its error;
a registered subclass box holds one, and a spawnSync error's `error`
property refers to its own node), so a sweep releases both sides. Sweeps
run when the cache doubles past the entries the last sweep kept, so they
cost amortized constant time per crossing. The cache is per thread, and
objects shared across threads are published (immortal), so a sweep never
races another thread's reference. Live errors and live nodes keep their
entries: identity, rethrowing the same instance, isNativeError and kind
checks are unchanged.

scr_error_set_prop sets an own property on an error's node; dns, assert,
cipher, crypto, util.callbackify and warning errors use it for the
properties they attach. util.inspect of a runtime error renders through
its node: Node's own properties follow the bracket form in key order (a
system error's errno, code, syscall and path, and the extra properties a
throw site attached), and an AssertionError renders at depth 0 with long
string actual/expected values shortened, as its custom inspect does. A
node that contains itself renders as Node's <ref *1>/[Circular *1].

child_process errors carry Node's own properties:
- execSync and execFileSync: status, signal, output, pid, stdout and
  stderr (Buffers, or strings with an encoding option); a spawn failure
  or timeout adds errno, syscall, path, spawnargs and error first.
- execFile's callback and the promisified execFile/exec: code (the exit
  status, or null after a signal), killed, signal and cmd, with stdout and
  stderr when promisified; a spawn failure has errno, code, syscall, path,
  spawnargs and cmd.
The sync forms echo the captured stderr before a timeout throw too, as
Node's inheritStderr does. The cp.execSync libCall takes a flag for Buffer
outputs.

An error's own `stack` stays non-enumerable when assigned through its
node. A worker's record-source table is freed when the worker exits.
2026-10-18 21:53:14 -05:00
Chris Tate f629d03dd8 Merge main 2026-10-18 21:28:03 -05:00
Chris Tate b9ca34887f Merge main 2026-10-18 21:24:49 -05:00
Chris Tate a7e3a9d0aa Cancel running subtests when their parent test times out (#1350) 2026-10-18 21:24:04 -05:00
Chris Tate e8e432b6dd Clean up subtests cancelled by their parent's timeout
node:test changes, matching Node 24.22:

- A subtest cancelled because its parent timed out has its mocks
  restored and its mock timers reset when it is cancelled, as Node's
  postRun resets the test's mock tracker. Before, a mocked method stayed
  mocked for the tests that ran after it.
- Its afterEach chain and t.after hooks run at the next turn, once the
  runner has moved on to the next test, as Node runs them when the
  cancelled test's own run resumes. Before, they never ran. The chain is
  copied when the test is cancelled, since its ancestors drop their hooks
  when they finish.
- Its hooks are then dropped, so closures that capture its context do not
  keep it alive. The registration parent is still not restored when the
  cancelled body settles: it belongs to the test that runs then.

The timeout-cancel-cleanup fixture records the mocked method and the hook
order against the next test's body and compares them with Node, plain and
with SCRIPTC_SAN=1. It fails without this change.
2026-10-18 20:58:20 -05:00
Chris Tate 684f73d186 Add ThreadSlots, per-thread values on shared objects, to @scriptc/experimental (#1271) 2026-10-18 20:39:11 -05:00
Chris Tate d9ac08e57c Merge main 2026-10-18 20:25:10 -05:00
Chris Tate ee9a0e1c33 Pool keep-alive sockets in http Agents and use globalAgent for default requests (#1230) 2026-10-18 20:21:54 -05:00
Chris Tate 705c197a71 Format Dates with Node's local time zone names
Date.prototype.toString, toDateString and toTimeString, String(date),
template literals and string concatenation print local time with the GMT
offset and the zone's long name, as Node does. The runtime identifies the
host zone from TZ or /etc/localtime, again after each write to
process.env.TZ, and looks its name up in a table generated from Node's
ICU data. Dates held in untyped values convert to strings with toString()
and to numbers with their time value.
2026-10-18 20:20:05 -05:00
Chris Tate 4f1a304163 Merge main 2026-10-18 20:12:11 -05:00
Chris Tate 2d37d451e9 Run iterator helpers on untyped iterators (#1352) 2026-10-18 20:07:41 -05:00
Chris Tate ada2657873 Stop creating self-capture cycles and buffer cycle candidates on the release fast path (#1320) 2026-10-18 20:03:40 -05:00
Chris Tate f761f7d75a Count idle pooled sockets in agent.totalSocketCount
agent.totalSocketCount counted only the requests an agent was serving,
so the idle sockets of a keepAlive agent in agent.freeSockets were left
out, and a second request that dialed a new socket while the first was
idle counted 1 instead of 2. As in Node, the count is now the number of
sockets the agent created that have not closed: a socket joins it when
its dial starts and leaves it when it closes, or before the request's
'close' when it does not go back to the pool. A request that takes a
free socket keeps the count.

Corpus 5593 covers reuse, a second socket, agent.destroy() and an agent
without keepAlive.
2026-10-18 19:54:32 -05:00
Chris Tate ec79cf2789 Run iterator helpers on untyped iterators
In JavaScript, a receiver without an inferred type, such as an untyped
parameter's xs.values(), is the runtime's checked iterator. Its only
method was next(), so xs.values().filter(f), .map(f) and the other
iterator helpers threw "TypeError: ... is not a function" where Node
runs them, and [].values().reduce(f) threw the same instead of Node's
"Reduce of a done iterator with no initial value".

The native array, string, Map and Set iterators now answer the
Iterator.prototype methods. map, filter, take, drop and flatMap make
helper objects (SCR_DYNH_ITERATOR_HELPER) that pull lazily and support
next(), return(), for...of and spreads, print as
"Object [Iterator Helper] {}" and tag as "[object Iterator Helper]".
toArray, forEach, reduce, some, every and find drive the iterator.

The helpers close the iterator they were made from where Node does:
when a callback throws, when take's budget is spent, on return(), and
before the error for an invalid argument. flatMap closes its inner
iterator first. An error from a close that runs because of another
error is dropped. Invalid arguments throw Node's messages, such as
"number 5 is not a function" and "x must be positive".
2026-10-18 19:42:27 -05:00
Chris Tate 3de2fb56a0 Cancel running subtests when their parent test times out
node:test changes, matching Node 24.22:

- When a test's { timeout } elapses, its subtests that are still running
  are cancelled, the innermost first. Each reports its duration so far and
  fails with "test did not finish before its parent and was cancelled",
  before the parent's result.
- A cancelled subtest's body keeps running unobserved: when it settles it
  reports nothing and leaves the runner's state alone. Before, it reported
  its result after the run's summary.

The timeout-subtests fixture compares the results, counts and failing
section with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 19:28:55 -05:00
Chris Tate 6ab49f33b2 Stringify class instances with JSON.stringify (#1338) 2026-10-18 18:59:22 -05:00
Chris Tate aa9289b9d9 Concatenate untyped Buffer lists as Node does (#1272) 2026-10-18 18:46:36 -05:00
Chris Tate 707ac5cf77 Carry Hash and Hmac values through unknown and any slots (#1257) 2026-10-18 18:38:52 -05:00
Chris Tate 1b40157fed Keep pending match metadata in the array's metadata slot
A pending match result now stores its shared match source as a dynamic
handle in ScrArrExt.metadata, next to its start index, instead of in a
separate field with its own release hook. Array teardown already
releases metadata, so scr_arr_ext_free and the publish check keep their
previous code, and a static hello world links the same array code as on
main.
2026-10-18 18:25:56 -05:00
Chris Tate cf11fbaaeb WebAssembly W0–W2: free handle tags, a cross-thread clone hook, the WebAssembly types and refusals, and a decoder that reports every module a program can load (#1339) 2026-10-18 18:12:42 -05:00
Chris Tate 4dc25a18c1 Lower iterator helpers on generators and Map and Set iterators (#1342) 2026-10-18 18:11:21 -05:00