Initialize the HashMap

The HashMap will be shared across many tasks and potentially many threads. To support this, it is wrapped in Arc<Mutex<_>>.

First, for convenience, add the following type alias after the use statements.

  1. use bytes::Bytes;
  2. use std::collections::HashMap;
  3. use std::sync::{Arc, Mutex};
  4. type Db = Arc<Mutex<HashMap<String, Bytes>>>;

Then, update the main function to initialize the HashMap and pass an Arc handle to the process function. Using Arc allows the HashMap to be referenced concurrently from many tasks, potentially running on many threads. Throughout Tokio, the term handle is used to reference a value that provides access to some shared state.

  1. use tokio::net::TcpListener;
  2. use std::collections::HashMap;
  3. use std::sync::{Arc, Mutex};
  4. #[tokio::main]
  5. async fn main() {
  6. let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
  7. println!("Listening");
  8. let db = Arc::new(Mutex::new(HashMap::new()));
  9. loop {
  10. let (socket, _) = listener.accept().await.unwrap();
  11. // Clone the handle to the hash map.
  12. let db = db.clone();
  13. println!("Accepted");
  14. tokio::spawn(async move {
  15. process(socket, db).await;
  16. });
  17. }
  18. }

On using std::sync::Mutex

Note, std::sync::Mutex and not tokio::sync::Mutex is used to guard the HashMap. A common error is to unconditionally use tokio::sync::Mutex from within async code. An async mutex is a mutex that is locked across calls to .await.

A synchronous mutex will block the current thread when waiting to acquire the lock. This, in turn, will block other tasks from processing. However, switching to tokio::sync::Mutex usually does not help as the asynchronous mutex uses a synchronous mutex internally.

As a rule of thumb, using a synchronous mutex from within asynchronous code is fine as long as contention remains low and the lock is not held across calls to .await. Additionally, consider using parking_lot::Mutex as a faster alternative to std::sync::Mutex.