Problem
_sync_lane_tick / _async_lane_tick (interlock/_coordination.py:283-300, 441-464) pull an op off the work queue before resolving the weak reference:
op = work.get(timeout=interval) # unfinished_tasks += 0? no: get() does not decrement
coordinator = ref()
if coordinator is None:
return False # op dequeued, task_done() never called
If the coordinator was collected between the enqueue and the dequeue, the lane exits without work.task_done(), so queue.unfinished_tasks stays positive forever and wait_idle() (Queue.join()) on that queue can never return.
Severity
Low. In practice the only holder of wait_idle() is the coordinator itself, so once it is collected nobody can join the queue — the hang is currently unreachable outside a test that keeps a direct reference to _work (as test__async_lane_tick__runs_queued_op_and_dead_ref_stops does). It is still a broken queue invariant that becomes reachable the moment the queue is exposed (see the shutdown API issue).
Fix
Release the dequeued op before returning:
coordinator = ref()
if coordinator is None:
if op is not None:
work.task_done()
return False
Acceptance criteria
- Both lanes call
task_done() on the drop path.
- A test asserts
work.join() returns after the lane exits with an op in flight and a dead coordinator ref.
- Coverage stays at 100%.
Problem
_sync_lane_tick/_async_lane_tick(interlock/_coordination.py:283-300,441-464) pull an op off the work queue before resolving the weak reference:If the coordinator was collected between the enqueue and the dequeue, the lane exits without
work.task_done(), soqueue.unfinished_tasksstays positive forever andwait_idle()(Queue.join()) on that queue can never return.Severity
Low. In practice the only holder of
wait_idle()is the coordinator itself, so once it is collected nobody can join the queue — the hang is currently unreachable outside a test that keeps a direct reference to_work(astest__async_lane_tick__runs_queued_op_and_dead_ref_stopsdoes). It is still a broken queue invariant that becomes reachable the moment the queue is exposed (see the shutdown API issue).Fix
Release the dequeued op before returning:
Acceptance criteria
task_done()on the drop path.work.join()returns after the lane exits with an op in flight and a dead coordinator ref.