Skip to content

Commit 7d9ad5c

Browse files
committed
[hyperactor] remove new from actor
Pull Request resolved: #1962 We shouldn't mandate how an actor is actually instantiated. Rather, 'spawn' should just take over ownership of an `Actor` object directly, and run its loop from there. This helps to separate concerns and simplify the implementation. In this change: 1) We remote `new` from `Actor`, and only require it in `RemoteSpawn` (renamed from `RemotableActor`), in order to enable remote spawning 2) Change the spawn APIs to take ownership over an actor object 3) Remote the `derive(Actor)` macro, this can now be implemented with a simple `impl Actor for Foo{}` marker trait when no additonal behavior needs to be customized. 4) Simplify a bunch of actor construction, esp. in tests, where we like to just use simple objects. Using this, in the next change, we will make `spawn` fully synchronous. This leaves the #[export] attribute macro somewhat schizophrenic: `spawn = true` now only registers the actor in the remote registry. We should think about how to simplify this, too. ghstack-source-id: 324991671 @exported-using-ghexport Differential Revision: [D87575629](https://our.internmc.facebook.com/intern/diff/D87575629/) **NOTE FOR REVIEWERS**: This PR has internal Meta-specific changes or comments, please review them on [Phabricator](https://our.internmc.facebook.com/intern/diff/D87575629/)!
1 parent 12ce58c commit 7d9ad5c

File tree

45 files changed

+590
-645
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+590
-645
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This chapter introduces the actor system in hyperactor. We'll cover:
88

99
- The [`Actor`](./actor.md) trait and its lifecycle hooks
1010
- The [`Handler`](./handler.md) trait for defining message-handling behavior
11-
- The [`RemotableActor`](./remotable_actor.md) trait for enabling remote spawning
11+
- The [`RemoteSpawn`](./remotable_actor.md) trait for enabling remote spawning
1212
- The [`Checkpointable`](./checkpointable.md) trait for supporting actor persistence and recovery
1313
- The [`Referable`](./remote_actor.md) marker trait for remotely referencable types
1414
- The [`Binds`](./binds.md) trait for wiring exported ports to reference types
Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
# The `RemoteableActor` Trait
22

33
```rust
4-
pub trait RemotableActor: Actor
5-
where
6-
Self::Params: RemoteMessage,
7-
{
4+
pub trait RemoteSpawn: Actor + Referable + Binds<Self> {
5+
/// The type of parameters used to instantiate the actor remotely.
6+
type Params: RemoteMessage;
7+
8+
/// Creates a new actor instance given its instantiation parameters.
9+
async fn new(params: Self::Params) -> anyhow::Result<Self>;
10+
811
fn gspawn(
912
proc: &Proc,
1013
name: &str,
1114
serialized_params: Data,
12-
) -> Pin<Box<dyn Future<Output = Result<ActorId, anyhow::Error>> + Send>>;
15+
) -> Pin<Box<dyn Future<Output = Result<ActorId, anyhow::Error>> + Send>> { /* default impl. */}
1316

1417
fn get_type_id() -> TypeId {
1518
TypeId::of::<Self>()
1619
}
1720
}
1821
```
19-
The `RemotableActor` trait marks an actor type as spawnable across process boundaries. It enables hyperactor's remote spawning and registration system, allowing actors to be created from serialized parameters in a different `Proc`.
22+
The `RemoteSpawn` trait marks an actor type as spawnable across process boundaries. It enables hyperactor's remote spawning and registration system, allowing actors to be created from serialized parameters in a different `Proc`.
2023

2124
## Requirements
2225
- The actor type must also implement `Actor`.
23-
- Its `Params` type (used in `Actor::new`) must implement `RemoteMessage`, so it can be serialized and transmitted over the network.
26+
- Its `Params` type (used in `RemoteSpawn::new`) must implement `RemoteMessage`, so it can be serialized and transmitted over the network.
27+
- `new` creates a new instance of the actor given its parameters
2428

2529
## `gspawn`
2630
```rust
@@ -39,41 +43,8 @@ The method deserializes the parameters, creates the actor, and returns its `Acto
3943

4044
This is used internally by hyperactor's remote actor registry and `spawn` services. Ordinary users generally don't call this directly.
4145

42-
> **Note:** This is not an `async fn` because `RemotableActor` must be object-safe.
46+
> **Note:** This is not an `async fn` because `RemoteSpawn` must be object-safe.
4347
4448
## `get_type_id`
4549

4650
Returns a stable `TypeId` for the actor type. Used to identify actor types at runtime—e.g., in registration tables or type-based routing logic.
47-
48-
## Blanket Implementation
49-
50-
The RemotableActor trait is automatically implemented for any actor type `A` that:
51-
- implements `Actor` and `Referable`,
52-
- and whose `Params` type implements `RemoteMessage`.
53-
54-
This allows `A` to be remotely registered and instantiated from serialized data, typically via the runtime's registration mechanism.
55-
56-
```rust
57-
impl<A> RemotableActor for A
58-
where
59-
A: Actor + Referable,
60-
A: Binds<A>,
61-
A::Params: RemoteMessage,
62-
{
63-
fn gspawn(
64-
proc: &Proc,
65-
name: &str,
66-
serialized_params: Data,
67-
) -> Pin<Box<dyn Future<Output = Result<ActorId, anyhow::Error>> + Send>> {
68-
let proc = proc.clone();
69-
let name = name.to_string();
70-
Box::pin(async move {
71-
let handle = proc
72-
.spawn::<A>(&name, bincode::deserialize(&serialized_params)?)
73-
.await?;
74-
Ok(handle.bind::<A>().actor_id)
75-
})
76-
}
77-
}
78-
```
79-
Note the `Binds<A>` bound: this trait specifies how an actor's ports are wired determining which message types the actor can receive remotely. The resulting `ActorId` corresponds to a port-bound, remotely callable version of the actor.

docs/source/books/hyperactor-book/src/macros/export.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The macro expands to include:
1818
- A `Binds<Self>` implementation that registers supported message types
1919
- Implementations of `RemoteHandles<T>` for each type in the `handlers = [...]` list
2020
- A `Referable` marker implementation
21-
- If `spawn = true`, a `RemotableActor` implementation and an inventory registration of the `spawn` function.
21+
- If `spawn = true`, the actor's `RemoteSpawn` implementation is registered in the remote actor inventory.
2222

2323
This enables the actor to be:
2424
- Spawned dynamically by name
@@ -46,7 +46,7 @@ impl Named for ShoppingListActor {
4646
```
4747
If `spawn = true`, the macro also emits:
4848
```rust
49-
impl RemotableActor for ShoppingListActor {}
49+
impl RemoteSpawn for ShoppingListActor {}
5050
```
5151
This enables remote spawning via the default `gspawn` provided by a blanket implementation.
5252

hyperactor/example/derive.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,11 @@ struct GetItemCount<C> {
4949
}
5050

5151
// Define an actor.
52-
#[derive(Debug, Actor, Default)]
53-
#[hyperactor::export(
54-
spawn = true,
55-
handlers = [
56-
ShoppingList,
57-
ClearList,
58-
GetItemCount<usize>,
59-
],
60-
)]
52+
#[derive(Debug, Default)]
6153
struct ShoppingListActor(HashSet<String>);
6254

55+
impl Actor for ShoppingListActor {}
56+
6357
// ShoppingListHandler is the trait generated by derive(Handler) above.
6458
// We implement the trait here for the actor, defining a handler for
6559
// each ShoppingList message.
@@ -140,7 +134,7 @@ async fn main() -> Result<(), anyhow::Error> {
140134

141135
// Spawn our actor, and get a handle for rank 0.
142136
let shopping_list_actor: hyperactor::ActorHandle<ShoppingListActor> =
143-
proc.spawn("shopping", ()).await?;
137+
proc.spawn("shopping", ShoppingListActor::default()).await?;
144138
let shopping_api: hyperactor::ActorRef<ShoppingApi> = shopping_list_actor.bind();
145139
// We join the system, so that we can send messages to actors.
146140
let (client, _) = proc.instance("client").unwrap();

hyperactor/example/stream.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use hyperactor::Handler;
1818
use hyperactor::Instance;
1919
use hyperactor::Named;
2020
use hyperactor::PortRef;
21+
use hyperactor::RemoteSpawn;
2122
use hyperactor::proc::Proc;
2223
use serde::Deserialize;
2324
use serde::Serialize;
@@ -54,13 +55,6 @@ struct CountClient {
5455

5556
#[async_trait]
5657
impl Actor for CountClient {
57-
// Where to send subscribe messages.
58-
type Params = PortRef<Subscribe>;
59-
60-
async fn new(counter: PortRef<Subscribe>) -> Result<Self, anyhow::Error> {
61-
Ok(Self { counter })
62-
}
63-
6458
async fn init(&mut self, this: &Instance<Self>) -> Result<(), anyhow::Error> {
6559
// Subscribe to the counter on initialization. We give it our u64 port to report
6660
// messages back to.
@@ -69,6 +63,16 @@ impl Actor for CountClient {
6963
}
7064
}
7165

66+
#[async_trait]
67+
impl RemoteableActor for CountClient {
68+
// Where to send subscribe messages.
69+
type Params = PortRef<Subscribe>;
70+
71+
async fn new(counter: PortRef<Subscribe>) -> Result<Self, anyhow::Error> {
72+
Ok(Self { counter })
73+
}
74+
}
75+
7276
#[async_trait]
7377
impl Handler<u64> for CountClient {
7478
async fn handle(&mut self, cx: &Context<Self>, count: u64) -> Result<(), anyhow::Error> {
@@ -81,13 +85,19 @@ impl Handler<u64> for CountClient {
8185
async fn main() {
8286
let proc = Proc::local();
8387

84-
let counter_actor: ActorHandle<CounterActor> = proc.spawn("counter", ()).await.unwrap();
88+
let counter_actor: ActorHandle<CounterActor> = proc
89+
.spawn("counter", CounterActor::default())
90+
.await
91+
.unwrap();
8592

8693
for i in 0..10 {
8794
// Spawn new "countees". Every time each subscribes, the counter broadcasts
8895
// the count to everyone.
8996
let _countee_actor: ActorHandle<CountClient> = proc
90-
.spawn(&format!("countee_{}", i), counter_actor.port().bind())
97+
.spawn(
98+
&format!("countee_{}", i),
99+
CountClient::new(counter_actor.port().bind()).unwrap(),
100+
)
91101
.await
92102
.unwrap();
93103
#[allow(clippy::disallowed_methods)]

0 commit comments

Comments
 (0)