multi_rpc/
builder.rs

1//! This module provides the `ServerBuilder` for configuring and launching multiple RPC servers
2//! from a single service implementation.
3//!
4//! When you use `multi-rpc`, your single service object is shared across all the different
5//! protocol servers you enable (e.g., tarpc, REST, jsonrpsee). To make this safe and efficient,
6//! we rely on two key synchronization primitives: `Arc` and `Mutex`.
7//!
8//! # `Arc` for Shared Ownership
9//! The `std::sync::Arc` (Atomically Reference Counted) enables your single service instance to be
10//! owned by multiple server tasks simultaneously. When you launch a server for a protocol, it
11//! receives a clone of the `Arc`, giving it a reference to the same underlying service. This
12//! prevents the need to duplicate your service's state for each server and ensures all requests
13//! are handled by the same, consistent logic.
14//!
15//! # `Mutex` for Thread-Safe Access
16//! The `tokio::sync::Mutex` provides exclusive, thread-safe access to your service object.
17//! While `Arc` allows multiple threads to own a reference, it doesn't prevent them from
18//! trying to modify the data at the same time. The `Mutex` ensures that only one server
19//! task can access or modify the service's state at any given moment. This is crucial for
20//! methods that take `&mut self` as it prevents data races and keeps your application's
21//! state consistent. We use the `tokio` version of `Mutex` because it works seamlessly
22//! with asynchronous code, allowing locks to be held across `.await` points without blocking
23//! the entire thread.
24//!
25//! # Using `ServerBuilder`
26//! You use the `ServerBuilder` to set up your server:
27//! 1. Create a new builder with your service object.
28//! 2. Use `add_protocol` to specify the protocols and network addresses for each server you want to run.
29//! 3. Call `build()` to create the servers.
30//! 4. Finally, call `run()` on the resulting `ServerRunner` to start listening for requests.
31
32use std::future::Future;
33use std::pin::Pin;
34use std::sync::Arc;
35
36use tokio::sync::Mutex;
37
38use crate::runner::ServerRunner;
39
40pub type ServerTask = Pin<Box<dyn Future<Output = ()> + Send>>;
41pub type ServerTaskFactory<S> = Box<dyn FnOnce(Arc<Mutex<S>>) -> ServerTask + Send>;
42
43pub struct ServerBuilder<S> {
44    service: Arc<Mutex<S>>,
45    task_factories: Vec<ServerTaskFactory<S>>,
46}
47
48impl<S> ServerBuilder<S>
49where
50    S: Send + Sync + 'static,
51{
52    pub fn new(service: S) -> Self {
53        Self {
54            service: Arc::new(Mutex::new(service)),
55            task_factories: Vec::new(),
56        }
57    }
58
59    /// Adds a protocol's server task factory to the builder.
60    pub fn add_protocol<F>(mut self, factory: F) -> Self
61    where
62        F: FnOnce(Arc<Mutex<S>>) -> ServerTask + Send + 'static,
63    {
64        self.task_factories.push(Box::new(factory));
65        self
66    }
67
68    pub fn build(self) -> Result<ServerRunner, std::io::Error> {
69        println!("🚀 Launching servers...");
70        let handles = self
71            .task_factories
72            .into_iter()
73            .map(|task_fn| {
74                let task = task_fn(self.service.clone());
75                tokio::spawn(task)
76            })
77            .collect();
78        Ok(ServerRunner { handles })
79    }
80}