Advanced

Inter-Process Communication

Actors can communicate across process boundaries using Acton's IPC system. This enables multi-process architectures, external tooling, and polyglot systems.

When You Need IPC

  • Multi-process architectures — Separate concerns into different processes
  • External monitoring — Query actor state from monitoring tools
  • Language interop — Python, Node.js, or other languages talking to Rust actors
  • Process isolation — Crash one process without affecting others

How It Works

Acton's IPC uses Unix domain sockets for fast, local communication. Messages are serialized as JSON.

Local Only

IPC is designed for same-machine communication. For network distribution, build on top with your preferred transport.


Server Side Setup

Step 1: Mark Messages for IPC

Add the ipc option to enable serialization:

#[acton_message(ipc)]
struct GetValue;

#[acton_message(ipc)]
struct SetValue { value: i32 }

#[acton_message(ipc)]
struct ValueResponse { value: i32 }

The ipc option adds Serialize and Deserialize derives. You must still register types with the runtime.

Step 2: Register Types and Expose Actors

use acton_reactive::prelude::*;

#[acton_actor]
struct MyService {
    value: i32,
}

#[acton_main]
async fn main() {
    let mut runtime = ActonApp::launch_async().await;

    // Register IPC message types
    let registry = runtime.ipc_registry();
    registry.register::<GetValue>("GetValue");
    registry.register::<SetValue>("SetValue");
    registry.register::<ValueResponse>("ValueResponse");

    // Create and configure the service actor
    let mut service = runtime.new_actor_with_name::<MyService>("my-service".to_string());

    service
        .act_on::<GetValue>(|actor, envelope| {
            let value = actor.model.value;
            let reply_envelope = envelope.reply_envelope();

            Reply::pending(async move {
                reply_envelope.send(ValueResponse { value }).await;
            })
        })
        .mutate_on::<SetValue>(|actor, envelope| {
            actor.model.value = envelope.message().value;
            Reply::ready()
        })
        .expose_for_ipc();  // Expose using the actor's name ("my-service")

    service.start().await;

    // Start the IPC listener
    let listener = runtime.start_ipc_listener().await
        .expect("Failed to start IPC listener");

    // Keep running until Ctrl+C
    tokio::signal::ctrl_c().await.ok();

    // Graceful shutdown
    listener.shutdown_gracefully().await;
    runtime.shutdown_all().await.ok();
}

Custom IPC Names

The expose_for_ipc() method uses the actor's ERN name automatically. If you need a different IPC name, use runtime.ipc_expose("custom-name", handle) after starting the actor.


Client Side

External clients connect via Unix domain sockets using the wire protocol:

use tokio::net::UnixStream;
use acton_reactive::ipc::protocol::{write_envelope, read_response};
use acton_reactive::ipc::IpcEnvelope;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect to the socket
    let stream = UnixStream::connect("/run/user/1000/acton/ipc.sock").await?;
    let (mut reader, mut writer) = stream.into_split();

    // Create an envelope targeting the exposed actor
    let envelope = IpcEnvelope::new(
        "my-service",  // Actor name (from expose_for_ipc or ipc_expose)
        "GetValue",    // Registered type name
        serde_json::json!({}),
    );

    // Send the request
    write_envelope(&mut writer, &envelope).await?;

    // Read the response
    let response = read_response(&mut reader, 1024 * 1024).await?;
    println!("Response: {:?}", response);

    Ok(())
}

Client Libraries

Acton includes example client libraries for other languages:

Python

from acton_ipc import ActonClient

client = ActonClient("/run/user/1000/acton/ipc.sock")
response = client.send("my-service", "GetValue", {})
print(f"Value: {response}")

Node.js

import { ActonClient } from 'acton-ipc';

const client = new ActonClient('/run/user/1000/acton/ipc.sock');
const response = await client.send('my-service', 'GetValue', {});
console.log('Value:', response);

See the examples/ipc_client_libraries/ directory for complete implementations.


Security Considerations

  • Unix sockets respect file permissions
  • Set appropriate permissions on the socket file
  • Validate all incoming messages
  • Consider authentication for sensitive operations

Next

Custom Supervision — Advanced failure recovery

Previous
Testing actors