Summary
RefreshCoordinator::force_complete_request() only handles the Completing and Idle states. If begin_completion() panics with an "unexpected key" error, the coordinator state is restored to Running before the panic. The RefreshCompletionGuard::drop then calls force_complete_request(), which hits the _ => {} wildcard arm — leaving the coordinator permanently stuck in Running.
Root Cause
In crates/pet/src/jsonrpc.rs:
- Line 148-150 (
begin_completion): On key mismatch, state is restored to Running(active) before panicking
- Line 273-278 (
RefreshCompletionGuard::drop): Calls force_complete_request() during stack unwind
- Line 210-220 (
force_complete_request): Only transitions Completing → Idle. The Running state matches _ => {} — a silent no-op.
Impact
After the panic, the coordinator is stuck in Running with no thread owning the refresh. All future refresh requests call wait_until_idle() and hang forever — deadlocking the JSONRPC server.
While the "unexpected key" panic is a defensive assertion that shouldn't fire in normal operation, if it ever fires (e.g., due to a future code change or race condition), the server becomes completely unresponsive.
Proposed Fix
Either:
- Have
force_complete_request() also handle the Running state by transitioning to Idle + notify_all(), OR
- Return a
Result from begin_completion() instead of panicking, allowing proper error recovery
Introduced By
PR #386 (d3a060f — fix: deduplicate concurrent refresh requests)
Summary
RefreshCoordinator::force_complete_request()only handles theCompletingandIdlestates. Ifbegin_completion()panics with an "unexpected key" error, the coordinator state is restored toRunningbefore the panic. TheRefreshCompletionGuard::dropthen callsforce_complete_request(), which hits the_ => {}wildcard arm — leaving the coordinator permanently stuck inRunning.Root Cause
In
crates/pet/src/jsonrpc.rs:begin_completion): On key mismatch, state is restored toRunning(active)before panickingRefreshCompletionGuard::drop): Callsforce_complete_request()during stack unwindforce_complete_request): Only transitionsCompleting → Idle. TheRunningstate matches_ => {}— a silent no-op.Impact
After the panic, the coordinator is stuck in
Runningwith no thread owning the refresh. All future refresh requests callwait_until_idle()and hang forever — deadlocking the JSONRPC server.While the "unexpected key" panic is a defensive assertion that shouldn't fire in normal operation, if it ever fires (e.g., due to a future code change or race condition), the server becomes completely unresponsive.
Proposed Fix
Either:
force_complete_request()also handle theRunningstate by transitioning toIdle+notify_all(), ORResultfrombegin_completion()instead of panicking, allowing proper error recoveryIntroduced By
PR #386 (
d3a060f— fix: deduplicate concurrent refresh requests)