diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml index c13a638..5786b46 100644 --- a/.github/workflows/build-check.yml +++ b/.github/workflows/build-check.yml @@ -3,16 +3,14 @@ name: Build check on: pull_request: branches: - - master - # Review gh actions docs if you want to further define triggers, paths, etc - # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on + - v2 jobs: test-deploy: name: Build check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16.x @@ -21,4 +19,4 @@ jobs: - name: Install dependencies run: npm install - name: Test build website - run: npm run build \ No newline at end of file + run: npm run build diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c551946..38aea19 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use stable run: | diff --git a/docs/actix/sec-0-quick-start.md b/docs/actix/sec-0-quick-start.md new file mode 100644 index 0000000..0697427 --- /dev/null +++ b/docs/actix/sec-0-quick-start.md @@ -0,0 +1,39 @@ +--- +title: Quick start +slug: /actix +--- + +# Quick start + +Before you can start writing an actix application, you’ll need a version of Rust installed. +We recommend you use rustup to install or configure such a version. + +## Install Rust + +Before we begin, we need to install Rust using the [rustup](https://rustup.rs/) installer: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +If you already have rustup installed, run this command to ensure you have the latest version of Rust: + +```bash +rustup update +``` + +The actix framework requires Rust version 1.40.0 and up. + +## Running Examples + +The fastest way to start experimenting with actix is to clone the actix repository +and run the included examples in the examples/ directory. The following set of +commands runs the `ping` example: + +```bash +git clone https://github.com/actix/actix +cd actix +cargo run --example ping +``` + +Check [examples/](https://github.com/actix/actix/tree/master/actix/examples) directory for more examples. diff --git a/docs/actix/sec-1-getting-started.md b/docs/actix/sec-1-getting-started.md new file mode 100644 index 0000000..4536d1a --- /dev/null +++ b/docs/actix/sec-1-getting-started.md @@ -0,0 +1,116 @@ +--- +title: Getting Started +slug: /actix/getting-started +--- + +# Getting Started + +Let’s create and run our first actix application. We’ll create a new Cargo project +that depends on actix and then run the application. + +In previous section we already installed required rust version. Now let's create new cargo projects. + +## Ping actor + +Let’s write our first actix application! Start by creating a new binary-based +Cargo project and changing into the new directory: + +```bash +cargo new actor-ping +cd actor-ping +``` + +Now, add actix as a dependency of your project by ensuring your Cargo.toml +contains the following: + +```toml +[dependencies] +actix = "0.11.0" +actix-rt = "2.2" # <-- Runtime for actix +``` + +Let's create an actor that will accept a `Ping` message and respond with the number of pings processed. + +An actor is a type that implements the `Actor` trait: + +```rust +use actix::prelude::*; + +struct MyActor { + count: usize, +} + +impl Actor for MyActor { + type Context = Context; +} +``` + +Each actor has an execution context, for `MyActor` we are going to use `Context`. More information +on actor contexts is available in the next section. + +Now we need to define the `Message` that the actor needs to accept. The message can be any type +that implements the `Message` trait. + +```rust +use actix::prelude::*; + +#[derive(Message)] +#[rtype(result = "usize")] +struct Ping(usize); +``` + +The main purpose of the `Message` trait is to define a result type. The `Ping` message defines +`usize`, which indicates that any actor that can accept a `Ping` message needs to +return `usize` value. + +And finally, we need to declare that our actor `MyActor` can accept `Ping` and handle it. +To do this, the actor needs to implement the `Handler` trait. + +```rust +impl Handler for MyActor { + type Result = usize; + + fn handle(&mut self, msg: Ping, _ctx: &mut Context) -> Self::Result { + self.count += msg.0; + + self.count + } +} +``` + +That's it. Now we just need to start our actor and send a message to it. +The start procedure depends on the actor's context implementation. In our case we can use +`Context` which is tokio/future based. We can start it with `Actor::start()` +or `Actor::create()`. The first is used when the actor instance can be created immediately. +The second method is used in case we need access to the context object before we can create +the actor instance. In case of the `MyActor` actor we can use `start()`. + +All communication with actors goes through an address. You can `do_send` a message +without waiting for a response, or `send` to an actor with a specific message. +Both `start()` and `create()` return an address object. + +In the following example we are going to create a `MyActor` actor and send one message. + +Here we use the actix-rt as way to start our System and drive our main Future +so we can easily `.await` for the messages sent to the Actor. + +```rust +#[actix_rt::main] +async fn main() { + // start new actor + let addr = MyActor { count: 10 }.start(); + + // send message and get future for result + let res = addr.send(Ping(10)).await; + + // handle() returns tokio handle + println!("RESULT: {}", res.unwrap() == 20); + + // stop system and exit + System::current().stop(); +} +``` + +`#[actix_rt::main]` starts the system and block until future resolves. + +The Ping example is available in the [examples directory](https://github.com/actix/actix/tree/master/actix/examples/). diff --git a/docs/actix/sec-10-registry.md b/docs/actix/sec-10-registry.md new file mode 100644 index 0000000..a1c6d3a --- /dev/null +++ b/docs/actix/sec-10-registry.md @@ -0,0 +1 @@ +**WIP** diff --git a/docs/actix/sec-11-helper-actors.md b/docs/actix/sec-11-helper-actors.md new file mode 100644 index 0000000..a1c6d3a --- /dev/null +++ b/docs/actix/sec-11-helper-actors.md @@ -0,0 +1 @@ +**WIP** diff --git a/docs/actix/sec-2-actor.md b/docs/actix/sec-2-actor.md new file mode 100644 index 0000000..cb1701b --- /dev/null +++ b/docs/actix/sec-2-actor.md @@ -0,0 +1,253 @@ +--- +title: Actor +slug: /actix/actor +--- + +# Actor + +Actix is a rust library providing a framework for developing concurrent applications. + +Actix is built on the [Actor Model] which +allows applications to be written as a group of independently executing but cooperating +"Actors" which communicate via messages. Actors are objects which encapsulate +state and behavior and run within the *Actor System* provided by the actix library. + +Actors run within a specific execution context [`Context`]. +The context object is available only during execution. Each actor has a separate +execution context. The execution context also controls the lifecycle of an actor. + +Actors communicate exclusively by exchanging messages. The sending actor can +optionally wait for the response. Actors are not referenced directly, but by means +of addresses. + +Any rust type can be an actor, it only needs to implement the [`Actor`] trait. + +To be able to handle a specific message the actor has to provide a +[`Handler`] implementation for this message. All messages +are statically typed. The message can be handled in an asynchronous fashion. +Actor can spawn other actors or add futures or streams to execution context. +The `Actor` trait provides several methods that allow controlling the actor's lifecycle. + +[Actor Model]: https://en.wikipedia.org/wiki/Actor_model +[`Context`]: ./context +[`Actor`]: https://docs.rs/actix/latest/actix/trait.Actor.html +[`Handler`]: https://docs.rs/actix/latest/actix/trait.Handler.html + +## Actor lifecycle + +### Started + +An actor always starts in the `Started` state. During this state the actor's `started()` +method is called. The `Actor` trait provides a default implementation for this method. +The actor context is available during this state and the actor can start more actors or register +async streams or do any other required configuration. + +### Running + +After an Actor's `started()` method is called, the actor transitions to the `Running` state. +The Actor can stay in `running` state indefinitely. + +### Stopping + +The Actor's execution state changes to the `stopping` state in the following situations: + +* `Context::stop` is called by the actor itself +* all addresses to the actor get dropped. i.e. no other actor references it. +* no event objects are registered in the context. + +An actor can restore from the `stopping` state to the `running` state by creating a new +address or adding an event object, and by returning `Running::Continue`. + +If an actor changed state to `stopping` because `Context::stop()` is called +then the context immediately stops processing incoming messages and calls +`Actor::stopping()`. If the actor does not restore back to the `running` state, all +unprocessed messages are dropped. + +By default this method returns `Running::Stop` which confirms the stop operation. + +### Stopped + +If the actor does not modify the execution context during the stopping state, the actor state changes +to `Stopped`. This state is considered final and at this point the actor is dropped. + +## Message + +An Actor communicates with other actors by sending messages. In actix all +messages are typed. A message can be any rust type which implements the +[`Message`] trait. `Message::Result` defines the return type. +Let's define a simple `Ping` message - an actor which will accept this message needs to return +`Result`. + +```rust +use actix::prelude::*; + +struct Ping; + +impl Message for Ping { + type Result = Result; +} +``` + +[`Message`]: https://docs.rs/actix/latest/actix/trait.Message.html + +## Spawning an actor + +How to start an actor depends on its context. Spawning a new async actor +is achieved via the `start` and `create` methods of +the [`Actor`] trait. It provides several different ways of +creating actors; for details check the docs. + +## Complete example + +```rust +use actix::prelude::*; + +/// Define message +#[derive(Message)] +#[rtype(result = "Result")] +struct Ping; + +// Define actor +struct MyActor; + +// Provide Actor implementation for our actor +impl Actor for MyActor { + type Context = Context; + + fn started(&mut self, ctx: &mut Context) { + println!("Actor is alive"); + } + + fn stopped(&mut self, ctx: &mut Context) { + println!("Actor is stopped"); + } +} + +/// Define handler for `Ping` message +impl Handler for MyActor { + type Result = Result; + + fn handle(&mut self, msg: Ping, ctx: &mut Context) -> Self::Result { + println!("Ping received"); + + Ok(true) + } +} + +#[actix_rt::main] +async fn main() { + // Start MyActor in current thread + let addr = MyActor.start(); + + // Send Ping message. + // send() message returns Future object, that resolves to message result + let result = addr.send(Ping).await; + + match result { + Ok(res) => println!("Got result: {}", res.unwrap()), + Err(err) => println!("Got error: {}", err), + } +} +``` + +## Responding with a MessageResponse + +Let's take a look at the `Result` type defined for the `impl Handler` in the above example. +See how we're returning a `Result`? We're able to respond to our actor's +incoming message with this type because it has the `MessageResponse` trait implemented for that type. +Here's the definition for that trait: + +```rust +pub trait MessageResponse { + fn handle(self, ctx: &mut A::Context, tx: Option>); +} +``` + +Sometimes it makes sense to respond to incoming messages with types that don't have this trait +implemented for them. When that happens we can implement the trait ourselves. +Here's an example where we're responding to a `Ping` message with a `GotPing`, +and responding with `GotPong` for a `Pong` message. + +```rust +use actix::dev::{MessageResponse, OneshotSender}; +use actix::prelude::*; + +#[derive(Message)] +#[rtype(result = "Responses")] +enum Messages { + Ping, + Pong, +} + +enum Responses { + GotPing, + GotPong, +} + +impl MessageResponse for Responses +where + A: Actor, + M: Message, +{ + fn handle(self, ctx: &mut A::Context, tx: Option>) { + if let Some(tx) = tx { + tx.send(self); + } + } +} + +// Define actor +struct MyActor; + +// Provide Actor implementation for our actor +impl Actor for MyActor { + type Context = Context; + + fn started(&mut self, _ctx: &mut Context) { + println!("Actor is alive"); + } + + fn stopped(&mut self, _ctx: &mut Context) { + println!("Actor is stopped"); + } +} + +/// Define handler for `Messages` enum +impl Handler for MyActor { + type Result = Responses; + + fn handle(&mut self, msg: Messages, _ctx: &mut Context) -> Self::Result { + match msg { + Messages::Ping => Responses::GotPing, + Messages::Pong => Responses::GotPong, + } + } +} + +#[actix_rt::main] +async fn main() { + // Start MyActor in current thread + let addr = MyActor.start(); + + // Send Ping message. + // send() message returns Future object, that resolves to message result + let ping_future = addr.send(Messages::Ping).await; + let pong_future = addr.send(Messages::Pong).await; + + match pong_future { + Ok(res) => match res { + Responses::GotPing => println!("Ping received"), + Responses::GotPong => println!("Pong received"), + }, + Err(e) => println!("Actor is probably dead: {}", e), + } + + match ping_future { + Ok(res) => match res { + Responses::GotPing => println!("Ping received"), + Responses::GotPong => println!("Pong received"), + }, + Err(e) => println!("Actor is probably dead: {}", e), + } +} +``` diff --git a/docs/actix/sec-3-address.md b/docs/actix/sec-3-address.md new file mode 100644 index 0000000..f134e2d --- /dev/null +++ b/docs/actix/sec-3-address.md @@ -0,0 +1,181 @@ +--- +title: Address +slug: /actix/address +--- + +# Address + +Actors communicate exclusively by exchanging messages. The sending actor can optionally +wait for the response. Actors cannot be referenced directly, only by their addresses. + +There are several ways to get the address of an actor. The `Actor` trait provides +two helper methods for starting an actor. Both return the address of the started actor. + +Here is an example of `Actor::start()` method usage. In this example `MyActor` actor +is asynchronous and is started in the same thread as the caller - threads are covered in +the [SyncArbiter] chapter. + +```rust +struct MyActor; +impl Actor for MyActor { + type Context = Context; +} + +let addr = MyActor.start(); +``` + +An async actor can get its address from the `Context` struct. The context needs to +implement the `AsyncContext` trait. `AsyncContext::address()` provides the actor's address. + +```rust +struct MyActor; + +impl Actor for MyActor { + type Context = Context; + + fn started(&mut self, ctx: &mut Context) { + let addr = ctx.address(); + } +} +``` + +[SyncArbiter]: ./sync-arbiter + +## Message + +To be able to handle a specific message the actor has to provide a +[`Handler`] implementation for this message. +All messages are statically typed. The message can be handled in an asynchronous +fashion. The actor can spawn other actors or add futures or +streams to the execution context. The actor trait provides several methods that allow +controlling the actor's lifecycle. + +To send a message to an actor, the `Addr` object needs to be used. `Addr` provides several +ways to send a message. + + * `Addr::do_send(M)` - this method ignores any errors in message sending. If the mailbox + is full the message is still queued, bypassing the limit. If the actor's mailbox is closed, + the message is silently dropped. This method does not return the result, so if the + mailbox is closed and a failure occurs, you won't have an indication of this. + + * `Addr::try_send(M)` - this method tries to send the message immediately. If + the mailbox is full or closed (actor is dead), this method returns a + [`SendError`]. + + * `Addr::send(M)` - This message returns a future object that resolves to a result + of a message handling process. If the returned `Future` object is dropped, the + message is cancelled. + +[`Handler`]: https://docs.rs/actix/latest/actix/trait.Handler.html +[`SendError`]: https://docs.rs/actix/latest/actix/prelude/enum.SendError.html + +## Recipient + +Recipient is a specialized version of an address that supports only one type of message. +It can be used in case the message needs to be sent to a different type of actor. +A recipient object can be created from an address with `Addr::recipient()`. + +Address objects require an actor type, but if we just want to send a specific message +to an actor that can handle the message, we can use the Recipient interface. + +For example recipient can be used for a subscription system. In the following example +`OrderEvents` actor sends a `OrderShipped` message to all subscribers. A subscriber can +be any actor that implements the `Handler` trait. + +```rust +use actix::prelude::*; + +#[derive(Message)] +#[rtype(result = "()")] +struct OrderShipped(usize); + +#[derive(Message)] +#[rtype(result = "()")] +struct Ship(usize); + +/// Subscribe to order shipped event. +#[derive(Message)] +#[rtype(result = "()")] +struct Subscribe(pub Recipient); + +/// Actor that provides order shipped event subscriptions +struct OrderEvents { + subscribers: Vec>, +} + +impl OrderEvents { + fn new() -> Self { + OrderEvents { + subscribers: vec![] + } + } +} + +impl Actor for OrderEvents { + type Context = Context; +} + +impl OrderEvents { + /// Send event to all subscribers + fn notify(&mut self, order_id: usize) { + for subscr in &self.subscribers { + subscr.do_send(OrderShipped(order_id)); + } + } +} + +/// Subscribe to shipment event +impl Handler for OrderEvents { + type Result = (); + + fn handle(&mut self, msg: Subscribe, _: &mut Self::Context) { + self.subscribers.push(msg.0); + } +} + +/// Subscribe to ship message +impl Handler for OrderEvents { + type Result = (); + fn handle(&mut self, msg: Ship, ctx: &mut Self::Context) -> Self::Result { + self.notify(msg.0); + System::current().stop(); + } +} + +/// Email Subscriber +struct EmailSubscriber; +impl Actor for EmailSubscriber { + type Context = Context; +} + +impl Handler for EmailSubscriber { + type Result = (); + fn handle(&mut self, msg: OrderShipped, _ctx: &mut Self::Context) -> Self::Result { + println!("Email sent for order {}", msg.0) + } + +} +struct SmsSubscriber; +impl Actor for SmsSubscriber { + type Context = Context; +} + +impl Handler for SmsSubscriber { + type Result = (); + fn handle(&mut self, msg: OrderShipped, _ctx: &mut Self::Context) -> Self::Result { + println!("SMS sent for order {}", msg.0) + } + +} + +fn main() { + let system = System::new("events"); + let email_subscriber = Subscribe(EmailSubscriber{}.start().recipient()); + let sms_subscriber = Subscribe(SmsSubscriber{}.start().recipient()); + let order_event = OrderEvents::new().start(); + order_event.do_send(email_subscriber); + order_event.do_send(sms_subscriber); + order_event.do_send(Ship(1)); + system.run(); +} +``` diff --git a/docs/actix/sec-4-context.md b/docs/actix/sec-4-context.md new file mode 100644 index 0000000..569987f --- /dev/null +++ b/docs/actix/sec-4-context.md @@ -0,0 +1,114 @@ +--- +title: Context +slug: /actix/context +--- + +# Context + +Actors all maintain an internal execution context, or state. This +allows an actor to determine its own Address, change mailbox limits, +or stop its execution. + +## Mailbox + +All messages go to the actor's mailbox first, then the actor's execution context +calls specific message handlers. Mailboxes in general are bounded. The capacity is +specific to the context implementation. For the `Context` type the capacity is set to +16 messages by default and can be increased with [`Context::set_mailbox_capacity()`]. + +```rust +struct MyActor; + +impl Actor for MyActor { + type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + ctx.set_mailbox_capacity(1); + } +} + +let addr = MyActor.start(); +``` + +Remember that this doesn't apply to `Addr::do_send(M)` which bypasses the Mailbox queue limit, or +`AsyncContext::notify(M)` and `AsyncContext::notify_later(M, Duration)` which bypasses the mailbox +entirely. + +[`Context::set_mailbox_capacity()`]: https://docs.rs/actix/latest/actix/struct.Context.html#method.set_mailbox_capacity + +## Getting your actors Address + +An actor can view its own address from its context. Perhaps you want to requeue an event for +later, or you want to transform the message type. Maybe you want to respond with your address +to a message. If you want an actor to send a message to itself, have a look at +`AsyncContext::notify(M)` instead. + +To get your address from the context you call [`Context::address()`]. An example is: + +```rust +struct MyActor; + +struct WhoAmI; + +impl Message for WhoAmI { + type Result = Result, ()>; +} + +impl Actor for MyActor { + type Context = Context; +} + +impl Handler for MyActor { + type Result = Result, ()>; + + fn handle(&mut self, msg: WhoAmI, ctx: &mut Context) -> Self::Result { + Ok(ctx.address()) + } +} + +let who_addr = addr.do_send(WhoAmI{}); +``` + +[`Context::address()`]: https://docs.rs/actix/latest/actix/struct.Context.html#method.address + +## Stopping an Actor + +From within the actors execution context you can choose to stop the actor from processing +any future Mailbox messages. This could be in response to an error condition, or as part +of program shutdown. To do this you call [`Context::stop()`]. + +This is an adjusted Ping example that stops after 4 pings are received. + +```rust +impl Handler for MyActor { + type Result = usize; + + fn handle(&mut self, msg: Ping, ctx: &mut Context) -> Self::Result { + self.count += msg.0; + + if self.count > 5 { + println!("Shutting down ping receiver."); + ctx.stop() + } + + self.count + } +} + +#[actix_rt::main] +async fn main() { + // start new actor + let addr = MyActor { count: 10 }.start(); + + // send message and get future for result + let addr_2 = addr.clone(); + let res = addr.send(Ping(6)).await; + + match res { + Ok(_) => assert!(addr_2.try_send(Ping(6)).is_err()), + _ => {} + } +} +``` + +[`Context::stop()`]: https://docs.rs/actix/latest/actix/struct.Context.html#method.stop diff --git a/docs/actix/sec-5-arbiter.md b/docs/actix/sec-5-arbiter.md new file mode 100644 index 0000000..4196554 --- /dev/null +++ b/docs/actix/sec-5-arbiter.md @@ -0,0 +1,133 @@ +--- +title: Arbiter +slug: /actix/arbiter +--- + +# Arbiter + +`Arbiter`s provide an asynchronous execution context for `Actor`s, `functions` and `futures`. Where an +actor contains a `Context` that defines its Actor specific execution state, +Arbiters host the environment where an actor runs. + +As a result Arbiters perform a number of functions. Most notably, they are able +to spawn a new OS thread, run an event loop, spawn tasks asynchronously on that +event loop, and act as helpers for asynchronous tasks. + +## System and Arbiter + +In all our previous code examples the function `System::new` creates an Arbiter +for your actors to run inside. When you call `start()` on your actor it is then +running inside of the System Arbiter's thread. In many cases, this is all you +will need for a program using Actix. + +While it only uses one thread, it uses the very efficient event loop pattern +which works well for asynchronous events. To handle synchronous, CPU-bound +tasks, it's better to avoid blocking the event loop and instead offload the +computation to other threads. For this usecase, read the next section and +consider using [`SyncArbiter`](./sync-arbiter). + +## The event loop + +One `Arbiter` is in control of one thread with one event pool. When an Arbiter +spawns a task (via `Arbiter::spawn`, `Context::run_later`, or similar +constructs), the Arbiter queues the task for execution on that task queue. When +you think `Arbiter`, you can think "single-threaded event loop". + +Actix in general does support concurrency, but normal `Arbiter`s (not +`SyncArbiter`s) do not. To use Actix in a concurrent way, you can spin up +multiple `Arbiter`s using `Arbiter::new`, `ArbiterBuilder`, or `Arbiter::start`. + +When you create a new Arbiter, this creates a new execution context for Actors. +The new thread is available to add new Actors to it, but Actors cannot freely +move between Arbiters: they are tied to the Arbiter they were spawned in. +However, Actors on different Arbiters can still communicate with each other +using the normal `Addr`/`Recipient` methods. The method of passing messages is +agnostic to whether the Actors are running on the same or different Arbiters. + +## Using Arbiter for resolving async events + +If you aren't an expert in Rust Futures, Arbiter can be a helpful and simple +wrapper to resolving async events in order. Consider we have two actors, A and +B, and we want to run an event on B only once a result from A is completed. We +can use `Arbiter::spawn` to assist with this task. + +```rust +use actix::prelude::*; + +struct SumActor {} + +impl Actor for SumActor { + type Context = Context; +} + +#[derive(Message)] +#[rtype(result = "usize")] +struct Value(usize, usize); + +impl Handler for SumActor { + type Result = usize; + + fn handle(&mut self, msg: Value, _ctx: &mut Context) -> Self::Result { + msg.0 + msg.1 + } +} + +struct DisplayActor {} + +impl Actor for DisplayActor { + type Context = Context; +} + +#[derive(Message)] +#[rtype(result = "()")] +struct Display(usize); + +impl Handler for DisplayActor { + type Result = (); + + fn handle(&mut self, msg: Display, _ctx: &mut Context) -> Self::Result { + println!("Got {:?}", msg.0); + } +} + +fn main() { + let system = System::new("single-arbiter-example"); + + // Define an execution flow using futures + let execution = async { + // `Actor::start` spawns the `Actor` on the *current* `Arbiter`, which + // in this case is the System arbiter + let sum_addr = SumActor {}.start(); + let dis_addr = DisplayActor {}.start(); + + // Start by sending a `Value(6, 7)` to our `SumActor`. + // `Addr::send` responds with a `Request`, which implements `Future`. + // When awaited, it will resolve to a `Result`. + let sum_result = sum_addr.send(Value(6, 7)).await; + + match sum_result { + Ok(res) => { + // `res` is now the `usize` returned from `SumActor` as a response to `Value(6, 7)` + // Once the future is complete, send the successful response (`usize`) + // to the `DisplayActor` wrapped in a `Display + dis_addr.send(Display(res)).await; + } + Err(e) => { + eprintln!("Encountered mailbox error: {:?}", e); + } + }; + }; + + // Spawn the future onto the current Arbiter/event loop + Arbiter::spawn(execution); + + // We only want to do one computation in this example, so we + // shut down the `System` which will stop any Arbiters within + // it (including the System Arbiter), which will in turn stop + // any Actor Contexts running within those Arbiters, finally + // shutting down all Actors. + System::current().stop(); + + system.run(); +} +``` diff --git a/docs/actix/sec-6-sync-arbiter.md b/docs/actix/sec-6-sync-arbiter.md new file mode 100644 index 0000000..afc92b1 --- /dev/null +++ b/docs/actix/sec-6-sync-arbiter.md @@ -0,0 +1,59 @@ +--- +title: SyncArbiter +slug: /actix/sync-arbiter +--- + +# SyncArbiter + +When you normally run Actors, there are multiple Actors running on the +System's Arbiter thread, using its event loop. However for CPU bound workloads, +or highly concurrent workloads, you may wish to have an Actor running multiple +instances in parallel. + +This is what a SyncArbiter provides - the ability to launch multiple instances of +an Actor on a pool of OS threads. + +It's important to note a SyncArbiter can only host a single type of Actor. This means +you need to create a SyncArbiter for each type of Actor you want to run in this +manner. + +## Creating a Sync Actor + +When implementing your Actor to be run on a SyncArbiter, it requires that your Actor's +Context is changed from `Context` to `SyncContext`. + +```rust +use actix::prelude::*; + +struct MySyncActor; + +impl Actor for MySyncActor { + type Context = SyncContext; +} +``` + +## Starting the Sync Arbiter + +Now that we have defined a Sync Actor, we can run it on a thread pool, created by +our `SyncArbiter`. We can only control the number of threads at SyncArbiter creation +time - we can't add/remove threads later. + +```rust +use actix::prelude::*; + +struct MySyncActor; + +impl Actor for MySyncActor { + type Context = SyncContext; +} + +let addr = SyncArbiter::start(2, || MySyncActor); +``` + +We can communicate with the addr the same way as we have with our previous Actors +that we started. We can send messages, receive futures and results, and more. + +## Sync Actor Mailboxes + +Sync Actors have no Mailbox limits, but you should still use `do_send`, `try_send` and `send` +as normal to account for other possible errors or sync vs async behavior. diff --git a/docs/actix/sec-7-stream.md b/docs/actix/sec-7-stream.md new file mode 100644 index 0000000..a1c6d3a --- /dev/null +++ b/docs/actix/sec-7-stream.md @@ -0,0 +1 @@ +**WIP** diff --git a/docs/actix/sec-8-io-helpers.md b/docs/actix/sec-8-io-helpers.md new file mode 100644 index 0000000..a1c6d3a --- /dev/null +++ b/docs/actix/sec-8-io-helpers.md @@ -0,0 +1 @@ +**WIP** diff --git a/docs/actix/sec-9-supervisor.md b/docs/actix/sec-9-supervisor.md new file mode 100644 index 0000000..a1c6d3a --- /dev/null +++ b/docs/actix/sec-9-supervisor.md @@ -0,0 +1 @@ +**WIP** diff --git a/docs/extractors.md b/docs/extractors.md index 27384a3..5abda12 100644 --- a/docs/extractors.md +++ b/docs/extractors.md @@ -93,7 +93,7 @@ Be careful when using blocking synchronization primitives like `Mutex` or `RwLoc [payload]: https://docs.rs/actix-web/4/actix_web/web/struct.Payload.html [payloadexample]: https://docs.rs/actix-web/4/actix_web/web/struct.Payload.html [docsrs_match_info]: https://docs.rs/actix-web/latest/actix_web/struct.HttpRequest.html#method.match_info -[actix]: https://actix.github.io/actix/actix/ +[actix]: /actix/docs/ [atomics]: https://doc.rust-lang.org/std/sync/atomic/ [shared_mutable_state]: /docs/application#shared-mutable-state [critical_section]: https://en.wikipedia.org/wiki/Critical_section diff --git a/docs/welcome.md b/docs/welcome.md index 0cb5a34..dc18659 100644 --- a/docs/welcome.md +++ b/docs/welcome.md @@ -8,9 +8,9 @@ slug: / Actix Web lets you quickly and confidently develop web services in Rust and this guide will get you going in no time. -The documentation on this website focusses primarily on the Actix Web framework. For information about the actor framework called Actix, check out the [Actix book][actix-book] (or the lower level [actix API docs][actix-docs]). Otherwise, head on to the [getting started guide][getting-started]. If you already know your way around and you need specific information you might want to read the [actix-web API docs][actix-web-docs]. +The documentation on this website focusses primarily on the Actix Web framework. For information about the actor framework called Actix, check out the [Actix chapter][actix-chapter] (or the lower level [actix API docs][actix-docs]). Otherwise, head on to the [getting started guide][getting-started]. If you already know your way around and you need specific information you might want to read the [actix-web API docs][actix-web-docs]. [getting-started]: ./getting-started [actix-web-docs]: https://docs.rs/actix-web [actix-docs]: https://docs.rs/actix -[actix-book]: https://actix.rs/book/actix +[actix-book]: ./actix diff --git a/sidebars.js b/sidebars.js index 1e2fe61..3e4fbdb 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1,48 +1,45 @@ module.exports = { - docs: { - 'Introduction': [ - 'welcome', - 'whatis' - ], - 'Basics': [ - 'getting-started', - 'application', - 'server', - 'extractors', - 'handlers', - ], - 'Advanced': [ - 'errors', - 'url-dispatch', - 'request', - 'response', - 'testing', - 'middleware', - 'static-files', - ], - 'Protocols': [ - 'websockets', - 'http2', - ], - 'Patterns': [ - 'autoreload', - 'databases', - ], - 'Diagrams': [ - 'http_server_init', - 'conn_lifecycle', - ], - 'API Documentation': [ - { - type: 'link', - label: 'actix', - href: 'https://docs.rs/actix/latest/actix/' - }, - { - type: 'link', - label: 'actix-web', - href: 'https://docs.rs/actix-web/latest/actix_web/' - } - ], - }, + docs: { + Introduction: ['welcome', 'whatis'], + Basics: [ + 'getting-started', + 'application', + 'server', + 'extractors', + 'handlers', + ], + Advanced: [ + 'errors', + 'url-dispatch', + 'request', + 'response', + 'testing', + 'middleware', + 'static-files', + ], + Protocols: ['websockets', 'http2'], + Patterns: ['autoreload', 'databases'], + Diagrams: ['http_server_init', 'conn_lifecycle'], + Actix: [ + 'actix/sec-0-quick-start', + 'actix/sec-1-getting-started', + 'actix/sec-2-actor', + 'actix/sec-3-address', + 'actix/sec-4-context', + 'actix/sec-5-arbiter', + 'actix/sec-6-sync-arbiter', + ], + 'API Documentation': [ + { + type: 'link', + label: 'actix', + href: 'https://docs.rs/actix/latest/actix/', + }, + { + type: 'link', + label: 'actix-web', + href: 'https://docs.rs/actix-web/latest/actix_web/', + }, + ], + }, };