feat(directives): scavenge sweep 4/5 (tracks + commands + styleguides + todos): 18 batch-4 directives + concurrent worker batches

This commit is contained in:
ed
2026-07-04 02:00:42 -04:00
parent e8d3578f2e
commit 79124774ec
82 changed files with 2430 additions and 0 deletions
@@ -0,0 +1,10 @@
# submit_io_lazy_pool_recreation
## v1
**Why this iteration:** Lifted from `conductor/todos/TODO_test_full_live_workflow_v2.md` §"Task 5" — `submit_io` must recover from a shut-down pool by recreating it lazily. Defense in depth: if the GUI crashes and shuts down the pool, the test can still submit work after the `immapp.run` wrap (Task 3) catches the exception. Without this, the controller is permanently dead.
**Source:** `conductor/todos/TODO_test_full_live_workflow_v2.md` §"Task 5" (LOW priority; ~30 min)
---
**Lifted:** 2026-07-03 scavenge sweep batch 4/5: tracks + commands + styleguides + todos
@@ -0,0 +1,51 @@
# `submit_io` MUST lazily recreate the thread pool if it has been shut down — do NOT raise `RuntimeError: cannot schedule new futures after shutdown`
## What it says
The controller's `submit_io(fn, *args)` method MUST check whether `self._io_pool` has been shut down before submitting. If shut down, it MUST lazily recreate the pool (with the same thread count and inflight counter) and submit to the new pool. Do NOT raise `RuntimeError: cannot schedule new futures after shutdown` to the caller.
## Why
If the GUI crashes (`immapp.run` raises `RuntimeError` from `IM_ASSERT`), the controller's `_io_pool` may be shut down by the exception's `__del__` chain. After the wrap (per `conductor/todos/TODO_test_full_live_workflow_v2.md` §3) catches the exception and resumes the GUI, subsequent `submit_io` calls must work — otherwise the controller is permanently dead and every test that follows the crash will fail with `RuntimeError: cannot schedule new futures after shutdown`.
## Pattern
```python
def submit_io(self, fn: Callable[..., T], *args: object, **kwargs: object) -> Future[T]:
if self._io_pool is None or self._io_pool._shutdown:
# Lazy recreation: same worker count, fresh inflight counter
self._io_pool = ThreadPoolExecutor(max_workers=self._io_pool_workers)
self._io_pool_inflight = 0
self._log(f"submit_io: lazy pool recreation after shutdown")
self._io_pool_inflight += 1
try:
return self._io_pool.submit(fn, *args, **kwargs)
except RuntimeError:
# Race: pool was shut down between the check and the submit
# Recreate and retry once
self._io_pool = ThreadPoolExecutor(max_workers=self._io_pool_workers)
self._io_pool_inflight = 1
return self._io_pool.submit(fn, *args, **kwargs)
```
## Test coverage
A test for this directive MUST:
1. Start the controller.
2. Shut down `self._io_pool` directly (`self._io_pool.shutdown(wait=False)`).
3. Call `submit_io(lambda: "ok")`.
4. Assert the result is `"ok"` (not a `RuntimeError`).
5. Assert `self._io_pool_inflight == 1` (counter was reset).
6. Assert no new thread leak (the recreated pool uses the same max_workers).
## Failure modes to avoid
- **Catching the RuntimeError too narrowly** — only catch from the submit call, not from the broader function body. The function body may have other RuntimeError sources (e.g., a logger that uses a closed file handle).
- **Not resetting the inflight counter** — a stale counter means `wait_for_io_drain` returns early (thinking everything is done) when in fact a new wave of work just started.
- **Recreating with different worker counts** — if the pool was 4 workers and we recreate with 8, the next batch may saturate the pool faster than the previous batches, leading to flaky tests.
## Cross-refs
- `conductor/todos/TODO_test_full_live_workflow_v2.md` §"Task 5" — the SHIP task that codifies this pattern
- `conductor/code_styleguides/error_handling.md` §"Result dataclasses" — the broader pattern (don't let exceptions bubble up; convert to `Result[T]` or `ErrorInfo`)