How to make a tower service factory sync

I have a ServiceFactory:

#[derive(Debug, Clone)]
pub struct HandlerServiceFactory<S> {
    svc: S,
}

impl<S: Clone, T> Service<T> for HandlerServiceFactory<S> {
    type Response = S;
    type Error = Infallible;
    type Future = HandlerServiceFactoryFuture<S>;

    #[inline]
    fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, _target: T) -> Self::Future {
        HandlerServiceFactoryFuture { future: std::future::ready(Ok(self.svc.clone())) }
    }
}

Actually that's a code piece copy from axum. In my understand, that struct only make service by clone() and the origin Service never get touched.
Now I need a static HandlerServiceFactory instance make Service across threads. Actually I may not need that factory across threads, but a static instance need that. (wrapped by a Lazy<ArcSwap<HashMap<u8, Vec<>>>>):

Shared static variables must have a type that implements Sync

My question:
If I ONLY use the factory as a MakeService, is it safe to be Sync? If so is there a safe way to make it Sync when generic parameter S is !Sync? I understand future shouldn't be Sync.

One more question: I need call that factory quite heavily. Each data package will make about ~10 svc clone. Will there be performace issues? S is a BoxCloneService wrap a fn.

A mutable, static variable in Rust always requires an unsafe code block to access. This is the case even if your variable's type is Sync or if you are only running a single thread.

The tower::Service trait requires the receiver of call() to be a mutable ref, and therefore HandlerServiceFactory must itself be a mutable value. Therefore, you have three options:

  1. Create a static mut FACTORY and use unsafe code to access it. It would be best to wrap the unsafe code in safe functions that hide the low-level details.
  2. Make factory non-mut. This allows you to use safe-only code, but you won't be able to use tower's Service trait.
  3. Make factory non-static. Again, you can use safe-only code, but will need to create the factory instance as a singleton in your main function, and then store it in application state, e.g. in Axum see State in axum::extract - Rust

The only way to know for sure would be to benchmark your code. Regardless of which option you choose, there will need to be some kind of lock on the factory instance to allow thread-safe access, so there will definitely be performance hit. Whether or not that's significant depends on the details of your app.

2 Likes