@startuml Actor Termination
title Actor Termination (stop, kill, and lifecycle completion)
actor Client
participant "ActorRef<T>" as ActorRef_obj
participant "MailboxChannel (mpsc)" as MailboxChannel
participant "TerminateChannel (mpsc)" as TerminateChannel
participant "run_actor_lifecycle()" as Lifecycle
participant "Actor::on_run()" as on_run_method
participant "Actor::on_stop()" as on_stop_method
participant "JoinHandle" as JoinHandle_obj
box "Actor Task" #LightCoral
participant Lifecycle
participant on_run_method
participant on_stop_method
end box
== Normal Operation ==
Lifecycle -> Lifecycle: Main tokio::select! loop (biased)
activate Lifecycle
loop Idle Processing (when message queue empty)
Lifecycle -> on_run_method: Calls actor.on_run(&actor_weak).await
activate on_run_method
on_run_method --> Lifecycle: Returns Ok(true), Ok(false), or Err(error)
deactivate on_run_method
note right of Lifecycle: Ok(true): continue idle processing\nOk(false): disable idle processing\nErr(): terminate with failure
end
== Stop Operation (Graceful) ==
Client -> ActorRef_obj: actor_ref.stop().await
activate ActorRef_obj
ActorRef_obj -> MailboxChannel: sender.send(MailboxMessage::StopGracefully(actor_ref)).await
deactivate ActorRef_obj
MailboxChannel -> Lifecycle: tokio::select! receives StopGracefully
Lifecycle -> on_stop_method: Calls actor.on_stop(&actor_weak, killed = false).await
activate on_stop_method
on_stop_method --> Lifecycle: Returns Ok(()) or Err(error)
deactivate on_stop_method
alt on_stop Success
Lifecycle -> Lifecycle: Breaks message loop gracefully
Lifecycle -> MailboxChannel: receiver.close()
Lifecycle -> TerminateChannel: terminate_receiver.close()
Lifecycle --> JoinHandle_obj: ActorResult::Completed { actor, killed: false }
else on_stop Failure
Lifecycle --> JoinHandle_obj: ActorResult::Failed { phase: OnStop, killed: false, .. }
end
deactivate Lifecycle
== Kill Operation (Immediate) ==
Client -> ActorRef_obj: actor_ref.kill()
activate ActorRef_obj
ActorRef_obj -> TerminateChannel: terminate_sender.try_send(ControlSignal::Terminate)
deactivate ActorRef_obj
TerminateChannel -> Lifecycle: tokio::select! (biased) receives Terminate signal
activate Lifecycle
Lifecycle -> on_stop_method: Calls actor.on_stop(&actor_weak, killed = true).await
activate on_stop_method
on_stop_method --> Lifecycle: Returns Ok(()) or Err(error)
deactivate on_stop_method
alt on_stop Success
Lifecycle -> Lifecycle: Breaks message loop immediately
Lifecycle -> MailboxChannel: receiver.close()
Lifecycle -> TerminateChannel: terminate_receiver.close()
Lifecycle --> JoinHandle_obj: ActorResult::Completed { actor, killed: true }
else on_stop Failure
Lifecycle --> JoinHandle_obj: ActorResult::Failed { phase: OnStop, killed: true, .. }
end
deactivate Lifecycle
== Termination via on_run Error ==
note over Lifecycle, on_stop_method
When on_run returns an error, on_stop IS called for cleanup.
The actor then terminates with ActorResult::Failed.
end note
Lifecycle -> on_run_method: Calls actor.on_run(&actor_weak).await
activate Lifecycle
activate on_run_method
on_run_method --> Lifecycle: Returns Err(error)
deactivate on_run_method
Lifecycle -> on_stop_method: Calls on_stop(&actor_weak, killed) for cleanup
activate on_stop_method
on_stop_method --> Lifecycle: Returns (result logged if error)
deactivate on_stop_method
Lifecycle --> JoinHandle_obj: ActorResult::Failed { phase: OnRun, actor: Some(actor), error, killed }
deactivate Lifecycle
== All ActorRef Instances Dropped ==
note over ActorRef_obj: All strong references dropped
MailboxChannel -> Lifecycle: receiver.recv() returns None (channel closed)
activate Lifecycle
Lifecycle -> on_stop_method: Calls actor.on_stop(&actor_weak, killed = false).await
activate on_stop_method
on_stop_method --> Lifecycle: Returns Ok(()) or Err(error)
deactivate on_stop_method
Lifecycle -> Lifecycle: Natural termination due to no references
Lifecycle --> JoinHandle_obj: ActorResult::Completed { actor, killed: false }
deactivate Lifecycle
@enduml