ractor

repository·main·Indexed 24 days ago

https://github.com/slawlor/ractor

A pure-Rust actor framework inspired by Erlang's gen_server. It provides primitives for building highly concurrent, distributed systems with features such as supervision trees, RPC, and process groups. The framework ensures that actors execute handler functions sequentially, eliminating the need for manual synchronization of internal state. It includes an optional cluster extension via the ractor_cluster crate for managing actors over a network.

Tokens
25.7K
Snippets
45
Records
126
Agent score
84%

What's inside ractor

  1. Difference between Signal (Kill) and Stop

    main

    Choosing between Signal::Kill and Stop depends on whether you need a graceful shutdown:

    • Signal (Kill):
      • Highest priority and immediate.
      • Terminates all work, including currently executing async handlers.
      • Cancellation is immediate and not graceful.
      • Use for forced termination or emergency shutdowns.
    • Stop:
      • Graceful termination: currently executing async work is allowed to finish.
      • The actor transitions to exiting on the next message processing iteration.
      • Use for normal shutdowns where cleanup logic must run.

    Important: Stop does not interrupt running handlers. If you require an immediate stop, send Kill.

  2. Manage actor state using pre_start

    main

    In ractor, an actor's internal state is decoupled from the actor struct itself (self). This design ensures safety and predictable ownership.

    • State Initialization: Use the pre_start routine to initialize and set up the actor's state (e.g., opening network sockets, database connections, or initializing counters).
    • Error Handling: Because pre_start can perform fallible operations, any panics occurring during initialization are captured and returned to the caller of Actor::spawn.
    • Self vs. State: The actor's self is passed as a read-only reference and should ideally only contain configuration or startup information. For owned values used to initialize state, use the Arguments provided to the Actor trait.
  3. Handle supervision and actor failures

    main

    Supervision allows actors to monitor and respond to child lifecycle events.

    • Supervision Events: These notify supervising actors of events such as started, exited, spawned with error, or panicked.
    • Panic Handling:
      • Panics inside pre_start are captured and returned to the spawner.
      • Panics during message processing are caught and surfaced as supervision events (unless the binary is built with panic = "abort").
    • Restart Policies: Supervisors decide whether to restart or stop a child by handling these supervision events and using provided restart utilities.
  4. Use ractor_cluster for distributed actors

    main

    To build a distributed pool of actors, use the ractor_cluster crate. It provides a NodeServer to manage host processes, which handles incoming/outgoing NodeSession actors, TcpListener sockets, and actor lifecycle management across nodes.

    When using ractor_cluster, actors are made available on remote systems via RemoteActors. These are untyped actors that handle serialized messages, leaving deserialization to the originating system.

  5. How actors work in ractor

    main
    Actors in ractor are lightweight, thread-safe entities. A key guarantee of the framework is that an actor will only call one of its handler functions at a time; they are never executed in parallel. This allows you to model microservices with well-defined state and processing logic without manual synchronization for the actor's internal state.
  6. Generate local message enums automatically

    main

    For local actors, you can skip defining an explicit enum by using the message = [visibility] enum EnumName syntax in the #[ractor::actor] attribute. The macro will generate the enum based on your handler patterns and parameter types.

    Rules for generated enums:

    • Every field in a pattern must have a named binding so the macro can determine its type.
    • Supports unit, tuple, and struct variants.
    • The optional visibility (e.g., pub, pub(crate)) defaults to private.
    • Generated enums do not include cluster serialization derives or wire contracts. For cluster builds, you must manually implement ractor::Message for the generated enum if it is used for local messaging.
    • Generated enums do not support generic actor implementations.
    struct Counter;
    
    #[ractor::actor(message = pub enum CounterMessage, state = i64, arguments = i64)]
    impl Counter {
        async fn pre_start(
            &self,
            _myself: ActorRef<CounterMessage>,
            initial: i64,
        ) -> Result<i64, ActorProcessingErr> {
            Ok(initial)
        }
    
        #[ractor::message(Add { amount })]
        fn add(&self, amount: i64, state: &mut i64) {
            *state += amount;
        }
    
        #[ractor::rpc(Read)]
        fn read(&self, state: &i64) -> i64 {
            *state
        }
    }
  7. How message priority and ordering work in ractor

    main

    Messages in ractor are multiplexed into four prioritized channels. Higher priority channels are processed before lower ones:

    1. Signals (highest): e.g., Signal::Kill. These immediately interrupt processing and terminate execution.
    2. Stop: A graceful stop request. Ongoing asynchronous work is allowed to complete. On the actor's next processing iteration, Stop takes priority over supervision events and user messages.
    3. SupervisionEvent: Lifecycle notifications from child actors (startup, exit, panic).
    4. Messages (lowest): User-defined messages sent via cast or call.

    Ordering Guarantees

    • Per-sender FIFO: Messages sent from a single sender to a single actor are delivered in the order they were sent (FIFO) for the user message channel when using ActorRef APIs.
    • Cross-sender ordering: There is no ordering guarantee between messages from different senders.
    • Priority preemption: Higher-priority messages can preempt lower-priority ones in the queue. For example, a Stop message will be processed before later-arriving user messages.
    • Local vs Remote: Local ordering is guaranteed per-sender. In ractor_cluster, ordering depends on the transport; it can be preserved per-connection but is not guaranteed across reconnections or multi-path routing.
  8. Understand actor message priorities

    main

    Actors communicate by passing messages. ractor supports four concurrent message types, which are processed in a specific priority order. Understanding this order is crucial for managing actor lifecycles and work flow:

    1. Signals (Highest Priority): These interrupt the actor immediately, including any currently executing async work. Currently, the only signal is Signal::Kill, which terminates all work (message processing or supervision events) immediately.
    2. Stop: A graceful exit. It does not terminate currently executing async work. Instead, it ensures that once the current work completes, the Stop signal takes priority over future supervision events or regular messages on the next iteration.
    3. SupervisionEvent: Messages sent from child actors to their supervisors regarding startup, death, or unhandled panics. These allow parents or peers to monitor and handle lifetime events. Note: If panic = 'abort' is set in Cargo.toml, panics will terminate the program instead of being caught in the supervision flow.
    4. Messages (Lowest Priority): Regular, user-defined messages used for the actor's primary work.
  9. Configure ractor_cluster for remote messaging

    main

    When using ractor_cluster for distributed computing, be aware of the following:

    • Remote Actors: Represented by RemoteActor wrappers containing serialized payloads. Deserialization occurs on the owning node.
    • Serialization: Cluster messages must implement BytesConvertable (or be produced via procedural macros for prost types).
    • Network Reliability:
      • There is no distributed global ordering guarantee.
      • Reconnection semantics are best-effort and may result in message loss. Implement application-level retries or acknowledgements if reliability is required.
    • Security: The cluster supports authentication and TLS (currently considered experimental).
  10. Understand ractor core guarantees and actor lifecycle

    main

    When building with ractor, keep the following execution and state rules in mind:

    • Single-message processing: An actor processes at most one message handler at a time. Handlers for a single actor are never executed in parallel.
    • Pre-start initialization: Actor state is initialized in the pre_start method. This method runs at spawn time and can fail; if it fails, the error is returned to the caller of Actor::spawn.
    • Actor self vs state: The self object is read-only and intended for configuration. All mutable actor state must reside in the State type returned by pre_start.
    • Message requirements: User message types must implement Send + 'static. For use in a ractor_cluster, messages must also implement network serialization traits (ractor::Message via derive macros, and ractor_cluster::BytesConvertable for payloads).
  11. Run unit tests for wasm32-unknown-unknown in a browser

    main

    Because wasm32-unknown-unknown unit tests cannot be executed in CI due to known issues, you must run them manually in a browser.

    1. Build and Start: Use wasm-pack test specifying your browser (e.g., firefox) and the package path ./ractor.
    2. Access Tests: Once the build completes, wasm-pack will start a web server. Access the interactive tests at http://127.0.0.1:8000.
    3. Monitor: You can monitor the test progress and results directly in the browser.
    4. Cleanup: Press Ctrl+C in your terminal to shut down the web server when finished.
    wasm-pack test --firefox ./ractor
  12. Run the Ractor advanced benchmark suite

    main

    To analyze Ractor's performance characteristics, you can run the advanced benchmark suite using cargo bench. The benchmarks are located in the simple_advanced_benchmarks benchmark target.

    Run all advanced benchmarks

    cargo bench --bench simple_advanced_benchmarks

    Run specific benchmark categories

    You can filter benchmarks by category using the following flags:

    • Small messages: cargo bench --bench simple_advanced_benchmarks -- small_messages
    • Large messages: cargo bench --bench simple_advanced_benchmarks -- large_messages
    • Spawn rate: cargo bench --bench simple_advanced_benchmarks -- spawn
    cargo bench --bench simple_advanced_benchmarks