Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to get a reference to the current task's context in an async function in rust?

In a rust async function is there any way to get access to the current Context without writing an explicit implementation of a Future?

like image 630
Thayne Avatar asked Sep 18 '25 23:09

Thayne


1 Answers

Before actually answering the question, it is useful to remember what a Context is; whenever you are writing an implementation of a Future that depends on outside resources (say, I/O), you do not want to busy-wait anything. As a result, you'll most likely have implementations of Future where you'll return Pending and then wake it up. Context (and Waker) exist for that purpose.

However, this is what they are: low-level, implementation details. If you are using a Future already as opposed to writing a low-level implementation of one, the Waker will most likely be contained somewhere, but not directly accessible to you.

As a result of this, a Waker directly leaking is an implementation detail leak 99.9% of the time and not actually recommended. A Waker being used as part of a bigger struct is perfectly fine, however, but this is where you'll need to implement your own Future from scratch. There is no other valid use case for this, and in normal terms, you should never need direct access to a Waker.

Due to the limitations of the playground, I sadly cannot show you a live example of when it is useful to get this Waker; however, such a future setup may be used in such a situation: let's assume we're building the front door of a house. We have a doorbell and a door, and we want to be notified when somebody rings the doorbell. However, we don't want to have to wait at the door for visitors.

We therefore make two objects: a FrontDoor and a Doorbell, and we give the option to wire() the Doorbell to connect the two.

pub struct FrontDoor {
    doorbell: Arc<RwLock<Doorbell>>
}

impl FrontDoor {
    pub fn new() -> FrontDoor {
        FrontDoor {
            doorbell: Arc::new(RwLock::new(Doorbell {
                waker: None,
                visitor: false
            }))
        }
    }
    pub fn wire(&self) -> Arc<RwLock<Doorbell>> {
        self.doorbell.clone() // We retrieve the bell
    }
}

impl Future for FrontDoor {

    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        self.doorbell.read().map(|guard| {
            match guard.visitor {
                true => Poll::Ready(()),
                false => Poll::Pending
            }
        }).unwrap_or(Poll::Pending)
    }
}


pub struct Doorbell {
    waker: Option<Waker>,
    pub visitor: bool
}

impl Doorbell {
    pub fn ring(&mut self) {
        self.visitor = true;
        self.waker.as_ref().map(|waker| waker.wake_by_ref());
    }
}

Our FrontDoor implements Future, which means we can just throw it on an executor of your choice; waker is contained in the Doorbell object and allows us to "ring" and wake up our future.

like image 80
Sébastien Renauld Avatar answered Sep 20 '25 12:09

Sébastien Renauld