Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
54a694d
Open py-version range + harness gate for py3.14 backends (#379)
goodboy Jun 10, 2026
4b63b7f
Handle py3.14+ incompats as test skips
goodboy Apr 17, 2026
f7dfd37
Extract `_actor_child_main()` as shared child entry
goodboy Jun 10, 2026
253b210
Add `._debug_hangs` to `.devx` for hang triage
goodboy Apr 18, 2026
fd4caa1
Arm `dump_on_hang` on `test_stale_entry_is_deleted`
goodboy Jun 10, 2026
b2cc4f5
Wall-cap `test_stale_entry_is_deleted` via `pytest-timeout`
goodboy Jun 10, 2026
6afeb6b
Skip `test_stale_entry_is_deleted` hanger with `subint`s
goodboy Apr 21, 2026
239c3fb
Add `skipon_spawn_backend` pytest marker
goodboy Apr 22, 2026
d5c549a
Mark `subint`-hanging tests with `skipon_spawn_backend`
goodboy Jun 10, 2026
21b17b6
Refactor `_runtime_vars` into pure get/set API
goodboy Jun 10, 2026
353fb82
Enable `debug_mode` for `subint_forkserver`
goodboy Apr 23, 2026
662a34b
Wire `reg_addr` through leaky cancel tests
goodboy Apr 23, 2026
d328177
Break parent-chan shield during teardown
goodboy Apr 23, 2026
6bf8364
Skip-mark `subint_forkserver` nested-multierror hang
goodboy Apr 23, 2026
af0b1fb
Bound peer-clear wait in `async_main` finally
goodboy Apr 24, 2026
f68ae75
Update `subint_forkserver` skip reason: capture-pipe
goodboy Jun 10, 2026
e70ef50
Default `pytest` to use `--capture=sys`
goodboy Jun 10, 2026
18754f2
Wire `reg_addr` through infected-asyncio tests
goodboy Apr 25, 2026
04ffde4
Skip `test_loglevel_propagated_to_subactor` on subint forkserver too
goodboy Apr 25, 2026
48ace6d
Add `_testing._reap` + auto-reap fixture
goodboy Apr 25, 2026
f633ebf
Add `tractor-reap` CLI + document auto-reap
goodboy Apr 26, 2026
50392e6
Document `SharedMemory` × `subint_forkserver` incompat
goodboy Jun 10, 2026
0b6a7aa
Fix `SharedMemory` under `subint_forkserver`
goodboy Jun 10, 2026
e5ca5bb
Add `--shm` orphan sweep to `tractor-reap`
goodboy Jun 10, 2026
d5a15b4
Bump `test_stale_entry_is_deleted`'s timeout to 30
goodboy Apr 27, 2026
a455410
Wire `test_dynamic_pub_sub` to standard fixtures
goodboy Apr 27, 2026
149e1e1
Wire `reg_addr` into `test_context_stream_semantics`
goodboy Apr 27, 2026
f7a58b8
Sweep `subint_forkserver` → `main_thread_forkserver` in code
goodboy Jun 10, 2026
d24ccaa
Fix `_testing.addr.get_rando_addr` cross-process collisions
goodboy Apr 28, 2026
ec5141b
Add opt-in `reap_subactors_per_test` fixture
goodboy Apr 28, 2026
13ccbaf
Use `trio.fail_after` cap in `test_dynamic_pub_sub`
goodboy Apr 28, 2026
3954d9f
Return parent `pid: int` from new `reap_subactors_per_test` fixture
goodboy Apr 28, 2026
c603a6e
Backend-aware timeout in `maybe_expect_raises`
goodboy Apr 29, 2026
dacd7a7
Backend-aware `fail_after` in pub/sub test
goodboy Apr 29, 2026
1d185e4
Add `--enable-stackscope` pytest plugin flag
goodboy Apr 29, 2026
c31b855
Route `stackscope` SIGUSR1 onto trio loop
goodboy Apr 29, 2026
950b6bc
Add todo for running `test_debugger` suite on forkserver spawner
goodboy Apr 29, 2026
d4eac06
Honor `TRACTOR_LOGLEVEL`+`TRACTOR_SPAWN_METHOD` env-vars
goodboy Apr 29, 2026
5d37acb
Drop test-local timeouts, +`sync_pause` to dev
goodboy Jun 10, 2026
f9e64d0
Allow per-call `start_method`/`loglevel` overrides
goodboy Apr 30, 2026
80048ff
Add UDS orphan-sweep helpers + reap fixtures to `_reap`
goodboy Apr 30, 2026
831cb6f
Add `--uds`/`--uds-only` flags to `tractor-reap`
goodboy Apr 30, 2026
af72192
Add `pytest_load_initial_conftests()` for `--capture=`
goodboy Apr 30, 2026
e1da7be
Fix `SIGUSR1` tree-dump ordering in `_stackscope`
goodboy Apr 30, 2026
158506d
Add `use_stackscope` runtime var for subactor init
goodboy May 1, 2026
99098c5
Update `sync_bp` + tighten `test_pause_from_sync`
goodboy May 1, 2026
b13d06a
Update debug examples + harden `test_debugger`
goodboy May 1, 2026
02177d9
Default `--ll` to `None` in test harness
goodboy May 1, 2026
836270e
Adjust `test_shield_pause` for capsys backends
goodboy May 1, 2026
afa14b0
Add fork-aware capture fixtures to `_testing.pytest`
goodboy May 2, 2026
8589a79
Fix `maybe_override_capture` to not get invalid capX fixture names..
goodboy May 4, 2026
a4f6d89
Add per-test runaway-subactor CPU detector to `_reap`
goodboy May 4, 2026
73c9b9e
Add `tractor.spawn._reap.unlink_uds_bind_addrs()`
goodboy May 4, 2026
98fdaf3
Add `tractor.trionics.patches` subpkg + first fix
goodboy Jun 10, 2026
797d6cf
Drop global mutation of `_PROC_SPAWN_WAIT`
goodboy May 4, 2026
272b668
Harden `test_debugger` for forkserver spawners
goodboy May 4, 2026
63c6da9
Use single f-string per pid in runaway warning
goodboy May 4, 2026
e873bb6
Replace sleep with active poll in `daemon` fixture
goodboy May 5, 2026
d8a398f
Mv `daemon` + `test_multi_program` to `discovery/`
goodboy May 6, 2026
cc5be29
Mk per-test reap fixtures opt-in
goodboy May 6, 2026
84d5283
Add `tractor_diag`(nosis) xontrib with aliases
goodboy May 6, 2026
0c64e76
Fix shutdown deadlock on UDS unlink race
goodboy May 6, 2026
a0456fe
Add `enable_transports`/`registry_addrs` proto guard
goodboy May 6, 2026
f500beb
Add `--tree` flag and cross-bucket parent annos to `pytree`
goodboy May 6, 2026
254a46c
Mk `--capture` guard CI-aware w/ local warn
goodboy May 7, 2026
4c600dc
Tidy proto-guard `ValueError` fmt in `open_root_actor()`
goodboy May 7, 2026
e9b63e8
Add `ActorTooSlowError` for cancel-cascade timeouts
goodboy May 7, 2026
9426fea
Escalate cancel-ack timeouts to `proc.terminate()`
goodboy May 7, 2026
2cd668d
Add `acli.reap`, namespace `tractor_diag` cmds
goodboy May 7, 2026
6b14c3d
Add dup-name cancel-cascade escalation test
goodboy May 8, 2026
338f0a1
Add per-actor `setproctitle` via `devx._proctitle`
goodboy May 8, 2026
3c6ea80
Add `_is_tractor_subactor()`, cgroup-aware `ptree`
goodboy May 8, 2026
5f2e89e
Harden `test_registrar` with reap fixtures, timeouts
goodboy May 12, 2026
f0b944f
Add bare-name arg, `ss` hints to `bindspace_scan`
goodboy May 12, 2026
9f9a536
Adjust legacy streaming test timeouts for fork+UDS
goodboy May 12, 2026
f5940de
Add boot-race conc-anal, widen `xfail` to `n_dups=8`
goodboy May 13, 2026
42881b3
Add ppid-aware liveness buckets to `bindspace_scan`
goodboy May 13, 2026
aced458
Fix `is_forking_spawner` fixture to call helper fn
goodboy May 13, 2026
d70e003
Add signal-alarm guard to `test_dynamic_pub_sub`
goodboy May 13, 2026
48a0f2f
Add `set_fork_aware_capture`, timeout to msg tests
goodboy May 13, 2026
105f0c2
Adjust `test_simple_context` timeout for forking spawner
goodboy May 13, 2026
75c8783
Enrich `pytestmark` in `test_inter_peer_cancellation`
goodboy May 13, 2026
acb042e
Adjust `test_streaming_to_actor_cluster` timeout
goodboy May 13, 2026
36ad568
Harden `test_infected_asyncio` for fork spawners
goodboy May 13, 2026
da2d5bb
Mv core impl `tractor_diag.xsh` to `_testing.trace`
goodboy May 13, 2026
46c1147
Add stray-proc scan + refine `_testing.trace` capture
goodboy May 13, 2026
f0a1971
Add hang-snapshot session index to pytest summary
goodboy May 13, 2026
dfe853e
Add subtree-walk to `reap()` for full actor-tree teardown
goodboy May 13, 2026
9852266
Add init-adopted orphan reap to `reap_subactors_per_test`
goodboy May 13, 2026
68698af
Harden `test_cancellation` for fork-spawner backends
goodboy May 14, 2026
c8a77fb
Use trace CM helpers in `test_dynamic_pub_sub`
goodboy May 14, 2026
fad1227
Filter `_find_tractor_strays` by ppid disposition
goodboy May 14, 2026
30242d0
Add `acli.ptree` poll .xsh snippet to docstr
goodboy May 14, 2026
da582a4
Add `acli.watch` flicker-free alias-loop
goodboy May 14, 2026
4b70b56
Use trace CM helpers in `test_infected_asyncio`
goodboy May 18, 2026
b57d095
Drop `debug_mode` gate on stackscope SIGUSR1
goodboy May 28, 2026
944262d
Add `maybe_signal_aio_task()` + cause-chain guard
goodboy May 29, 2026
8bb9df9
Lift `--ll`/`--tl` to plugin + `LogSpec` API
goodboy May 29, 2026
b6bf865
Fix `get_logger()` collapse of nested sub-pkgs
goodboy May 29, 2026
fa0208e
Bump trio depth=3 cancel timeout 6→12s
goodboy Jun 2, 2026
6a7dea4
Bump trio `echoserver` cancel timeout 1→4s
goodboy Jun 2, 2026
6d45581
Add autouse fixture to reset `_runtime_vars` per-test
goodboy Jun 16, 2026
41b5371
Relock `uv.lock` after rebase onto `main`
goodboy Jun 17, 2026
f08a7d5
Drop stray `breakpoint()` in `RuntimeVars.__setattr__`
goodboy Jun 17, 2026
d28173f
Make SIGUSR1 `stackscope` dumps actually work
goodboy Jun 17, 2026
fdac157
Harden cancel-ack hard-kill escalation
goodboy Jun 17, 2026
b0ac681
Fix dead-code env-var override notice in `open_root_actor`
goodboy Jun 17, 2026
7b518fe
Make `cpu_scaling_factor()` CI-aware for timing tests
goodboy Jun 18, 2026
c797bcb
Address Copilot review nits on PR #462
goodboy Jun 18, 2026
084f0fc
Give macOS CI extra headroom for cancel-cascade tests
goodboy Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .claude/skills/run-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,105 @@ filling log volume. Full post-mortem in
`ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md`.
Lesson codified here so future-me grep-finds the
workaround before digging.

## 10. Reaping zombie subactors (`tractor-reap`)

**Symptom:** after a `pytest` run crashes, times out,
or is `Ctrl+C`'d, subactor forks (esp. under
`subint_forkserver`) can be reparented to `init`
(PPid==1) and linger. They hold onto ports, inherit
pytest's capture-pipe fds, and flakify later
sessions.

**Two layers of defense:**

### a) Session-scoped auto-fixture (always on)

`tractor/_testing/pytest.py::_reap_orphaned_subactors`
runs at pytest session teardown. It walks `/proc` for
direct descendants of the pytest pid, SIGINTs them,
waits up to 3s, then SIGKILLs survivors. SC-polite:
gives the subactor runtime a chance to run its trio
cancel shield + IPC teardown before escalation.

This is *autouse* and session-scoped — you don't need
to do anything. It just runs.

### b) `scripts/tractor-reap` CLI (manual reap)

For the **pytest-died-mid-session** case (Ctrl+C, OOM
kill, hung process you had to `kill -9`), the fixture
never ran. Reach for the CLI:

```sh
# default: orphans (PPid==1, cwd==repo, cmd contains python)
scripts/tractor-reap

# descendant-mode: from a still-live supervisor
scripts/tractor-reap --parent <pytest-pid>

# see what would be reaped, don't signal
scripts/tractor-reap -n

# tune the SIGINT → SIGKILL grace window
scripts/tractor-reap --grace 5
```

Exit code: `0` if everyone exited on SIGINT, `1` if
SIGKILL had to escalate — so you can chain it in CI
health-checks (`scripts/tractor-reap || <alert>`).

**What it matches** (orphan-mode):
- `PPid == 1` (reparented to init → definitely
orphaned, not just a currently-running child)
- `cwd == <repo-root>` (keeps the sweep scoped; won't
touch unrelated init-children elsewhere)
- `python` in cmdline

**What it does not do:** kill anything whose PPid is
still a live tractor parent. If the parent is alive
it's not an orphan; use `--parent <pid>` if you need
to force-reap under a still-live supervisor.

**When NOT to run it:** while a pytest session is
active in another terminal. It's safe (won't touch
that session's live children in orphan-mode) but can
race if the target session is mid-teardown.

### c) `--shm` / `--shm-only`: orphan-segment sweep

Because `tractor.ipc._mp_bs.disable_mantracker()`
turns off `mp.resource_tracker` (see
`ai/conc-anal/subint_forkserver_mp_shared_memory_issue.md`),
a hard-crashing actor can leave `/dev/shm/<key>`
segments behind that nothing else GCs.

```sh
# process reap THEN shm sweep
scripts/tractor-reap --shm

# shm sweep only (skip process phase)
scripts/tractor-reap --shm-only

# dry-run: list candidates, don't unlink
scripts/tractor-reap --shm -n
```

**Match criteria** (very conservative — this is a
shared-system path, can't be wrong):
- segment is a regular file under `/dev/shm`,
- owned by the **current uid** (`stat.st_uid`),
- AND **no live process holds it open** —
enumerated by walking every readable
`/proc/<pid>/maps` (post-mmap mappings) AND
`/proc/<pid>/fd/*` (pre-mmap shm-opened fds).

The "nobody has it open" check is the
kernel-canonical "is this leaked?" test — same
answer `lsof /dev/shm/<key>` would give. No
reliance on tractor-specific naming, so it works
for any tractor app. Critically, it WILL NOT touch
segments held by other apps you have running
(e.g. `piker`, `lttng-ust-*`, `aja-shm-*` —
verified locally with 81 in-use segments correctly
preserved).
142 changes: 142 additions & 0 deletions ai/conc-anal/spawn_time_boot_death_dup_name_issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Spawn-time boot-death (`rc=2`) under rapid same-name spawn against a registrar

## Symptom

Spawning N (≥4) sub-actors with the **same name** in tight
succession against a daemon registrar surfaces as
`ActorFailure: Sub-actor (...) died during boot (rc=2)
before completing parent-handshake`.

```
tests/discovery/test_multi_program.py
::test_dup_name_cancel_cascade_escalates_to_hard_kill[n_dups=4]
```

```
tractor._exceptions.ActorFailure:
Sub-actor ('doggy', '<uuid>') died during boot (rc=2)
before completing parent-handshake.
proc: <_ForkedProc pid=<n> returncode=None>
```

The `proc` repr shows `returncode=None` because the repr is
captured before `proc.wait()` returns; the actual
`os.WEXITSTATUS == 2` is reported via `result['died']` in the
race-helper.

## When it surfaces

- N=2 (`n_dups=2`): **always passes**.
- N=4 (`n_dups=4`): **consistent fail** under both `tpt-proto=tcp`
and `tpt-proto=uds`, MTF backend.
- N=8 (`n_dups=8`): **passes** (counter-intuitive — see "racing
windows").
- Non-MTF backends: not yet exercised systematically.

## What previously masked it

Pre the spawn-time `wait_for_peer_or_proc_death` race-helper
(in `tractor.spawn._spawn`), the parent's `start_actor` flow
ended with a bare:

```python
event, chan = await ipc_server.wait_for_peer(uid)
```

That awaits an unsignalled `trio.Event` on `_peer_connected[uid]`.
If the sub-actor process **dies during boot** (before its
runtime executes the parent-callback handshake that sets the
event), the wait parks forever. The dead proc becomes a zombie
because no one ever calls `proc.wait()` to reap it.

In test contexts the failure presented as a hang or a much
later `trio.TooSlowError` from an outer `fail_after`. In
production it'd present as a parent that never makes progress
past `start_actor`. The death itself was silently masked.

## What surfaces it now

`tractor.spawn._spawn.wait_for_peer_or_proc_death` (used by
`_main_thread_forkserver_proc`) races the handshake-wait
against `proc.wait()`. The race-helper raises `ActorFailure`
on death-first instead of parking, exposing the rc=2.

## Hypothesis: registrar-side same-name contention

The test spawns N actors with name `doggy` sequentially:

```python
for i in range(n_dups):
p: Portal = await an.start_actor('doggy')
portals.append(p)
```

Each spawned doggy:

1. Forks via the forkserver.
2. Boots its runtime in `_actor_child_main`.
3. Connects back to the parent for handshake.
4. Connects to the daemon registrar to call `register_actor`.
5. Enters its RPC msg-loop.

Step (4) is where the same-name contention lives. The
registrar's `register_actor` (in
`tractor.discovery._registry`) accepts duplicate names
(stores `(name, uuid) -> addr`), but its internal bookkeeping
may have a non-trivial check (e.g. `wait_for_actor` resolution,
`_addrs2aids` map updates) that errors out under specific
ordering between the existing entry and the incoming one.

`rc=2 == os.WEXITSTATUS == 2` corresponds to `sys.exit(2)`
in the doggy process — typically reached via an unhandled
exception that's translated to exit code 2 by Python's top-
level (e.g. `argparse` errors use 2; `SystemExit(2)` etc.).
So the doggy is hitting an explicit exit path during
`register_actor` or just-after.

The non-monotonic shape (N=2 OK, N=4 BAD, N=8 OK) suggests a
specific timing window — likely "the 3rd register-RPC arrives
while the 1st-or-2nd is in some intermediate state". With
N=8, the additional procs widen the registration spread
enough that no two land in the conflicting window.

## Where to dig next

- Add per-actor logging in `_actor_child_main` and
`register_actor` to surface the actual exception that
triggers the rc=2 exit. Currently the doggy dies before
the parent ever sees its stderr (forkserver doesn't
marshal child stdio back).
- Race-test the registrar's `register_actor` /
`unregister_actor` / `wait_for_actor` against same-name
concurrent calls in isolation (no spawn).
- Consider whether `register_actor` should be idempotent
under same-name re-register or should explicitly reject
same-name (and ideally with a clear `RemoteActorError`,
not `sys.exit(2)`).

## Test-suite handling

Currently:

- `tests/discovery/test_multi_program.py
::test_dup_name_cancel_cascade_escalates_to_hard_kill[n_dups=4]`
is `pytest.mark.xfail(strict=False, reason=...)` to keep
the suite green while this issue is investigated.
- `n_dups=2` and `n_dups=8` continue to validate the
cancel-cascade hard-kill escalation.

Once the underlying race is understood + fixed, drop the
xfail.

## Related work

- The cancel-cascade fix that introduced this regression
test:
`tractor/_exceptions.py:ActorTooSlowError`,
`tractor/runtime/_supervise.py:_try_cancel_then_kill`,
`tractor/runtime/_portal.py:Portal.cancel_actor(
raise_on_timeout=...)`.
- The spawn-time death-detection that exposed this:
`tractor/spawn/_spawn.py:wait_for_peer_or_proc_death`,
used by `tractor/spawn/_main_thread_forkserver.py`.
102 changes: 102 additions & 0 deletions ai/conc-anal/trio_033_cancel_cascade_slowdown_depth3_issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# `trio` 0.29 -> 0.33 slows the depth=3 cancel-cascade

## Symptom

After locking to `trio==0.33.0` (commit `c7741bba`, was
`0.29.0`), this test reliably trips its `fail_after`
deadline on the **`trio`** backend:

```
FAILED tests/test_cancellation.py::test_nested_multierrors[start_method=trio-depth=3]
- AssertionError: assert False
where False = isinstance(
Cancelled(source='deadline', source_task=None, reason=None),
tractor.RemoteActorError,
)
```

A `fail_after_w_trace` hang-snapshot is captured for the
test each run (deadline-injected `Cancelled` wrapped into
the actor-nursery `BaseExceptionGroup`).

## Root cause (immediate)

The test budgets `fail_after(6)` for the `trio` backend.
That 6s was chosen (commit `32955db0`, while `trio==0.29`)
with the assertion that trio finishes "well under" 6s.
The `trio` 0.29 -> 0.33 bump slowed the depth=3 cascade
past that budget, so the 6s deadline now fires mid-cascade.

trio 0.33 added **cancel-reason tracking** — every
`Cancelled` now carries `(source=, reason=, source_task=)`.
The injected exc is `Cancelled(source='deadline')`, i.e.
trio itself naming our `fail_after(6)` scope as the cancel
origin. When that `Cancelled` collapses one branch of the
nursery BEG, the test's `isinstance(subexc,
RemoteActorError)` assertion fails. The healthy outcome is
`BEG = [RemoteActorError, RemoteActorError]`; the
`Cancelled` is purely an artifact of the deadline cutting
the cascade short.

## Measurements (standalone, this machine)

```
depth=1 trio ~3.15s PASS (keeps 6s budget)
depth=3 trio ~6.8-8.2s FAIL @ 6s (now bumped to 12s)
```

depth=1 still fits comfortably; only depth=3 (deeper
recursive spawn-and-error tree => more actors to reap)
exceeds the old budget. The ~2s/depth-level cost looks
like serialized per-actor reap / `terminate_after` waits.

## Mitigation applied

`test_nested_multierrors` now splits the `trio` budget:

```python
case ('trio', 1):
timeout = 6
case ('trio', 3):
timeout = 12 # was 6; see this doc
```

This stops the deadline from firing so the cascade
completes naturally to `[RAE, RAE]`.

## Also affected — same root cause, different test

`test_echoserver_detailed_mechanics[trio-raise_error=KeyboardInterrupt]`
(`tests/test_infected_asyncio.py`) tripped the *same*
slowdown via its much tighter `trio` budget of `1s`. The
single-aio-subactor teardown now takes ~1s, so the `1s`
`fail_after` raced the deadline (PASS at 0.99s / FAIL at
1.03s across back-to-back standalone runs). On a deadline-
fire the injected `Cancelled(source='deadline')` wraps the
mid-stream `KeyboardInterrupt` into a `BaseExceptionGroup`,
which is NOT a `KeyboardInterrupt` so the bare
`pytest.raises(KeyboardInterrupt)` fails. (The sibling
`raise_error=Exception` variant only "passes" by accident:
an `ExceptionGroup` *is-a* `Exception`, so its
`pytest.raises(Exception)` still matches even when wrapped.)

Mitigation: bump that `trio` budget `1 -> 4s` (matching the
forking-spawner case). Without a deadline-fire the KBI
propagates bare and the assertion passes.

## Open follow-up (the actual regression)

The budget bump is a band-aid — the underlying question is
**why** the depth=3 `trio` cancel-cascade went from <6s to
~7-8s across `trio` 0.29 -> 0.33. Candidate avenues:

- which scope owns the per-actor `terminate_after` wait,
and are the tree's reaps concurrent or serialized?
- did trio 0.33's abort/reschedule or cancel-reason
bookkeeping change checkpoint timing on the cancel path?

If/when the cascade speeds back up under-budget, depth=3
will start completing well under 12s — at which point the
budget can be tightened back toward 6s as a regression
tripwire. Related (different backend, same cascade class):
`cancel_cascade_too_slow_under_main_thread_forkserver_issue.md`.
Loading
Loading