Tokio/async puzzle

It seems like I've messed up something with async and I would appreciate any help.
The code in question is here:

Which simply does tokio::spawn on some future.
This results in compilation error:

error: implementation of `std::marker::Send` is not general enough
  --> qpackt-backend/src/
94 |     tokio::spawn(run_with_args(vec![]));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `std::marker::Send` is not general enough
   = note: `std::marker::Send` would have to be implemented for the type `&ssl::challenge::AcmeChallenge`
   = note: ...but `std::marker::Send` is actually implemented for the type `&'0 ssl::challenge::AcmeChallenge`, for some specific lifetime `'0`

and some more of the same errors with regards to other structs used by run_with_args.
Why is that?


as pointed out by the compiler, the problem is in the AcmeChallenge type, specifically, &AcmeChallenge is not Send, which means AcmeChallenge itself is not Sync. I don't know the details of your design, but as a start point, you can try to solve the error by making the type thread safe.

Thanks, but I don't think that's it. The same line gives the same error about &Path:

error: implementation of `std::marker::Send` is not general enough
  --> qpackt-backend/src/
94 |     tokio::spawn(run_with_args(vec![]));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `std::marker::Send` is not general enough
   = note: `std::marker::Send` would have to be implemented for the type `&std::path::Path`
   = note: ...but `std::marker::Send` is actually implemented for the type `&'0 std::path::Path`, for some specific lifetime `'0`

Both AcmeChallenge and Path are created inside run_with_args and I don't think that any type created in async function must be Sync.


I don't have a linux machine at hand, I just did some quick experiment on a windows machine, so I cannot test your code for sure, and the following explanation might not be accurate.

I did some search, and find the following thread.

I also find the bug report #96865 and see your comment there.

it appears to me that this error only shows up for async functions with generic lifetime parameters, and I believe the actual code that triggers the bug is this line:

specifically, the Migrator::run() function is a generic function with lifetime parameter, it's type signature is:

I don't know what's the "proper" way to workaround this issue, my idea is to make a wrapper for the opaque future that "force" a Send marker. but I noticed you have the #![forbid(unsafe_code)] lint configuration, so you might want to put the wrapper into a separate crate and audit with real experts.

    /// Called on startup to ensure that sqlite file exists and all migrations are applied.
    async fn ensure_sqlite_initialized(&self) -> Result<()> {
        let url = self.inner.get_read_write_url().await;
        let mut conn = get_sqlite_connection(&url).await?;
-       sqlx::migrate!("db/migrations").run(&mut conn).await.map_err(|e| QpacktError::DatabaseError(e.to_string()))?;
+       migrate_wrapper(&mut conn).await?;

+fn migrate_wrapper<'a>(conn: &'a mut SqliteConnection) -> impl Future<Output = Result<()>> + Send + 'a {
+    ForceSendFuture(async move {
+        sqlx::migrate!("db/migrations").run(conn).await.map_err(|e| QpacktError::DatabaseError(e.to_string()))
+    })

and the ForceSendFuture type is very simple (but unsafe):

struct ForceSendFuture<F>(F);

/// SAFETY: extremely unsafe!!!
unsafe impl<F> Send for ForceSendFuture<F> {}

impl<F: Future> Future for ForceSendFuture<F> {
    type Output = F::Output;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // SAFETY: Self is #[repr(transparent)]
        // better to use the `pin_project` crate
        unsafe { std::mem::transmute::<_, Pin<&mut F>>(self).poll(cx) }

Thanks for the effort, I really appreciate it.
In my case the solution was fairly simple: I had to change how start_http is run: qpackt/qpackt-backend/src/ at c28c0641643b2386d7519616f401eddb6b5a89af · qpackt/qpackt · GitHub. Remove tokio_spawn and it breaks.

I've also learnt about clippy warning 'future_not_send' (or something similar) and it seems like I have this problem in other places.

glad you fixed it.

from my experiments, it is the future of the async function Migrator::run() that triggers the compiler bug. in your demo code, run_with_args() calls Dao::init(), which calls Dao::ensure_sqlite_initialized(), which eventually calls the problelmatic Migrator::run().

the start_http() async function has no problem regarding the Send bound, and Send is required by tokio::spawn() because the task might be run on different runtime worker threads.

your fix split the run_with_args() into two parts:

  • the first part does the initialization work, but it contains the call chain of the code which has problem with Send bound, however it doesn't need to be run concurrently, so no need to spawn a new task for it
  • the second part is the server loop, which should be spwaned as a separate task, and it doesn't trigger the Send being non general enough bug.

as far as I know, the tokio::main task doesn't require a Send bound. as long you only call the initialization part from the main task, your fix will work without any problem, as it simply eliminate the Send requirement for the problematic code and doesn't involve unsafe. I think it's better than my workaround.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.