Tokio non blocking background task leads to error `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement

1k views Asked by At

I have an async function which I want to run in the background. This function is part of a call hierarchy which is not using async calls.

My call hierarchy looks like this:

struct Handler {}
impl Handler {

  pub async fn handle(&self) {
     /// does some stuff and updates internal caches
  }
}

struct Client_v2 {
  handler: Handler,
  rt: tokio::runtime::Runtime,
}

impl Client_v2 {

  fn do_work(&self) {
    let future = self.handler(handle);
    self.rt.spawn(future); // want this to run in background and not block! 
    // `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  }
}

From what I understand there is some issue where perhaps the client could outlive its owner Client_v2 leading to this error.

How should I go about making this lifetime valid? do I need to resort to creating threads and moving objects in and out of the thread scope? This function is called very frequently.

2

There are 2 answers

2
Netwave On BEST ANSWER

Alternatively to @sebpuetz problem explanation. In rust you can wrap your type in Arc to being able to share its state, so you can move it (a clone of the Arc) to the future you need to compute:

struct Client_v2 {
  handler: Handler,
  rt: tokio::runtime::Runtime,
}

impl Client_v2 {

  pub async fn handle(&self) {
  }
  
  fn do_work(self: Arc<Self>) {
    let handler = self.clone();
    let future = async move { handler.handle().await; };
    self.rt.spawn(future);
  }
}

Playground

1
sebpuetz On

First of all, handler is not callable, it's a member struct of Client_v2. You need to access it through the dot operator: self.handler.handle().

On to the actual question:

Runtime::spawn requires the provided future to be 'static + Send, i.e., it can't borrow data and it must be possible to move it to another thread.

The 'static requirement is due to the parent thread possibly exiting before the thread executing the future, thus any data given to the task must be ensured to live at least as long as the task itself.

It must be Sendable because the tokio runtime is free to move spawned Futures between the threads of its threadpool.

The relevant requirement for your error is the first one, the future produced by Handler::handle is not 'static because the function borrows self as a shared reference: async fn handle(&self). Calling this function produces a Future + 'a where 'a is the lifetime of &'a self, i.e. you could write the whole signature as fn handle<'a>(&'a self) -> impl Future<Output=()> + 'a + Send but Runtime::spawn needs you to return impl Future<Output=()> + 'static + Send.

To prove that you're not borrowing data from &self in handle, you can use an async {} block and explicitly state the return type as impl Future<Output = ()> + Send + 'static:

  pub fn handle(&self) -> impl Future<Output = ()> + 'static + Send {
      async {}
  }

If you actually need to access data from &self inside the async block, you'll need to produce it outside of the async block and move it inside - otherwise the produced future will again violate the 'static requirement.

  pub fn handle(&self) -> impl Future<Output = ()> + 'static + Send {
      let owned_data = self.prepare_data();
      async move {
          let _owned_data = owned_data; // can now work with `owned_data`
      }
  }