Skip to content

Commit 4226f74

Browse files
books: hyperactor-book: actor types (#422)
Summary: Pull Request resolved: #422 actor types including notably `ActorHandle` Reviewed By: mariusae Differential Revision: D77737939 fbshipit-source-id: 6bdffd21788af6a007c9c35c68142e1b63b081de
1 parent 27e4184 commit 4226f74

File tree

4 files changed

+291
-1
lines changed

4 files changed

+291
-1
lines changed

books/hyperactor-book/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
- [RemoteActor](actors/remote_actor.md)
3131
- [Binds](actors/binds.md)
3232
- [RemoteHandles](actors/remote_handles.md)
33+
- [ActorHandle](actors/actor_handle.md)
34+
- [Actor Lifecycle](actors/actor_lifecycle.md)
3335
- [Macros](macros/index.md)
3436
- [`#[derive(Handler)]`](macros/handler.md)
3537
- [`#[derive(HandleClient)]`](macros/handle_client.md)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# `ActorHandle<A>`
2+
3+
An `ActorHandle<A>` is a reference to a **local, running actor** of type `A`. It provides access to the actor's messaging ports, lifecycle status, and control methods (such as stop signals).
4+
5+
Unlike remote references (e.g. `ActorRef<A>`), which may refer to actors on other `Proc`s, an `ActorHandle` only exists within the same `Proc` and can be sent messages without requiring serialization.
6+
7+
## Definition
8+
9+
```rust
10+
pub struct ActorHandle<A: Actor> {
11+
cell: InstanceCell,
12+
ports: Arc<Ports<A>>,
13+
}
14+
```
15+
An `ActorHandle` contains:
16+
- `cell` is the actor’s internal runtime state, including identity and lifecycle metadata.
17+
- `ports` is a shared dictionary of all typed message ports available to the actor.
18+
19+
This handle is cloneable, sendable across tasks, and allows interaction with the actor via messaging, status observation, and controlled shutdown.
20+
21+
## Methods
22+
23+
### `new` (internal)
24+
25+
Constructs a new `ActorHandle` from its backing `InstanceCell` and `Ports`. This is called by the runtime when spawning a new actor.
26+
```rust
27+
pub(crate) fn new(cell: InstanceCell, ports: Arc<Ports<A>>) -> Self {
28+
Self { cell, ports }
29+
}
30+
```
31+
32+
### `cell` (internal)
33+
34+
Returns the underlying `InstanceCell` backing the actor.
35+
```rust
36+
pub(crate) fn cell(&self) -> &InstanceCell {
37+
&self.cell
38+
}
39+
```
40+
41+
### `actor_id`
42+
43+
Returns the `ActorId` of the actor represented by this handle.
44+
```rust
45+
pub fn actor_id(&self) -> &ActorId {
46+
self.cell.actor_id()
47+
}
48+
```
49+
50+
### `drain_and_stop`
51+
52+
Signals the actor to drain any pending messages and then stop. This enables a graceful shutdown procedure.
53+
```rust
54+
pub fn drain_and_stop(&self) -> Result<(), ActorError> {
55+
self.cell.signal(Signal::DrainAndStop)
56+
}
57+
```
58+
59+
### `status`
60+
61+
Returns a watch channel that can be used to observe the actor's lifecycle status (e.g., running, stopped, crashed).
62+
```rust
63+
pub fn status(&self) -> watch::Receiver<ActorStatus> {
64+
self.cell.status().clone()
65+
}
66+
```
67+
68+
### `send`
69+
70+
Sends a message of type `M` to the actor. The actor must implement `Handler<M>` for this to compile.
71+
72+
Messages sent via an `ActorHandle` are always delivered in-process and do not require serialization.
73+
```rust
74+
pub fn send<M: Message>(&self, message: M) -> Result<(), MailboxSenderError>
75+
where
76+
A: Handler<M>,
77+
{
78+
self.ports.get().send(message)
79+
}
80+
```
81+
82+
### `port`
83+
84+
Returns a reusable port handle for the given message type.
85+
```rust
86+
pub fn port<M: Message>(&self) -> PortHandle<M>
87+
where
88+
A: Handler<M>,
89+
{
90+
self.ports.get()
91+
}
92+
```
93+
94+
### `bind`
95+
96+
Creates a remote reference (`ActorRef<R>`) by applying a `Binds<A>` implementation.
97+
```rust
98+
pub fn bind<R: Binds<A>>(&self) -> ActorRef<R> {
99+
self.cell.bind(self.ports.as_ref())
100+
}
101+
```
102+
103+
### Binding and ActorRefs
104+
105+
The `bind()` method on `ActorHandle` creates an `ActorRef<R>` for a given remote-facing reference type `R`. This is the bridge between a local actor instance and its externally visible interface.
106+
```rust
107+
pub fn bind<R: Binds<A>>(&self) -> ActorRef<R>
108+
```
109+
This method requires that `R` implements the `Binds<A>` trait. The `Binds` trait specifies how to associate a remote-facing reference type with the concrete ports handled by the actor:
110+
```rust
111+
pub trait Binds<A: Actor>: RemoteActor {
112+
fn bind(ports: &Ports<A>);
113+
}
114+
```
115+
In practice, `A` and `R` are usually the same type; this is the pattern produced by the `#[export]` macro. But `R` can also be a trait object or wrapper that abstracts over multiple implementations.
116+
117+
### Binding internals
118+
119+
Calling `bind()` on the `ActorHandle`:
120+
1. Invokes the `Binds<A>::bind()` implementation for `R`, registering the actor's message handlers into the `Ports<A>` dictionary.
121+
2. Always binds the `Signal` type (used for draining, stopping, and supervision).
122+
3. Records the bound message types into `InstanceState::exported_named_ports`, enabling routing and diagnostics.
123+
4. Constructs the final `ActorRef<R>` using `ActorRef::attest(...)`, which assumes the type-level correspondence between `R` and the bound ports.
124+
125+
The result is a typed, routable reference that can be shared across `Proc`s.
126+
127+
## `IntoFuture for ActorHandle`
128+
129+
### Overview
130+
131+
An `ActorHandle<A>` can be awaited directly thanks to its `IntoFuture` implementation. Awaiting the `handle` waits for the actor to shut down.
132+
133+
### Purpose
134+
135+
This allows you to write:
136+
```rust
137+
let status = actor_handle.await;
138+
```
139+
Instead of:
140+
```rust
141+
let mut status = actor_handle.status();
142+
status.wait_for(ActorStatus::is_terminal).await;
143+
```
144+
145+
### Behavior
146+
147+
When awaited, the handle:
148+
- Subscribes to the actor’s status channel,
149+
- Waits for a terminal status (`Stopped`, `Crashed`, etc.),
150+
- Returns the final status,
151+
- Returns `ActorStatus::Unknown` if the channel closes unexpectedly.
152+
153+
### Implementation
154+
```rust
155+
impl<A: Actor> IntoFuture for ActorHandle<A> {
156+
type Output = ActorStatus;
157+
type IntoFuture = BoxFuture<'static, Self::Output>;
158+
159+
fn into_future(self) -> Self::IntoFuture {
160+
let future = async move {
161+
let mut status_receiver = self.cell.status().clone();
162+
let result = status_receiver.wait_for(ActorStatus::is_terminal).await;
163+
match result {
164+
Err(_) => ActorStatus::Unknown,
165+
Ok(status) => status.passthrough(),
166+
}
167+
};
168+
future.boxed()
169+
}
170+
}
171+
```
172+
### Summary
173+
174+
This feature is primarily ergonomic. It provides a natural way to synchronize with the termination of an actor by simply awaiting its handle.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Actor Lifecycle Types
2+
3+
This page documents auxiliary types used in actor startup, shutdown, and supervision logic.
4+
5+
## `ActorStatus`
6+
7+
`ActorStatus` describes the current runtime state of an actor. It is used to monitor progress, coordinate shutdown, and detect failure conditions.
8+
```rust
9+
pub enum ActorStatus {
10+
Unknown,
11+
Created,
12+
Initializing,
13+
Client,
14+
Idle,
15+
Processing(SystemTime, Option<(String, Option<String>)>),
16+
Saving(SystemTime),
17+
Loading(SystemTime),
18+
Stopping,
19+
Stopped,
20+
Failed(String),
21+
}
22+
```
23+
24+
### States
25+
- `Unknown`: The status is unknown (e.g. not yet initialized).
26+
- `Created`: The actor has been constructed but not yet started.
27+
- `Initializing`: The actor is running its init lifecycle hook and is not yet receiving messages.
28+
- `Client`: The actor is operating in “client” mode; its ports are being managed manually.
29+
- `Idle`: The actor is ready to process messages but is currently idle.
30+
- `Processing`: The actor is handling a message. Contains a timestamp and optionally the handler/arm label.
31+
- `Saving`: The actor is saving its state as part of a checkpoint. Includes the time the operation began.
32+
- `Loading`: The actor is loading a previously saved state.
33+
- `Stopping`: The actor is in shutdown mode and draining its mailbox.
34+
- `Stopped`: The actor has exited and will no longer process messages.
35+
- `Failed`: The actor terminated abnormally. Contains an error description.
36+
37+
### Methods
38+
- `is_terminal(&self) -> bool`: Returns true if the actor has either stopped or failed.
39+
- `is_failed(&self) -> bool`: Returns true if the actor is in the Failed state.
40+
- `passthrough(&self) -> ActorStatus`: Returns a clone of the status. Used internally during joins.
41+
- `span_string(&self) -> &'static str`: Returns the active handler/arm name if available. Used for tracing.
42+
43+
## `Signal`
44+
45+
`Signal` is used to control actor lifecycle transitions externally. These messages are sent internally by the runtime (or explicitly by users) to initiate operations like shutdown.
46+
```rust
47+
pub enum Signal {
48+
Stop,
49+
DrainAndStop,
50+
Save,
51+
Load,
52+
}
53+
```
54+
Variants
55+
- `Stop`: Immediately halts the actor, even if messages remain in its mailbox.
56+
- `DrainAndStop`: Gracefully stops the actor by first draining all queued messages.
57+
- `Save`: Triggers a state snapshot using the actor’s Checkpointable::save method.
58+
- `Load`: Requests state restoration via Checkpointable::load.
59+
60+
These signals are routed like any other message, typically sent using `ActorHandle::send` or by the runtime during supervision and recovery procedures.
61+
62+
## `ActorError`
63+
64+
`ActorError` represents a failure encountered while serving an actor. It includes the actor's identity and the underlying cause.
65+
```rust
66+
pub struct ActorError {
67+
actor_id: ActorId,
68+
kind: ActorErrorKind,
69+
}
70+
```
71+
This error type is returned in various actor lifecycle operations such as initialization, message handling, checkpointing, and shutdown. It is structured and extensible, allowing the runtime to distinguish between different classes of failure.
72+
73+
### Associated Methods
74+
```rust
75+
impl ActorError {
76+
/// Constructs a new `ActorError` with the given ID and kind.
77+
pub(crate) fn new(actor_id: ActorId, kind: ActorErrorKind) -> Self
78+
79+
/// Returns a cloneable version of this error, discarding error structure
80+
/// and retaining only the formatted string.
81+
fn passthrough(&self) -> Self
82+
}
83+
```
84+
85+
## `ActorErrorKind`
86+
87+
```rust
88+
pub enum ActorErrorKind {
89+
Processing(anyhow::Error),
90+
Panic(anyhow::Error),
91+
Init(anyhow::Error),
92+
Mailbox(MailboxError),
93+
MailboxSender(MailboxSenderError),
94+
Checkpoint(CheckpointError),
95+
MessageLog(MessageLogError),
96+
IndeterminateState,
97+
Passthrough(anyhow::Error),
98+
}
99+
```
100+
### Variants
101+
102+
- `Processing`: The actor's `handle()` method returned an error.
103+
- `Panic`: A panic occurred during message handling or actor logic.
104+
- `Init`: Actor initialization failed.
105+
- `Mailbox`: A lower-level mailbox error occurred.
106+
- `MailboxSender`: A lower-level sender error occurred.
107+
- `Checkpoint`: Error during save/load of actor state.
108+
- `MessageLog`: Failure in the underlying message log.
109+
- `IndeterminateState`: The actor reached an invalid or unknown internal state.
110+
- `Passthrough`: A generic error, preserving only the error message.
111+
112+
`Passthrough` is used when a structured error needs to be simplified for cloning or propagation across boundaries.

books/hyperactor-book/src/actors/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ This chapter introduces the actor system in hyperactor. We'll cover:
1313
- The [`RemoteActor`](./remote_actor.md) marker trait for remotely referencable types
1414
- The [`Binds`](./binds.md) trait for wiring exported ports to reference types
1515
- The [`RemoteHandles`](./remote_handles.md) trait for associating message types with a reference
16+
- The [`ActorHandle`](./actor_handle.md) type for referencing and communicating with running actors
17+
- [Actor Lifecycle](./lifecycle.md), including `Signal` and `ActorStatus`
1618

17-
Actors are always instantiated with parameters and bound to a mailbox, enabling them to participate in reliable message-passing systems. Supervision, checkpointing, and references all build upon this core abstraction.
19+
Actors are instantiated with parameters and bound to mailboxes, enabling reliable message-passing. The runtime builds upon this foundation to support supervision, checkpointing, and remote interaction via typed references.

0 commit comments

Comments
 (0)