Producing/consuming an event in async

For context, in our program we have a long running task that acts as a state machine, and every so often an event occurs that we need to handle. The event is produced and consumed by one task at most, the event is recurring, and multiple events shouldn't queue (i.e. if the event happens twice without being consumed, we need to handle it only once).

A coworker of mine recently implemented this using a loop and a shared atomic boolean flag (the idea being when the flag is set, do a thing. The event producer sets the flag when the event occurs, and the consumer resets the flag after handling the event):

async fn toggle_on_reactor(flag: Arc<AtomicBool>) {
  while !flag.load(Ordering::Acquire) {
    tokio::time::sleep(Duration::from_millis(10)).await
  }
}

used by the consumer like

let event = toggle_on_reactor(flag);

loop {
  select! {
    // other futures here

    // the event
    _ = event => {
      // do things, then set `flag` back to false
    }
  }
}

In code review, I suggested they use something like tokio::sync::Notify instead, but got some pushback (because Notify internally uses a loop and does some atomic operations to check for a notification, which my coworker believed was just as wasteful as their version) and now I'm doubting myself.

What would be the best way to do this? For such a simple case Notify may be overkill, but using a loop with a timeout as in the original just doesn't feel right to me.

I am one of the maintainers of Tokio, and I can assure you that tokio::sync::Notify is more efficient than your loop with a sleep. Another utility you might consider is the watch channel.

2 Likes

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.