Skip to content

fix(test-isolation): close the async_hooks / tui::state / timer-queue split-lock gaps - #7710

Merged
proggeramlug merged 3 commits into
mainfrom
gc/7680-split-lock-sinks
Aug 9, 2026
Merged

fix(test-isolation): close the async_hooks / tui::state / timer-queue split-lock gaps#7710
proggeramlug merged 3 commits into
mainfrom
gc/7680-split-lock-sinks

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #7680.

Background

Follow-on to #7672/#7674. #7674 converted the ~20 statics on the GC test
guards' clear list (reset_copying_nursery_runtime_test_state) to
per_test_global! and gated the class with scripts/global_sink_isolation.py.
#7680 named three more process-global sinks with the same split-lock-domain
signature, all outside that clear list, so the #7674 gate never saw them.

Per-sink verdict

1. async_hooks (HOOKS, RESOURCES, NEXT_ASYNC_ID, and five more
statics reset_for_tests() also clears).
Real hazard, same shape as the
three flakes #7674 fixed. reset_for_tests() cleared eight process-global
statics from whatever thread ran it, under any of four disjoint lock
domains
(a private module TEST_LOCK, AsyncHookRuntimeTestGuard's
private ASYNC_HOOK_RUNTIME_TEST_LOCK, the GC guards' shared lock, or —
test_async_hooks_promise_alloc_remains_malloc_tracked — no lock at all).
All eight are already reachable by scan_async_hooks_roots_mut, a scanner
registered in the same file, so scripts/gc_runtime_root_holders.py's
coverage is unaffected. Converted all eight to per_test_global!, which
removes the need for any of the four locks — deleted the now-redundant
TEST_LOCK and ASYNC_HOOK_RUNTIME_TEST_LOCK.

2. tui::state::SLOTS / STATE_DIRTY. Same shape: cleared under three
different locks (a private TEST_LOCK, gc/tests/roots.rs's
lock_safe_runtime_scanner_test_guard(), and the GC guards' shared lock).
SLOTS is already GC-scanned in the same file (scan_state_slot_roots_mut).
Converted to per_test_global!; deleted the private TEST_LOCK.
gc/tests/roots.rs still takes lock_safe_runtime_scanner_test_guard()
that lock also protects tui::hooks's still-unconverted slot pool and
scanner-registration bookkeeping, unrelated to this table.

3. agent_dispatch_tests.rs's timer-queue tests. Different shape than
the issue's framing assumed. TIMER_QUEUE / CALLBACK_TIMERS /
INTERVAL_TIMERS were already per_test_global! (#7674 counted them among
its 37). Investigating why the issue still called the "unguarded clear" a
live problem turned up something more serious than a lock-domain mismatch:
per-thread storage had silently made two of the five #6185 cross-agent
dispatch tests vacuous.
Those tests spawn a "worker" thread and assert it
can neither fire nor see a timer the "primary" thread scheduled — but under
plain per_test_global!, a freshly spawned thread gets its own empty
queue by construction, so the assertions hold no matter what
crate::agent::owns does.

Confirmed by sabotage: changed owns() to always return true (i.e.
disabled the owner-tag filtering these tests exist to cover) and re-ran —
only 2 of the 5 tests failed. The other 2 (a_worker_neither_fires_nor_eats_a_primary_agent_timer,
a_workers_gc_scan_never_visits_another_agents_timer_slots) passed anyway.

Fixed with a new escape hatch rather than reverting the per-thread storage
(which would reintroduce the #7672 hazard for the ~180-reader population
elsewhere): PerThread::shared_key() / PerThread::adopt()
(per_test_global.rs) let a test whose actual subject is cross-thread
visibility opt a deliberately-spawned worker thread into the SAME queue
instance the spawning thread already has, instead of the worker's own
isolated one. timer/test_shared_queues.rs exposes this for the three
timer statics; the three affected tests capture
test_shared_queue_keys() before spawning and call test_adopt_queues()
as the first thing the worker does. Re-ran the owns() sabotage after the
fix: 4 of 5 tests now fail (the 5th, a_worker_disowns_the_primary_agents_work,
tests owns() directly and always caught it).

Also unified the private TIMER_QUEUE_TESTS lock with
crate::gc::global_side_table_test_lock() per the issue's ask and the
plugin::REGISTRY precedent it names — the DATA hazard is already closed
by per_test_global! structurally (every libtest test runs on its own
thread), but the adoption mechanism means primary and worker genuinely
share a table for part of the test's run, so serializing against a
concurrent GC guard reset is no longer purely decorative.

Validation

  • cargo test -p perry-runtime --lib --no-fail-fast: 1946 passed, 0
    failed (one unrelated pre-existing timing test,
    promise::keyed_table::tests::settling_many_keys_is_not_quadratic,
    flaked under this host's ~70+ load average from concurrent builds by
    other agents; confirmed unrelated — untouched file, reproduces in
    isolation, passes when run alone at lower load).
  • python3 scripts/gc_runtime_root_holders.py and --self-test: clean,
    identical counts to the pre-change baseline (80 holders / 47 reached / 29
    classified) — the conversions don't change what the gate sees.
  • python3 scripts/global_sink_isolation.py and --self-test: clean, 0
    hazards, 93 statics classified, 15/15 self-test checks (these three
    sinks are outside that gate's clear-list scope by design, per the
    issue title).
  • cargo fmt --all -- --check: clean.
  • scripts/check_file_size.sh: clean (timer.rs stays at 1999 lines by
    moving the new test-only adoption helpers into a submodule rather than
    inlining them).
  • Each of the three sinks has a deterministic regression test, verified
    by reverting the corresponding fix (temporarily, then restored) to
    confirm it fails with the exact message named in code:
    • async_hooks::tests::async_hooks_state_survives_a_foreign_reset_for_tests
    • tui::state::tests::state_slots_survive_a_foreign_clear
    • agent_dispatch_tests's three timer tests, via the owns() sabotage
      described above (a repo-level A/B rather than an in-tree regression
      test, since the fix is about which thread reads which data, not a
      single reset call).

No version bump, no CLAUDE.md / CHANGELOG.md edits (external-style
change per repo convention for this kind of fix).

Summary by CodeRabbit

  • Bug Fixes

    • Improved test isolation for asynchronous hooks, runtime state, and TUI state across threads.
    • Fixed cross-thread timer and garbage-collection test behavior, including timer queue sharing and cleanup verification.
    • Prevented state resets in one test thread from affecting another thread’s state.
  • Tests

    • Added regression coverage for cross-thread timer visibility, ownership handling, and independent state lifecycles.

proggeramlug pushed a commit that referenced this pull request Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ad859e2-31f5-4e26-abde-d271b876385e

📥 Commits

Reviewing files that changed from the base of the PR and between 403a44a and 1400d5a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • CLAUDE.md
  • Cargo.toml
  • crates/perry-runtime/src/timer.rs
📝 Walkthrough

Walkthrough

The change isolates async-hook and TUI test state with per_test_global!, adds cross-thread timer-queue adoption helpers, and updates timer dispatch tests to use shared GC synchronization and queues.

Changes

Runtime test isolation

Layer / File(s) Summary
Shared per-test state primitives
crates/perry-runtime/src/per_test_global.rs, crates/perry-runtime/src/timer.rs, crates/perry-runtime/src/timer/test_shared_queues.rs, changelog.d/7710-split-lock-sinks.md
PerThread::shared_key() and adopt() support cross-thread instance sharing. Test-only helpers expose and adopt timer, callback-timer, and interval-timer queues.
Async-hook state isolation
crates/perry-runtime/src/async_hooks.rs, crates/perry-runtime/src/gc/tests/runtime_roots.rs
Async-hook state uses per-test globals. Private test locks were removed. Tests verify monotonic IDs and isolation after a foreign-thread reset.
TUI state isolation
crates/perry-runtime/src/tui/state.rs
Slots and dirty flags use per-test globals. Reset no longer returns a lock guard. Tests verify cross-thread state isolation and handle sequencing.
Shared timer queues in worker tests
crates/perry-runtime/src/agent_dispatch_tests.rs
Timer tests use the shared GC side-table lock. Worker threads adopt the primary timer queues before pumping, scanning, and retirement cleanup.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • PerryTS/perry#7445: Replaces shared process-global test-state locking with per-test-thread globals and shared queue adoption.
  • PerryTS/perry#7671: Updates test isolation and coordinates timer and GC side-table access with the shared test lock.
  • PerryTS/perry#7674: Extends per-test-thread isolation and adds shared timer-queue adoption.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main test-isolation fixes for async_hooks, tui::state, and timer queues.
Description check ✅ Passed The description explains the motivation, changes, linked issue, validation commands, and test results, but omits the repository template headings and checklist.
Linked Issues check ✅ Passed The changes satisfy issue #7680 by isolating async_hooks and TUI state, unifying timer locks, and restoring valid cross-thread timer tests.
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope and directly support test isolation, timer ownership coverage, and regression validation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gc/7680-split-lock-sinks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/per_test_global.rs`:
- Around line 123-156: Replace the raw usize contract in PerThread::shared_key
and PerThread::adopt with an opaque, type-parameterized SharedKey<T> whose
constructor is private and carries the originating table identity. Update the
per-thread slot mapping and instance lookup to use SharedKey<T>, and have adopt
validate that the key belongs to the receiving PerThread<T> before switching
mappings, rejecting zero, arbitrary addresses, and keys from other tables or
types.

In `@crates/perry-runtime/src/tui/state.rs`:
- Around line 217-256: Update state_slots_survive_a_foreign_clear to set the
primary thread’s STATE_DIRTY flag to true before spawning the clearing thread,
then assert STATE_DIRTY remains true after the foreign thread joins. Keep the
foreign thread’s false store and existing slot-survival assertions unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9878057b-4100-494f-8e5f-e7f4308b68b1

📥 Commits

Reviewing files that changed from the base of the PR and between 241bfce and 403a44a.

📒 Files selected for processing (8)
  • changelog.d/7710-split-lock-sinks.md
  • crates/perry-runtime/src/agent_dispatch_tests.rs
  • crates/perry-runtime/src/async_hooks.rs
  • crates/perry-runtime/src/gc/tests/runtime_roots.rs
  • crates/perry-runtime/src/per_test_global.rs
  • crates/perry-runtime/src/timer.rs
  • crates/perry-runtime/src/timer/test_shared_queues.rs
  • crates/perry-runtime/src/tui/state.rs

Comment on lines +123 to +156
/// This thread's instance of the table, as an opaque key a *different*
/// thread can hand to [`Self::adopt`] to observe the SAME instance
/// instead of materializing its own.
///
/// Per-thread isolation exists to keep a table out of reach of an
/// UNRELATED test's noise (#7672's own guards, running on whatever
/// libtest thread happens to construct them) — it is not meant to hide a
/// test's own deliberately-spawned worker thread from data the spawning
/// thread just wrote. A test whose actual subject is cross-thread
/// visibility of the SAME table (`agent_dispatch_tests.rs`'s #6185
/// coverage: an entry enqueued by one agent, read by a pump acting for
/// another) needs this escape hatch — without it, the per-thread split
/// makes such a test's assertions pass regardless of whether the
/// production filtering logic they exist to exercise is even still
/// there. Materializes this thread's instance if it has not touched the
/// table yet, so the key handed out is never dangling.
pub fn shared_key(&self) -> usize {
self.instance() as *const T as usize
}

/// Make this thread's future accesses to this table resolve to `key`
/// (obtained from [`Self::shared_key`] on another thread) instead of
/// materializing this thread's own instance.
///
/// Must run before this thread's first access to the table. `instance()`
/// re-reads the mapping on every call, so calling this AFTER the table
/// has already materialized on this thread does not merge the two — it
/// just switches future lookups to `key`, silently abandoning whatever
/// this thread had already written to its own (now orphaned) instance.
pub fn adopt(&self, key: usize) {
let slot = self as *const Self as usize;
let _ = SLOTS.try_with(|slots| {
slots.borrow_mut().insert(slot, key);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 '\.(shared_key|adopt)\s*\(' crates/perry-runtime
rg -n -C 5 'pub fn (shared_key|adopt)' crates/perry-runtime/src/per_test_global.rs

Repository: PerryTS/perry

Length of output: 3391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' crates/perry-runtime/src/per_test_global.rs
printf '\n--- tests/shared queue adoption ---\n'
sed -n '1,120p' crates/perry-runtime/src/timer/test_shared_queues.rs
printf '\n--- timer module exports/usages ---\n'
rg -n -C 2 'test_shared_queues|test_adopt_queues|test_shared_queue_keys|pub\(crate\) fn (TIMER_QUEUE|CALLBACK_TIMERS|INTERVAL_TIMERS)' crates/perry-runtime/src crates/perry-runtime

Repository: PerryTS/perry

Length of output: 23169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- per_writeback.rs outline ---'
ast-grep outline crates/perry-runtime/src/per_writeback.rs --match PerWriteback --view expanded || true

printf '%s\n' '--- PerWriteback usages ---'
rg -n -C 4 'pub struct PerWriteback|struct PerWriteback|PerWriteback|per_writeback!' crates/perry-runtime/src/per_writeback.rs crates/perry-runtime/src

printf '%s\n' '--- writeback tests ---'
fd -a '.*write.*|.*writeback.*' crates/perry-runtime/src | sed 's#^\./##' | head -50

Repository: PerryTS/perry

Length of output: 401


Make per-thread adoption type-safe and table-specific.

adopt accepts any usize. A caller can pass 0, an arbitrary address, or a key from PerThread<U>, and instance() then treats that value as a *const T. Define an opaque SharedKey<T> whose constructor is private, store the source table identity in the key, and reject adoption unless the key came from the receiving PerThread<T>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/per_test_global.rs` around lines 123 - 156, Replace
the raw usize contract in PerThread::shared_key and PerThread::adopt with an
opaque, type-parameterized SharedKey<T> whose constructor is private and carries
the originating table identity. Update the per-thread slot mapping and instance
lookup to use SharedKey<T>, and have adopt validate that the key belongs to the
receiving PerThread<T> before switching mappings, rejecting zero, arbitrary
addresses, and keys from other tables or types.

Comment on lines +217 to +256
/// #7680: plants the #7672 shape directly — allocate a slot on THIS
/// thread, clear `SLOTS` (what all three pre-fix lock domains eventually
/// did) on ANOTHER thread, and assert the slot survived. Revert the
/// `per_test_global!` conversion above (back to a bare `static`) and this
/// fails: a foreign thread's clear empties the slot this thread just
/// allocated, and a subsequent `js_perry_tui_state_alloc` on this thread
/// hands out handle `0` again — the exact non-sequential-handle shape
/// `alloc_returns_sequential_handles` is named for.
#[test]
fn state_slots_survive_a_foreign_clear() {
reset();
let h = js_perry_tui_state_alloc(7680.0);
assert_eq!(
js_perry_tui_state_get(h).to_bits(),
7680.0_f64.to_bits(),
"the probe installed nothing, so survived-vs-wiped would be vacuous"
);

std::thread::spawn(|| {
crate::gc::lock_gc_root_registry(&SLOTS).clear();
STATE_DIRTY.store(false, Ordering::Release);
})
.join()
.expect("the clearing thread panicked");

assert_eq!(
js_perry_tui_state_get(h).to_bits(),
7680.0_f64.to_bits(),
"a state slot written on this thread was destroyed by a foreign thread's \
clear (#7680). Per-thread storage (`per_test_global!`) is what prevents \
this."
);
let h_next = js_perry_tui_state_alloc(1.0);
assert_eq!(
h_next,
h + 1,
"this thread's slot count must not have been reset by the foreign clear"
);
reset();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert STATE_DIRTY isolation in this regression test.

The primary thread starts with STATE_DIRTY == false. The foreign thread also stores false. This test passes even if STATE_DIRTY remains process-global.

Set the flag to true on the primary thread before spawning the clearer. Assert that it remains true after the foreign clear.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/tui/state.rs` around lines 217 - 256, Update
state_slots_survive_a_foreign_clear to set the primary thread’s STATE_DIRTY flag
to true before spawning the clearing thread, then assert STATE_DIRTY remains
true after the foreign thread joins. Keep the foreign thread’s false store and
existing slot-survival assertions unchanged.

Ralph Küpper added 3 commits August 9, 2026 17:45
… split-lock gaps (#7680)

Follow-on to #7672/#7674: three more process-global sinks had the same
split-lock-domain signature outside the GC guards' clear list, so #7674's
gate never saw them.

async_hooks (NEXT_ASYNC_ID, HOOKS_ACTIVE, HOOKS, RESOURCES,
GC_DESTROY_QUEUE, NEXT_CONTEXT_SNAPSHOT_ID, CONTEXT_SNAPSHOTS,
ASYNC_WRAP_PROVIDERS) and tui::state (SLOTS, STATE_DIRTY) sat behind four
and three disjoint test lock domains respectively, with reads that took
none of them. Both are converted to per_test_global!, which removes the
need for any lock at all; the now-redundant private locks (this module's
own async_hooks TEST_LOCK, gc/tests/runtime_roots.rs's
ASYNC_HOOK_RUNTIME_TEST_LOCK, tui/state.rs's TEST_LOCK) are deleted.

agent_dispatch_tests.rs is a different shape: its timer queues were
already per_test_global (#7674), so its private TIMER_QUEUE_TESTS lock is
unified with the GC guards' shared crate::gc::global_side_table_test_lock()
per the issue's ask, but the more serious finding is that per-thread
storage had silently made two of its five #6185 cross-agent tests
vacuous — a spawned "worker" thread got its own empty queue by
construction, so the tests passed whether or not crate::agent::owns
filtering still worked. Confirmed by sabotaging owns() to always return
true: only 2 of 5 tests caught it before this fix, 4 of 5 after. Fixed by
adding PerThread::shared_key/adopt (per_test_global.rs) so a test whose
actual subject is cross-thread visibility can opt a spawned thread into
the SAME queue instance instead of its own isolated one.

Each conversion carries a deterministic regression test (write on one
thread, clear/read on another) verified to fail against the pre-fix
shape.
@proggeramlug
proggeramlug force-pushed the gc/7680-split-lock-sinks branch from 403a44a to 1400d5a Compare August 9, 2026 15:45
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Merging as v0.5.1411

The best thing in this PR is not in the issue.

The third sink was already fixed, and investigating it found something worse. TIMER_QUEUE/CALLBACK_TIMERS/INTERVAL_TIMERS had been per_test_global! since #7674 — the literal ask was stale. But per-thread storage had silently made 2 of 5 #6185 cross-agent dispatch tests vacuous: a spawned "worker" thread got its own empty queue by construction, so the assertions passed regardless of whether crate::agent::owns filtering worked at all.

Proved by sabotage rather than argued: forcing owns() to always return true was caught by 2 of 5 tests before the fix and 4 of 5 after. That is a test-isolation mechanism defeating the tests it was added to protect — the same shape as the gates this project keeps finding, one level down.

The fix is right too: rather than reverting the isolation, PerThread::shared_key()/adopt() lets these specific tests opt a spawned worker into the same queue instance, so the isolation stays for everyone else.

The two real sinks

  • async_hooks — eight globals (HOOKS, RESOURCES, NEXT_ASYNC_ID, HOOKS_ACTIVE, GC_DESTROY_QUEUE, NEXT_CONTEXT_SNAPSHOT_ID, CONTEXT_SNAPSHOTS, ASYNC_WRAP_PROVIDERS) cleared by reset_for_tests() under four disjoint lock domains, one of which was no lock at all (gc/tests/alloc.rs:836). All eight are already GC-scanned by scan_async_hooks_roots_mut, so holder coverage is unaffected — this is isolation, not a missing root.
  • tui::stateSLOTS/STATE_DIRTY, same shape, three domains.

Both got a failing-without-fix demonstration (async_hooks_state_survives_a_foreign_reset_for_tests, state_slots_survive_a_foreign_clear), each verified by reverting the conversion and reproducing the named failure.

Two now-redundant private locks deleted. lock_safe_runtime_scanner_test_guard() correctly kept — it also protects tui::hooks's still-unconverted pool.

Gates

cargo test -p perry-runtime --lib --no-fail-fast: 1947 passed, 0 failed. gc_runtime_root_holders.py + self-test clean at 80/47/29. global_sink_isolation.py + self-test clean, 15/15. Lint 19/19. timer.rs held at 1,999 lines against the 2,000 cap via a submodule — worth noting that file has no headroom left for the next change.

@proggeramlug
proggeramlug merged commit 5b75176 into main Aug 9, 2026
11 of 13 checks passed
@proggeramlug
proggeramlug deleted the gc/7680-split-lock-sinks branch August 9, 2026 15:50
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.

Three more process-global sinks with split lock domains, outside the GC guards' clear list (async_hooks, tui::state, agent_dispatch timer queues)

1 participant