'Value may be used later' error with async fn

So here's what we're trying to do:

// -- snip --
tokio::spawn(async move {
    if let Error(e)  = merkle_blockmap_readback(handle, con, act) {
        eprintln!("Ledger failure: {}", e);
    }
})

Here's what merkle_blockmap_readback looks like:

pub async fn merkle_blockmap_readback(handle: &BlockWriter, con: &mut Connection, act: BlockMapArray) 
-> Result<(), Box<dyn Error>> {
    let howmany = act.howmany();
    if howmany == 0 {
        return con.write_peer(precompiledresp::Failed).await;
    }
    // write_peer is almost like TcpStream.write(&[..]).await
    con.write_peer(&[b'$', b'1']).await?;
    let mut keys = act.into_iter();
    // acquire_read returns a read lock from `parking_lot::RwLock`
    let rhandle = handle.acquire_read(); // Get a read lock
    while let Some(key) = keys.next() {
        if let Some(value) = rhandle.get(&key) {
            let sig_wback = relayer::sig_wback(value);
            con.write_peer(sig_wback).await?;
        } else {
            // Something wrong with genesis sig
            con.write_peer(precompiledresp::BlockSigNotFound).await?;
        }
    }
    Ok(())
}

The trouble that we've run into is with the closure becoming non-Send, because of the read handle which returns a read lock from parking_lot::RwLock.

Edit
More precisely, this is the error that we get:

  |     let rhandle = handle.acquire_read();
  |         ------ has type `lock_api::rwlock::RwLockReadGuard<'_, parking_lot::raw_rwlock::RawRwLock, blocktables::BlockMapTable>` which is not `Send`
...
  |             con.write_peer(sig_wback).await?;
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rhandle` maybe used later

What's the resolution here?

You're currently holding the read lock until the end of the function which crosses an await. Anything that is held past an await needs to be Send. This is because it might get resumed on a different thread. You probably want to acquire the lock at the top of each loop instead of before the loop. You may need to reorganize the code a little after that to convince the compiler that the lock isn't held across the await, but I would just try it first.

Didn't get much luck with that.
I tried:

-- snip --
while let Some(key) = keys.next() {
    let rhandle = handle.acquire_read();
    if let Some(value) = rhandle.get(&key) {
    // -- snip --
   } else {
       // -- snip --
   }
   drop(rhandle); // explicitly drop the read lock
   // Now that was dumb of me, rhandle is anyways dropped since it goes out of scope
}
// -- snip --

Try something like this. Your drop needs to happen before the await. Though in this case, I don't think the async code understands drop at all, so you need to use explicit scoping. Though I haven't tested this.

while let Some(key) = keys.next() {
    let res = {
        rhandle.acquire_read().get(&key).map(relayer::sig_wback)
    };
    if let Some(value) = res {
        con.write_peer(value).await?;
    } else {
        con.write_peer(precompiledresp::BlockSigNotFound).await?;
    }
}
1 Like

Just the neat solution I was looking for. Thanks!

In this case actually, that scope may be unnecessary since the read lock is only held in a temporary variable.

This error has its own section in the shared state chapter in the Tokio tutorial. The chapter uses Mutex, but it is the exact same for rwlocks.

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.