mirror of
https://github.com/actix/actix-website
synced 2024-11-23 16:31:08 +01:00
Port actix book (#297)
* Tweak GHA Signed-off-by: Yuki Okushi <jtitor@2k36.org> * Port actix book Signed-off-by: Yuki Okushi <jtitor@2k36.org> * Fix links Signed-off-by: Yuki Okushi <jtitor@2k36.org> Signed-off-by: Yuki Okushi <jtitor@2k36.org>
This commit is contained in:
parent
02aba0903b
commit
1da84fca06
6
.github/workflows/build-check.yml
vendored
6
.github/workflows/build-check.yml
vendored
@ -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
|
||||
|
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use stable
|
||||
run: |
|
||||
|
39
docs/actix/sec-0-quick-start.md
Normal file
39
docs/actix/sec-0-quick-start.md
Normal file
@ -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.
|
116
docs/actix/sec-1-getting-started.md
Normal file
116
docs/actix/sec-1-getting-started.md
Normal file
@ -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<Self>;
|
||||
}
|
||||
```
|
||||
|
||||
Each actor has an execution context, for `MyActor` we are going to use `Context<A>`. 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<Ping>` trait.
|
||||
|
||||
```rust
|
||||
impl Handler<Ping> for MyActor {
|
||||
type Result = usize;
|
||||
|
||||
fn handle(&mut self, msg: Ping, _ctx: &mut Context<Self>) -> 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<A>` 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/).
|
1
docs/actix/sec-10-registry.md
Normal file
1
docs/actix/sec-10-registry.md
Normal file
@ -0,0 +1 @@
|
||||
**WIP**
|
1
docs/actix/sec-11-helper-actors.md
Normal file
1
docs/actix/sec-11-helper-actors.md
Normal file
@ -0,0 +1 @@
|
||||
**WIP**
|
253
docs/actix/sec-2-actor.md
Normal file
253
docs/actix/sec-2-actor.md
Normal file
@ -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<A>`].
|
||||
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<M>`] 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<A>`]: ./context
|
||||
[`Actor`]: https://docs.rs/actix/latest/actix/trait.Actor.html
|
||||
[`Handler<M>`]: 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<bool, std::io::Error>`.
|
||||
|
||||
```rust
|
||||
use actix::prelude::*;
|
||||
|
||||
struct Ping;
|
||||
|
||||
impl Message for Ping {
|
||||
type Result = Result<bool, std::io::Error>;
|
||||
}
|
||||
```
|
||||
|
||||
[`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<bool, std::io::Error>")]
|
||||
struct Ping;
|
||||
|
||||
// Define actor
|
||||
struct MyActor;
|
||||
|
||||
// Provide Actor implementation for our actor
|
||||
impl Actor for MyActor {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
||||
println!("Actor is alive");
|
||||
}
|
||||
|
||||
fn stopped(&mut self, ctx: &mut Context<Self>) {
|
||||
println!("Actor is stopped");
|
||||
}
|
||||
}
|
||||
|
||||
/// Define handler for `Ping` message
|
||||
impl Handler<Ping> for MyActor {
|
||||
type Result = Result<bool, std::io::Error>;
|
||||
|
||||
fn handle(&mut self, msg: Ping, ctx: &mut Context<Self>) -> 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<bool, std::io::Error>`? 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<A: Actor, M: Message> {
|
||||
fn handle(self, ctx: &mut A::Context, tx: Option<OneshotSender<M::Result>>);
|
||||
}
|
||||
```
|
||||
|
||||
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<A, M> MessageResponse<A, M> for Responses
|
||||
where
|
||||
A: Actor,
|
||||
M: Message<Result = Responses>,
|
||||
{
|
||||
fn handle(self, ctx: &mut A::Context, tx: Option<OneshotSender<M::Result>>) {
|
||||
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<Self>;
|
||||
|
||||
fn started(&mut self, _ctx: &mut Context<Self>) {
|
||||
println!("Actor is alive");
|
||||
}
|
||||
|
||||
fn stopped(&mut self, _ctx: &mut Context<Self>) {
|
||||
println!("Actor is stopped");
|
||||
}
|
||||
}
|
||||
|
||||
/// Define handler for `Messages` enum
|
||||
impl Handler<Messages> for MyActor {
|
||||
type Result = Responses;
|
||||
|
||||
fn handle(&mut self, msg: Messages, _ctx: &mut Context<Self>) -> 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),
|
||||
}
|
||||
}
|
||||
```
|
181
docs/actix/sec-3-address.md
Normal file
181
docs/actix/sec-3-address.md
Normal file
@ -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<Self>;
|
||||
}
|
||||
|
||||
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<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
||||
let addr = ctx.address();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[SyncArbiter]: ./sync-arbiter
|
||||
|
||||
## Message
|
||||
|
||||
To be able to handle a specific message the actor has to provide a
|
||||
[`Handler<M>`] 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<M>`]: 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<OrderShipped>` 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<OrderShipped>);
|
||||
|
||||
/// Actor that provides order shipped event subscriptions
|
||||
struct OrderEvents {
|
||||
subscribers: Vec<Recipient<OrderShipped>>,
|
||||
}
|
||||
|
||||
impl OrderEvents {
|
||||
fn new() -> Self {
|
||||
OrderEvents {
|
||||
subscribers: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for OrderEvents {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
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<Subscribe> for OrderEvents {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Subscribe, _: &mut Self::Context) {
|
||||
self.subscribers.push(msg.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe to ship message
|
||||
impl Handler<Ship> 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<Self>;
|
||||
}
|
||||
|
||||
impl Handler<OrderShipped> 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<Self>;
|
||||
}
|
||||
|
||||
impl Handler<OrderShipped> 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();
|
||||
}
|
||||
```
|
114
docs/actix/sec-4-context.md
Normal file
114
docs/actix/sec-4-context.md
Normal file
@ -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<Self>;
|
||||
|
||||
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<actix::Addr<MyActor>, ()>;
|
||||
}
|
||||
|
||||
impl Actor for MyActor {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
impl Handler<WhoAmI> for MyActor {
|
||||
type Result = Result<actix::Addr<MyActor>, ()>;
|
||||
|
||||
fn handle(&mut self, msg: WhoAmI, ctx: &mut Context<Self>) -> 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<Ping> for MyActor {
|
||||
type Result = usize;
|
||||
|
||||
fn handle(&mut self, msg: Ping, ctx: &mut Context<Self>) -> 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
|
133
docs/actix/sec-5-arbiter.md
Normal file
133
docs/actix/sec-5-arbiter.md
Normal file
@ -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<Actor>::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<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "usize")]
|
||||
struct Value(usize, usize);
|
||||
|
||||
impl Handler<Value> for SumActor {
|
||||
type Result = usize;
|
||||
|
||||
fn handle(&mut self, msg: Value, _ctx: &mut Context<Self>) -> Self::Result {
|
||||
msg.0 + msg.1
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayActor {}
|
||||
|
||||
impl Actor for DisplayActor {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
struct Display(usize);
|
||||
|
||||
impl Handler<Display> for DisplayActor {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Display, _ctx: &mut Context<Self>) -> 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<usize, MailboxError>`.
|
||||
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();
|
||||
}
|
||||
```
|
59
docs/actix/sec-6-sync-arbiter.md
Normal file
59
docs/actix/sec-6-sync-arbiter.md
Normal file
@ -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<Self>;
|
||||
}
|
||||
```
|
||||
|
||||
## 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<Self>;
|
||||
}
|
||||
|
||||
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.
|
1
docs/actix/sec-7-stream.md
Normal file
1
docs/actix/sec-7-stream.md
Normal file
@ -0,0 +1 @@
|
||||
**WIP**
|
1
docs/actix/sec-8-io-helpers.md
Normal file
1
docs/actix/sec-8-io-helpers.md
Normal file
@ -0,0 +1 @@
|
||||
**WIP**
|
1
docs/actix/sec-9-supervisor.md
Normal file
1
docs/actix/sec-9-supervisor.md
Normal file
@ -0,0 +1 @@
|
||||
**WIP**
|
@ -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
|
||||
|
@ -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
|
||||
|
37
sidebars.js
37
sidebars.js
@ -1,17 +1,14 @@
|
||||
module.exports = {
|
||||
docs: {
|
||||
'Introduction': [
|
||||
'welcome',
|
||||
'whatis'
|
||||
],
|
||||
'Basics': [
|
||||
Introduction: ['welcome', 'whatis'],
|
||||
Basics: [
|
||||
'getting-started',
|
||||
'application',
|
||||
'server',
|
||||
'extractors',
|
||||
'handlers',
|
||||
],
|
||||
'Advanced': [
|
||||
Advanced: [
|
||||
'errors',
|
||||
'url-dispatch',
|
||||
'request',
|
||||
@ -20,29 +17,29 @@ module.exports = {
|
||||
'middleware',
|
||||
'static-files',
|
||||
],
|
||||
'Protocols': [
|
||||
'websockets',
|
||||
'http2',
|
||||
],
|
||||
'Patterns': [
|
||||
'autoreload',
|
||||
'databases',
|
||||
],
|
||||
'Diagrams': [
|
||||
'http_server_init',
|
||||
'conn_lifecycle',
|
||||
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/'
|
||||
href: 'https://docs.rs/actix/latest/actix/',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
label: 'actix-web',
|
||||
href: 'https://docs.rs/actix-web/latest/actix_web/'
|
||||
}
|
||||
href: 'https://docs.rs/actix-web/latest/actix_web/',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user