Parent: #108
Problem
Every willow_actor::state::select(...).await and willow_actor::state::mutate(...).await call in crates/client/src/ is awaited without a timeout. If the target StateActor hangs, deadlocks, or is slow-blocked by a large mutation, the calling task (and by transitivity any UI reactive chain) hangs forever.
Fix
-
Add a small helper in crates/client/src/util.rs:
use std::time::Duration;
pub const ACTOR_CALL_TIMEOUT: Duration = Duration::from_secs(5);
pub async fn with_timeout<T, F>(label: &'static str, f: F) -> Result<T, ClientError>
where
F: std::future::Future<Output = T>,
{
#[cfg(not(target_arch = "wasm32"))]
{
tokio::time::timeout(ACTOR_CALL_TIMEOUT, f)
.await
.map_err(|_| ClientError::ActorTimeout(label))
}
#[cfg(target_arch = "wasm32")]
{
// wasm doesn't have tokio timers; use wasm-timer or gloo_timers.
// ...
}
}
-
Wrap all willow_actor::state::select/mutate calls in with_timeout("context_label", ...).
-
Add ClientError::ActorTimeout(&'static str) variant.
-
On timeout, log the label and return the error to the caller so the UI can retry or show an error state.
Test
- Unit test: register a fake actor that sleeps longer than
ACTOR_CALL_TIMEOUT, call a client method that uses it, assert ClientError::ActorTimeout is returned within ~6 seconds.
- Regression: existing happy-path tests should continue passing without modification.
Scope
Don't block on this for every call at once. Start with the hot paths in mutations.rs, accessors.rs, and joining.rs. File a follow-up for the long tail.
Out of scope
- Switching to a different actor system.
- Backpressure / mailbox bounds on actors (separate issue).
Parent: #108
Problem
Every
willow_actor::state::select(...).awaitandwillow_actor::state::mutate(...).awaitcall incrates/client/src/is awaited without a timeout. If the targetStateActorhangs, deadlocks, or is slow-blocked by a large mutation, the calling task (and by transitivity any UI reactive chain) hangs forever.Fix
Add a small helper in
crates/client/src/util.rs:Wrap all
willow_actor::state::select/mutatecalls inwith_timeout("context_label", ...).Add
ClientError::ActorTimeout(&'static str)variant.On timeout, log the label and return the error to the caller so the UI can retry or show an error state.
Test
ACTOR_CALL_TIMEOUT, call a client method that uses it, assertClientError::ActorTimeoutis returned within ~6 seconds.Scope
Don't block on this for every call at once. Start with the hot paths in
mutations.rs,accessors.rs, andjoining.rs. File a follow-up for the long tail.Out of scope