@startuml Error Handling
title rsActor Error Handling Scenarios
participant "Client" as Client
participant "ActorRef<T>" as ActorRef
participant "JoinHandle" as JoinHandle
participant "Actor" as Actor
participant "on_start()" as OnStart
participant "on_run()" as OnRun
participant "on_stop()" as OnStop
== Scenario 1: Initialization Failure ==
Client -> ActorRef: spawn(invalid_args)
activate ActorRef
ActorRef -> OnStart: Call on_start(invalid_args, &actor_ref)
activate OnStart
OnStart -> OnStart: Initialization fails (e.g., DB connection error)
OnStart --> ActorRef: Err(InitializationError)
deactivate OnStart
ActorRef --> JoinHandle: ActorResult::Failed { phase: OnStart, actor: None, error, killed: false }
deactivate ActorRef
note right of JoinHandle
Initialization Failure:
- No actor instance created
- actor field is None
- phase is FailurePhase::OnStart
- killed is always false
end note
Client -> JoinHandle: await join_handle
activate JoinHandle
JoinHandle --> Client: ActorResult::Failed { phase: OnStart, actor: None, ... }
deactivate JoinHandle
== Scenario 2: Runtime Failure in on_run ==
note over Actor: Actor is running normally (on_run is idle handler)
Actor -> OnRun: on_run() called when message queue empty
activate OnRun
OnRun -> OnRun: Runtime error occurs (e.g., resource exhaustion)
OnRun --> Actor: Err(RuntimeError)
deactivate OnRun
note over Actor, OnStop
When on_run returns an error:
- on_stop IS called for cleanup
- on_stop errors are logged but don't change the result
- The actor terminates with ActorResult::Failed
end note
Actor -> OnStop: Call on_stop(&actor_weak, killed) for cleanup
activate OnStop
OnStop --> Actor: Returns (logged if error)
deactivate OnStop
Actor --> JoinHandle: ActorResult::Failed { phase: OnRun, actor: Some(actor), error, killed }
note right of JoinHandle
Runtime Failure:
- Actor instance available for inspection
- phase is FailurePhase::OnRun
- on_stop WAS called for cleanup
- killed reflects termination state at error time
end note
== Scenario 3: Cleanup Failure in on_stop ==
Client -> ActorRef: actor_ref.stop().await
activate ActorRef
ActorRef -> Actor: Send StopGracefully message
Actor -> OnStop: Call on_stop(&actor_weak, killed = false)
activate OnStop
OnStop -> OnStop: Cleanup fails (e.g., file system error)
OnStop --> Actor: Err(CleanupError)
deactivate OnStop
Actor --> JoinHandle: ActorResult::Failed { phase: OnStop, actor: Some(actor), error, killed: false }
deactivate ActorRef
note right of JoinHandle
Cleanup Failure:
- Actor instance available
- phase is FailurePhase::OnStop
- Original termination was graceful
- killed indicates termination type
end note
== Scenario 4: Multiple Error Handling ==
Client -> ActorRef: spawn(args)
activate ActorRef
loop Error Recovery Pattern
ActorRef -> JoinHandle: await join_handle
activate JoinHandle
alt ActorResult::Failed
JoinHandle --> Client: Failed result with error details
deactivate JoinHandle
Client -> Client: Analyze failure phase and error
alt FailurePhase::OnStart
Client -> Client: Fix initialization parameters
Client -> ActorRef: spawn(corrected_args) [retry with fixes]
else FailurePhase::OnRun
Client -> Client: Handle runtime state if available
note right of Client
Runtime failures provide actor state:
- Can inspect final state
- Can recover partial work
- Can implement retry logic
end note
Client -> ActorRef: spawn(args) [optional restart]
else FailurePhase::OnStop
Client -> Client: Log cleanup failure
note right of Client
Cleanup failures are usually logged:
- Actor completed its work
- Only cleanup had issues
- May need manual cleanup
end note
end
else ActorResult::Completed
JoinHandle --> Client: Successful completion
deactivate JoinHandle
note right of Client: Normal completion - no error handling needed
end
end
deactivate ActorRef
== Scenario 5: Message Handling Errors ==
Client -> ActorRef: actor_ref.ask(problematic_message).await
activate ActorRef
ActorRef -> Actor: Process message in handler
activate Actor
note right of Actor
Message Handler Error Handling:
- Errors in handlers don't terminate actor
- Return error types in reply
- Actor continues processing other messages
end note
alt Handler Returns Error Reply
Actor --> ActorRef: Reply with error result
deactivate Actor
ActorRef --> Client: Error reply (actor still alive)
else Handler Panics (should be avoided)
Actor -> Actor: Panic in handler
note right of Actor
Panics in handlers can terminate the actor task
Use proper error handling instead of panics
end note
Actor --> JoinHandle: Task panic -> ActorResult::Failed
ActorRef --> Client: Send error (channel closed)
end
deactivate ActorRef
== Best Practices for Error Handling ==
note over Client, OnStop
Error Handling Best Practices:
1. Initialization (on_start):
- Validate all required resources
- Use ? operator for early returns
- Provide meaningful error messages
2. Runtime (on_run - idle handler):
- Called when message queue is empty
- Return Ok(true) to continue, Ok(false) to disable
- Handle recoverable errors gracefully
- Use timeouts for external operations
- on_stop IS called for cleanup if on_run fails
3. Cleanup (on_stop):
- Called for stop/kill, ActorRef drop, or on_run error
- Use Drop trait for additional guaranteed cleanup
- Differentiate between graceful and forced termination
4. Message Handlers:
- Never panic in handlers
- Return error types in replies
- Use Result<T, E> for fallible operations
5. Client Code:
- Always check ActorResult from join handles
- Implement retry logic for recoverable failures
- Extract and use actor state when available
end note
@enduml