What is the cleanest way to make this subroutine asynchronous?

    /// Because of the internal algorithms, it will be absolutely necessary that all clients possess a different CID.
    /// If they do not, then a false search result may return, thus skewing the routing of the network and thus posing
    /// a severe security risk.
    pub fn generate_unused_cid(&self) -> u64 {
        loop {
            let rnd = rand::random::<u64>();
            if !self.cid_table.contains(&rnd) {
                return rnd;
            }
        }
    }

I was thinking of maybe using a stream, and running while let None = ..., but I'm not sure what the cleanest and leanest way to make this async is. Upon each poll, a random value is generated, then compared to cid_table for any duplicates. The future completes once no duplicates are found, else, it ought to be re-polled again sometime in the future. It is very likely I don't need this subroutine async, but I'm considering "worst case scenario" and the entropy of rand drops, thus causing a loop which could hold back the entire event loop from continuing.

"Why not just use a counter to generate ID's"? I do not want this for security reasons

Depending on your requirements, you could pass a counter through an HMAC to have deterministic but non-predictable values.

What do you mean by the "entropy of rand dropping"? rand's thread-local RNG uses a cryptographically secure PRNG. It's not going to magically shrink its period down to something tiny at random.

1 Like

Sometimes the shannon entropy becomes small (Thus producing duplicate consecutive values; I've seen this happen before when pseudo random generators are spam-called), but yeah it is unlikely to be an issue. If rand is indeed cryptographically secure, then what I have will suffice. But for educational purposes, how might this function get converted into async form without** having to write an entire impl Future on a custom struct? This will be useful for knowing how to make iterative functions as such async.

I am suspecting that a while let None stream will be "the" way to go about it?

That could happen if you're seeding an RNG off of the system time, but if you do... not that... it's not going to be a problem.

This is a version of what you're asking for:

async fn generate_unused_cid(&self) -> u64 {
    future::poll_fn(|cx| {
        let rnd = rand::random::<u64>();
        if self.cid_table.contains(&rnd) {
            cx.waker().wake_by_ref();
            Poll::Pending
        } else {
            Poll::Ready(rnd)
        }
    }).await
}
1 Like

When you call wake_by_ref, does the function return first with Poll::Pending, and then the context is polled?

wake_by_ref indicates that the task is runnable again. When it then returns Pending, the executor knows it can immediately poll it again (maybe after running some other tasks).

1 Like