Reference
Troubleshooting
Solutions to common issues when working with Acton Reactive.
Compilation Errors
"cannot find macro acton_actor"
Problem: The macro isn't in scope.
Solution: Import the prelude:
use acton_reactive::prelude::*;
"the trait Default is not implemented"
Problem: Actor state must implement Default.
Solution: Add #[derive(Default)] or implement it manually:
#[acton_actor]
#[derive(Default)]
struct MyActor {
count: i32,
}
Or for custom initialization:
impl Default for MyActor {
fn default() -> Self {
Self { count: 100 }
}
}
"expected Reply, found ()"
Problem: Handler must return a Reply.
Solution: Return Reply::ready() for no response:
builder.mutate_on::<Message>(|actor, envelope| {
actor.model.value = envelope.message().value;
Reply::ready() // Don't forget this!
});
"Send is not implemented for..."
Problem: Something in your async block isn't thread-safe.
Solution: Ensure data moved into async blocks is Send:
// Bad: Rc is not Send
let data = Rc::new(value);
// Good: Arc is Send
let data = Arc::new(value);
Runtime Errors
Actor stops unexpectedly
Problem: A panic in a handler stops the actor.
Solution: Catch panics or use Result types:
builder.mutate_on::<RiskyMessage>(|actor, envelope| {
// Catch potential panics
let result = std::panic::catch_unwind(|| {
risky_operation()
});
match result {
Ok(_) => Reply::ready(),
Err(e) => {
tracing::error!("Panic caught: {:?}", e);
Reply::ready()
}
}
});
Messages not being received
Possible causes:
- Actor stopped: Check if the actor is still running
- Wrong handler type: Using
act_onwhen you needmutate_on - Not awaiting send:
handle.send(msg).await
Debug with logging:
builder.mutate_on::<Message>(|actor, envelope| {
tracing::debug!("Received message: {:?}", envelope.message());
// ...
Reply::ready()
});
Reply envelope not working
Problem: Response never arrives at sender.
Solution: Ensure the sender has a handler for the response type:
// Sender must handle the response
sender.mutate_on::<CountResponse>(|actor, envelope| {
let count = envelope.message().0;
println!("Got count: {}", count);
Reply::ready()
});
Deadlock between actors
Problem: Actor A waits for B, B waits for A via reply envelopes.
Solution: Avoid circular request chains. Use fire-and-forget with callbacks:
// Bad: potential circular wait
// Actor A sends to B, B sends back to A, A sends back to B...
// Good: use fire-and-forget
actor_b.send(QueryRequest { reply_to: self_handle }).await;
Performance Issues
Slow message processing
Possible causes:
- Blocking in handlers: Use async or spawn blocking work
- Single actor bottleneck: Use worker pools
- Too many clones: Use
Arcfor large data
Solution for blocking work:
builder.act_on::<HeavyWork>(|actor, envelope| {
let data = envelope.message().data.clone();
let reply = envelope.reply_envelope();
Reply::pending(async move {
// Move to blocking thread pool
let result = tokio::task::spawn_blocking(move || {
heavy_computation(&data)
}).await.unwrap();
reply.send(WorkResult(result)).await;
})
});
Memory growing over time
Problem: Actor state accumulating without cleanup.
Solution: Implement periodic cleanup:
#[acton_message]
struct Cleanup;
builder.mutate_on::<Cleanup>(|actor, _envelope| {
actor.model.cache.retain(|_, v| !v.is_expired());
Reply::ready()
});
// Schedule cleanup
let cleanup_handle = handle.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(60)).await;
cleanup_handle.send(Cleanup).await;
}
});
IPC Issues
"Connection refused"
Problem: Socket doesn't exist or server isn't running.
Solution: Verify the server started successfully:
// Server
let listener = runtime.start_ipc_listener().await
.expect("Failed to start IPC listener");
println!("IPC listener started");
// Client - check socket exists
let socket_path = IpcConfig::load().socket_path();
if !std::path::Path::new(&socket_path).exists() {
eprintln!("Socket not found at: {}", socket_path);
}
"Permission denied" on socket
Problem: Unix socket file permissions.
Solution: Set appropriate permissions or run with correct user:
# Check permissions
ls -la /run/user/$(id -u)/acton/
# Socket should be writable by your user
Testing Issues
Tests hang
Problem: Actors not shut down, runtime waiting.
Solution: Always shutdown in tests:
#[tokio::test]
async fn test() {
let mut runtime = ActonApp::launch_async().await;
// ... test code ...
runtime.shutdown_all().await.ok(); // Don't forget!
}
Flaky tests
Problem: Race conditions in async tests.
Solution: Use atomic counters and allow processing time:
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
#[tokio::test]
async fn test() {
let mut runtime = ActonApp::launch_async().await;
let count = Arc::new(AtomicI32::new(0));
let count_clone = count.clone();
// Set up actor that updates atomic counter
let mut actor = runtime.new_actor::<MyActor>();
actor.mutate_on::<Increment>(move |_actor, _env| {
count_clone.fetch_add(1, Ordering::SeqCst);
Reply::ready()
});
let handle = actor.start().await;
handle.send(Increment).await;
// Wait for async processing
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
assert_eq!(count.load(Ordering::SeqCst), 1);
runtime.shutdown_all().await.ok();
}
Getting Help
If you can't find your answer here:
- Check the API docs
- Search GitHub issues
- Open a new issue with:
- Minimal reproduction code
- Error messages
- Rust and acton-reactive versions