From 8c8de9f7fb81c589c9a3b6e02dd551f3ee93c54c Mon Sep 17 00:00:00 2001 From: Rotem Yaari Date: Wed, 29 Jan 2020 14:27:22 +0200 Subject: [PATCH] Add explanations regarding actix-web's multithreaded server model (#150) * Add explanations regarding actix-web's multithreaded server model * Improve phrasing * Typo fix Co-authored-by: Yuki Okushi --- content/docs/server.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/content/docs/server.md b/content/docs/server.md index fcb4865..e0c505c 100644 --- a/content/docs/server.md +++ b/content/docs/server.md @@ -31,18 +31,53 @@ Following example shows how to start http server in separate thread. ## Multi-threading -`HttpServer` automatically starts a number of http workers, by default this number is +`HttpServer` automatically starts a number of http *workers*, by default this number is equal to number of logical CPUs in the system. This number can be overridden with the [`HttpServer::workers()`][workers] method. {{< include-example example="server" file="workers.rs" section="workers" >}} -The server creates a separate application instance for each created worker. Application state -is not shared between threads. To share state, `Arc` could be used. +Once the workers are created, they each receive a separate *application* instance to handle +requests. Application state is not shared between the threads, and handlers are free to manipulate +their copy of the state with no concurrency concerns. > Application state does not need to be `Send` or `Sync`, but application factory must be `Send` + `Sync`. +To share state between worker threads, use an `Arc`. Special care should be taken once sharing and +synchronization are introduced. In many cases, performance costs are inadvertently introduced as a +result of locking the shared state for modifications. + +In some cases these costs can be alleviated using more efficient locking strategies, for example +using [read/write locks](https://doc.rust-lang.org/std/sync/struct.RwLock.html) instead of +[mutexes](https://doc.rust-lang.org/std/sync/struct.Mutex.html) to achieve non-exclusive locking, +but the most performant implementations often tend to be ones in which no locking occurs at all. + +Since each worker thread processes its requests sequentially, handlers which block the current +thread will cause the current worker to stop processing new requests: + +```rust +fn my_handler() -> impl Responder { + std::thread::sleep(Duration::from_secs(5)); // <-- Bad practice! Will cause the current worker thread to hang! + "response" +} +``` +For this reason, any long, non-cpu-bound operation (e.g. I/O, database operations, etc.) should be +expressed as futures or asynchronous functions. Async handlers get executed concurrently by worker +threads and thus don't block execution: + +```rust +async fn my_handler() -> impl Responder { + tokio::time::delay_for(Duration::from_secs(5)).await; // <-- Ok. Worker thread will handle other requests here + "response" +} +``` + +The same limitation applies to extractors as well. When a handler function receives an argument +which implements `FromRequest`, and that implementation blocks the current thread, the worker thread +will block when running the handler. Special attention must be given when implementing extractors +for this very reason, and they should also be implemented asynchronously where needed. + ## SSL There are two features for the ssl server: `rustls` and `openssl`. The `rustls` feature is for