linera-io/linera-protocol · error
Failed to unwrap shared context
Error message
Failed to unwrap shared context
What it means
Arc::try_unwrap on the shared ClientContext failed because at least one other Arc clone is still alive. After the benchmark finishes, main.rs needs exclusive ownership (&mut) to run wrap_up_benchmark, but the chain listener or another background task still holds a clone, so the strong count is > 1 at the unwrap point.
Source
Thrown at linera-service/src/cli/main.rs:943
}
})
.collect::<Result<_, _>>()?;
linera_client::benchmark::Benchmark::run_benchmark(
bps,
chain_clients.clone(),
generators,
transactions_per_block,
health_check_endpoints.clone(),
runtime_in_seconds,
delay_between_chains_ms,
chain_listener,
&shutdown_notifier,
)
.await?;
let mut context = std::sync::Arc::try_unwrap(shared_context)
.map_err(|_| anyhow::anyhow!("Failed to unwrap shared context"))?
.into_inner();
context
.wrap_up_benchmark(chain_clients, close_chains, wrap_up_max_in_flight)
.await?;
}
BenchmarkCommand::Multi {
options: benchmark_options,
processes,
faucet,
client_state_dir,
delay_between_processes,
cross_wallet_transfers,
} => {
let mut command = BenchmarkCommand::Single {
options: benchmark_options.clone(),
};
let faucet_client = cli_wrappers::Faucet::new(faucet.clone());View on GitHub (pinned to 6c226ddcb3)
Solutions
- Ensure every task holding an Arc<ClientContext> clone is joined (or aborted and awaited) before try_unwrap.
- Trigger shutdown_notifier, then await the listener task's JoinHandle so its clone is provably dropped.
- If the race is benign, retry try_unwrap briefly after signaling shutdown instead of failing immediately.
- Longer term, restructure wrap-up so it does not require exclusive ownership (e.g. message-passing to the task that owns the context).
Example fix
// before: unwrap races against tasks that still hold clones
let context = Arc::try_unwrap(shared_context)
.map_err(|_| anyhow::anyhow!("Failed to unwrap shared context"))?
.into_inner();
// after: signal shutdown, join the holder, then unwrap
shutdown_notifier.notify_one();
let shared_context = listener_handle.await?; // task returns its clone / drops it
let context = Arc::try_unwrap(shared_context)
.expect("all clones dropped after tasks joined")
.into_inner(); Defensive patterns
Strategy: retry
Validate before calling
if Arc::strong_count(&shared_context) != 1 {
// another task still holds the ClientContext — signal and join it first
shutdown_notifier.notify_one();
listener_handle.abort();
} Try / catch
let context = match Arc::try_unwrap(shared_context) {
Ok(ctx) => ctx.into_inner(),
Err(ctx) => {
tracing::warn!("context still shared; waiting for holders to drop");
shutdown_notifier.notify_one();
let _ = listener_handle.await; // ensure the clone is dropped
Arc::try_unwrap(ctx)
.expect("all clones dropped after tasks joined")
.into_inner()
}
}; Prevention
- Keep a registry of every spawned task holding an Arc<ClientContext> clone and join them all before unwrap.
- Always await task handles after triggering shutdown — signaling alone does not drop clones.
- Debug with Arc::strong_count to find unexpected holders early.
When it happens
Trigger: The shutdown notifier fired but the listener task (or any task holding Arc<ClientContext>: notification handlers, query-subscription watchers) has not yet observed cancellation and dropped its clone — a race between shutdown signaling and task teardown.
Common situations: New background holders of the context added in newer versions that outlive the benchmark; overloaded machines where task teardown lags the shutdown signal; aborting on the unwrap path instead of joining tasks.
Related errors
- Expected balance: {expected_balance}, actual balance: {actua
- MetaMask is not available
- owner should be different from spender
- invalid block export configuration: {message}
- Expected an `ExecutionError`. Got: {self:#?}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/a14191c8e31feefd.
Report an issue: GitHub.