Future is not 'Sync', when calling async fn of dyn trait obj in another async fn

So I asked this question at "async-trait" issue tracker but it looks like this is something related to not just the library.

I'm invoking an async method that in itself calls another async method of a struct (async-trait constrained method). It says 'future' is not Sync, and I tried with every possible "dyn Trait + Send + Sync", "unsafe impl Send/Sync" bandage wherever possible to no avail. The driver method is given as a box-pinned async function.

Could somebody give me a clue as to where this issue is coming from?

type Driver = Box<dyn Fn() -> Pin<Box<dyn Future<Output = Response<Body>> + Send + Sync + 'static>>
        + Send
        + Sync
        + 'static>;

// driver
let handler: Driver = Box::new(|| {
    Box::pin(async {
        outer.outer_func().await
    })
});

handler();

// Traits and structs defined somewhere else
[async_trait::async_trait]
pub trait AsyncTrait {
    fn async_func(&self);
}

pub trait AsyncTraitObj;

[async_trait::async_trait]
impl AsyncTrait for AsyncTraitObj {
     fn async_func(&self) {}
}

struct Outer {
    trait_object: Arc<dyn AsyncTrait + Send + Sync>,
}

impl Outer {
    pub fn outer_func(&self) {
        self.trait_object.async_func().await;   // warning (cause of error): future is not 'Sync' as this value is used across an await
    }
}

EDIT
I came up with a reproducible example with smaller footprint.

pub struct S {}

#[async_trait::async_trait]
pub trait Tr: Send + Sync {
    async fn a(&self)
    where
        Self: Send + Sync + 'static;
}

#[async_trait::async_trait]
impl Tr for S {
    async fn a(&self)
    where
        Self: Send + Sync + 'static,
    {
    }
}

unsafe impl Send for S {}
unsafe impl Sync for S {}

Struct A;

impl A {
    pub fn f() {
        let f: Box<
            dyn Fn() -> Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>
                + Send
                + Sync
                + 'static,
        > = Box::new(move || {
            let s: Arc<dyn Tr> = Arc::new(S {});
    
            Box::pin(async move { // future cannot be shared between threads safely
                let s = s.clone();
                s.a().await; // future is not 'Sync' as it awats another future which is not 'Sync'
            })
        });
    }
}

Generally, futures should not be required to be Sync. Send should be enough. Any time you get an error saying "this future is not Sync", the solution is to remove that requirement — you should not try to make the future Sync.

(Note that dyn Fn() is different — this one should be required to be Sync.)

Your examples are not complete (missing imports, struct written with a capital S, let statement in global scope, etc), so I'm not able to prepare an example for you.

Edit: Not -> Note

1 Like

Alice, I see. I have a feeling that something with that dyn Fn() is what I'm missing. Would this example below help for you to see what went wrong? Compiler complaints and I left the error messages in the comments. I'd really appreciate your advice.

The codebase has such dependencies.

tokio = { version = "1.21.2", features = ["full"] }
tokio-util = { version = "0.7.2", features = ["full"] }
futures = "0.3.17"
async-trait = "0.1.58"
#[async_trait::async_trait]
pub trait SomeTrait {
    async fn func(&self);
}

pub struct SomeStruct;

#[async_trait::async_trait]
impl SomeTrait for SomeStruct {
    async fn func(&self) {}
}

fn main() {
    let driver: Box<
        dyn Fn() -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + Send + Sync + 'static>>
            + Send
            + Sync
            + 'static,
    > = Box::new(move || {
        let trait_obj: std::sync::Arc<dyn SomeTrait + Send + Sync> =
            std::sync::Arc::new(SomeStruct);

        Box::pin(async move {
            // future cannot be shared between threads safely
            let trait_obj = trait_obj.clone();
            trait_obj.func().await; // future is not 'Sync' as it awaits another future which is not 'Sync'
        })
    });
}

The comment about dyn Fn() was that you should apply the following diff, removing the first Sync but not the second:

(should have been "note that", instead of "not that")

1 Like

Yes, sorry that should be Note rather than Not.

1 Like

@riking @alice

Wow.. that one piece ("Sync") of constraints had me totally lost for a few days.
I would be surprised if this will not cause trouble for other people.
Really appreciate your help!