Remote Actors Guide¶
Guide to using actors across a cluster with location transparency.
Cluster Setup¶
Starting a Seed Node¶
from pulsing.actor import SystemConfig, create_actor_system
# Node 1: Start seed node
config = SystemConfig.with_addr("0.0.0.0:8000")
system = await create_actor_system(config)
# Spawn a public actor
await system.spawn(WorkerActor(), "worker", public=True)
Joining a Cluster¶
# Node 2: Join cluster
config = SystemConfig.with_addr("0.0.0.0:8001").with_seeds(["192.168.1.1:8000"])
system = await create_actor_system(config)
# Wait for cluster sync
await asyncio.sleep(1.0)
Finding Remote Actors¶
Using system.find()¶
# Find actor by name (searches entire cluster)
remote_ref = await system.find("worker")
if remote_ref:
response = await remote_ref.ask(Message.single("request", b"data"))
Checking Actor Existence¶
# Check if actor exists in cluster
if await system.has_actor("worker"):
ref = await system.find("worker")
# Use ref...
Public vs Private Actors¶
Public Actors¶
Public actors are visible to all nodes in the cluster:
# Public actor - can be found by other nodes
await system.spawn(WorkerActor(), "worker", public=True)
Private Actors¶
Private actors are only accessible locally:
Location Transparency¶
The same API works for both local and remote actors:
# Local actor
local_ref = await system.spawn(MyActor(), "local")
# Remote actor (found via cluster)
remote_ref = await system.find("remote-worker")
# Same API for both
response1 = await local_ref.ask(msg)
response2 = await remote_ref.ask(msg)
Error Handling¶
Remote actor calls can fail due to network issues:
try:
remote_ref = await system.find("worker")
if remote_ref:
response = await remote_ref.ask(msg)
else:
print("Actor not found")
except Exception as e:
print(f"Remote call failed: {e}")
Best Practices¶
- Wait for cluster sync: Add a small delay after joining a cluster
- Handle missing actors: Always check if
find()returns None - Use public actors for cluster communication: Set
public=Truefor actors that need remote access - Handle network errors: Wrap remote calls in try-except blocks
- Use timeouts: Consider adding timeouts for remote calls
Example: Distributed Counter¶
@as_actor
class DistributedCounter:
def __init__(self, init_value: int = 0):
self.value = init_value
def get(self) -> int:
return self.value
def increment(self, n: int = 1) -> int:
self.value += n
return self.value
# Node 1
system1 = await create_actor_system(SystemConfig.with_addr("0.0.0.0:8000"))
counter1 = await DistributedCounter.local(system1, init_value=0)
await system1.spawn(counter1, "counter", public=True)
# Node 2
system2 = await create_actor_system(
SystemConfig.with_addr("0.0.0.0:8001").with_seeds(["127.0.0.1:8000"])
)
await asyncio.sleep(1.0)
# Access remote counter from Node 2
remote_counter = await system2.find("counter")
if remote_counter:
value = await remote_counter.get() # 0
value = await remote_counter.increment(5) # 5
Next Steps¶
- Learn about Actor System basics
- Check Node Discovery for cluster details