m4_004_schema_parity_resource_decision_checkpoint was added to master
after this branch was created, creating a second Alembic head alongside
a5_006_action_invariants_unique_constraint. The CI unit_tests job runs
on the merge commit (PR branch + master), so both migration files are
present, causing create_template_db.py to fail with MultipleHeads.
Add a5_007_merge_m4_004_and_a5_006 no-op merge migration to resolve
the two heads into a single head.
ThreadPoolExecutor holds internal locks (e.g. _global_shutdown_lock) that
can be in a locked state after fork(). The behave-parallel runner uses
multiprocessing.Pool with the fork start method, so forked worker processes
inherit these locked states and deadlock when trying to create or shut down
a ThreadPoolExecutor.
Replace both concurrent step implementations with direct threading.Thread
usage, following the pattern established in context_tier_thread_safety_steps.py
and other step files throughout the codebase. This eliminates the fork+lock
deadlock while preserving the deterministic concurrency-counter approach for
proving the RLock is released during transport calls.
Fixed ruff format violation (single quotes to double quotes) in step definitions file. Reduced SlowMCPTransport delay from 0.3s to 0.15s and tightened the wall-clock assertion from 0.9s to 0.40s to improve CI reliability while still proving concurrent execution.
ISSUES CLOSED: #10512
MCPToolAdapter.invoke() and discover_tools() held the RLock during the
entire transport.call(), blocking all concurrent operations on the adapter.
The fix splits both methods into three phases:
1. Acquire lock — validate connection state and inputs
2. Release lock — make the transport call without holding the lock
3. Re-acquire lock (discover_tools only) — update shared state
Added Behave BDD tests verifying:
- Concurrent invocations complete in parallel (wall-clock < serial time)
- Concurrent discoveries complete in parallel
- Lock is provably not held during transport calls (cross-thread check)
- Validation still occurs under lock before transport call
- Disconnected adapter still raises RuntimeError under lock
ISSUES CLOSED: #10512
MCPToolAdapter.invoke() and discover_tools() held the RLock during the
entire transport.call(), blocking all concurrent operations on the adapter.
The fix splits both methods into three phases:
1. Acquire lock — validate connection state and inputs
2. Release lock — make the transport call without holding the lock
3. Re-acquire lock (discover_tools only) — update shared state
Added Behave BDD tests verifying:
- Concurrent invocations complete in parallel (wall-clock < serial time)
- Concurrent discoveries complete in parallel
- Lock is provably not held during transport calls (cross-thread check)
- Validation still occurs under lock before transport call
- Disconnected adapter still raises RuntimeError under lock
ISSUES CLOSED: #10512