Skip to content

Disable Atomics agent suspension by default - #3058

Merged
lahma merged 1 commit into
sebros/default-stack-guardfrom
sebros/default-atomics-suspension-off
Aug 21, 2026
Merged

lahma merged 1 commit into
sebros/default-stack-guardfrom
sebros/default-atomics-suspension-off

Conversation

@sebastienros

Copy link
Copy Markdown
Owner

Summary

  • disable synchronous Atomics.wait suspension by default and preserve an explicit AgentCanSuspend = true compatibility opt-in
  • keep Atomics.waitAsync available and preserve blocking Test262 worker agents while honoring CanBlockIsFalse
  • document the breaking security default and server-thread denial guidance in the README and threat model
  • retain waiter cleanup and GC coverage, including rejected waits, timeout/notify races, repeated waits, and worker harness behavior

Security behavior

A default engine now rejects Atomics.wait with a sanitized JavaScript TypeError before registering a waiter. Worker-like hosts may explicitly opt in, accepting that an indefinite synchronous wait cannot be preempted safely in process. Atomics.waitAsync remains nonblocking and unaffected.

The change was transplanted from the previously reviewed Atomics-only commit series without reimplementation. It applied conflict-free over the live sebros/default-stack-guard tip, preserving all prior security layers.

Validation

  • Release multi-target Jint/Jint.csproj build: passed (0 warnings/errors)
  • focused Atomics/waiter/Test262-agent tests: 12 passed
  • full Jint.Tests under UTC: 5,800 passed, 4 skipped
  • full Jint.Tests.PublicInterface under UTC: 1,737 passed, 9 skipped
  • full Jint.Tests.PublicInterface with host-contract verification under UTC: 1,741 passed, 5 skipped
  • git diff --check: passed
  • security specialist review against origin/sebros/default-stack-guard: clean, no confirmed findings

The mandated NuGet proxy does not contain Meziantou.Analyzer 3.0.156 (nearest 3.0.141), so validation used a temporary analyzer-only downgrade to 3.0.141; Directory.Packages.props was restored exactly before publishing. The proxy also does not contain Test262Harness.Console 1.1.2, so the Test262 Atomics selection could not generate; the in-repo worker-agent harness paths were validated by the focused and full core suites.

Stack

#3057 -> #3056 -> #3054 -> #3052 -> #3051 -> #3046 -> #3045 -> #3037 -> #3036 -> #3035 -> #3030

@lahma lahma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed as the incremental diff against sebros/default-stack-guard. Straightforward and correct: flipping AgentCanSuspend to false makes a default engine behave like a browser main thread — Atomics.wait throws TypeError before a waiter is ever registered (per the spec's AgentCanSuspend check), Atomics.waitAsync is untouched, and the worker-like opt-in is one property. The Test262 harness change is right (worker agents suspend, CanBlockIsFalse tests don't, and spawned $262.agent engines opt in explicitly), and the new default-rejection test is unusually careful — the notify-loop cleanup path that prevents a regressed implementation from stranding an indefinite background waiter in the test process is a nice piece of defensive test engineering. Approving.

One note: breaking-default number five — same major-release migration-story point as the earlier flips; the README migration note added here is exactly the right shape for the combined release notes.

@lahma
lahma force-pushed the sebros/default-atomics-suspension-off branch from 6bf436a to c57c57b Compare August 21, 2026 19:07
@lahma
lahma merged commit cee3dae into main Aug 21, 2026
14 of 20 checks passed
@lahma
lahma deleted the sebros/default-atomics-suspension-off branch August 21, 2026 19:59
lahma added a commit to lahma/jint that referenced this pull request Aug 23, 2026
…lly has

Main has moved 181 commits past v4.16.0 and nothing records what an embedder
has to react to. Most of it is invisible to a compiler: every entry below still
compiles exactly as it did in 4.16 and behaves differently at run time, and six
of them flip a default.

`docs/v5-migration.md` is the artefact those entries go in. It is deliberately
not a second README: a table per change, before/after code where it helps, and
the rationale left in the pull request it cites.

Seeded from git history, every claim checked against the tree rather than
against the pull request title:

  * sebastienros#3054 `Interop.AllowWrite` true -> false. A projected CLR write is now
    silently ignored in sloppy mode and a TypeError in strict mode.
  * sebastienros#3056 `Interop.ArrayConversion` LiveView -> Copy. `Array.isArray` flips to
    true, `push`/`length` stop throwing, and CLR-side mutations after the
    crossing stop being visible.
  * sebastienros#3057 `Constraints.StackOverflowGuard` false -> true. RangeError instead of
    a process that ends with no exception in the log.
  * sebastienros#3058 `AgentCanSuspend` true -> false. `Atomics.wait` is a TypeError before
    a waiter is registered; `waitAsync` is untouched.
  * sebastienros#3052 namespace type discovery loses its implicit fallback to
    `Assembly.GetCallingAssembly()` / `GetExecutingAssembly()` /
    `Type.GetType(name)` and becomes the `AllowedAssemblies` allow-list.
  * sebastienros#3051 host exception, module-load and CLR-resolution messages are redacted
    from script; `ExposeDetailedErrors()` restores all three.
  * sebastienros#3035 concurrent `Engine` use fails fast, and an engine stays reserved for
    the lifetime of a returned async Task.
  * sebastienros#3036 `LimitMemory` charges allocations across async continuations, so a
    budget can now trip where one synchronous segment never reached it.
  * sebastienros#3252 every `*Async` entry reports the operation's failures through the
    task and only a usage error out of the call.
  * sebastienros#3248 an array-like `length` above 2^32-1 stops answering differently per
    target framework.
  * sebastienros#3037, sebastienros#3045, sebastienros#3046 add parser, module-graph and result bounds that all
    default to unlimited, so they change nothing until configured; sebastienros#3059 and
    sebastienros#3060 add the diagnostics and the hardened profile on top.

Two entries the prompt for this work had slightly differently, both checked and
written as the tree has them: the stack-overflow guard raises a catchable
`RangeError`, not a `RecursionDepthOverflowException`, and `ArrayOperations` is
internal, so sebastienros#3248 removes no public member — its break is what a script sees.

Sections 2, 3 and 6 (removed API, renamed API, AOT) are explicit empty
placeholders. Nothing public has been removed or renamed since v4.16.0, and a
parallel task is measuring the AOT state.

The target-framework section is written and marked pending: `Jint.csproj` still
lists net462, and the net472 change is not on main yet.

`Jint/AGENTS.md` gains the rule that makes the guide stay current — a change to
anything in its public-contract table is a row in the guide, in the same pull
request, including a change that breaks nothing at compile time. The root
`AGENTS.md` is untouched; it is at 23 KB against a 24 KiB budget.

README's "Branches and releases" described `main` alone and did not mention the
`3.x` branch at all. It now has a row per live branch, in the same wording
sebastienros#3291 gives the 4.x branch's own copy, plus a pointer to the guide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S
lahma added a commit that referenced this pull request Aug 23, 2026
…lly has (#3294)

Main has moved 181 commits past v4.16.0 and nothing records what an embedder
has to react to. Most of it is invisible to a compiler: every entry below still
compiles exactly as it did in 4.16 and behaves differently at run time, and six
of them flip a default.

`docs/v5-migration.md` is the artefact those entries go in. It is deliberately
not a second README: a table per change, before/after code where it helps, and
the rationale left in the pull request it cites.

Seeded from git history, every claim checked against the tree rather than
against the pull request title:

  * #3054 `Interop.AllowWrite` true -> false. A projected CLR write is now
    silently ignored in sloppy mode and a TypeError in strict mode.
  * #3056 `Interop.ArrayConversion` LiveView -> Copy. `Array.isArray` flips to
    true, `push`/`length` stop throwing, and CLR-side mutations after the
    crossing stop being visible.
  * #3057 `Constraints.StackOverflowGuard` false -> true. RangeError instead of
    a process that ends with no exception in the log.
  * #3058 `AgentCanSuspend` true -> false. `Atomics.wait` is a TypeError before
    a waiter is registered; `waitAsync` is untouched.
  * #3052 namespace type discovery loses its implicit fallback to
    `Assembly.GetCallingAssembly()` / `GetExecutingAssembly()` /
    `Type.GetType(name)` and becomes the `AllowedAssemblies` allow-list.
  * #3051 host exception, module-load and CLR-resolution messages are redacted
    from script; `ExposeDetailedErrors()` restores all three.
  * #3035 concurrent `Engine` use fails fast, and an engine stays reserved for
    the lifetime of a returned async Task.
  * #3036 `LimitMemory` charges allocations across async continuations, so a
    budget can now trip where one synchronous segment never reached it.
  * #3252 every `*Async` entry reports the operation's failures through the
    task and only a usage error out of the call.
  * #3248 an array-like `length` above 2^32-1 stops answering differently per
    target framework.
  * #3037, #3045, #3046 add parser, module-graph and result bounds that all
    default to unlimited, so they change nothing until configured; #3059 and
    #3060 add the diagnostics and the hardened profile on top.

Two entries the prompt for this work had slightly differently, both checked and
written as the tree has them: the stack-overflow guard raises a catchable
`RangeError`, not a `RecursionDepthOverflowException`, and `ArrayOperations` is
internal, so #3248 removes no public member — its break is what a script sees.

Sections 2, 3 and 6 (removed API, renamed API, AOT) are explicit empty
placeholders. Nothing public has been removed or renamed since v4.16.0, and a
parallel task is measuring the AOT state.

The target-framework section is written and marked pending: `Jint.csproj` still
lists net462, and the net472 change is not on main yet.

`Jint/AGENTS.md` gains the rule that makes the guide stay current — a change to
anything in its public-contract table is a row in the guide, in the same pull
request, including a change that breaks nothing at compile time. The root
`AGENTS.md` is untouched; it is at 23 KB against a 24 KiB budget.

README's "Branches and releases" described `main` alone and did not mention the
`3.x` branch at all. It now has a row per live branch, in the same wording
#3291 gives the 4.x branch's own copy, plus a pointer to the guide.


Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants