use std::task::{RawWaker, RawWakerVTable, Waker};
static FAN_OUT_VTABLE: RawWakerVTable = RawWakerVTable::new(
clone_members,
wake_members,
wake_members_by_ref,
drop_members,
);
pub(super) fn register(slot: &mut Option<Waker>, waker: &Waker) {
let Some(held) = slot.as_mut() else {
*slot = Some(waker.clone());
return;
};
if held.will_wake(waker) {
return;
}
if let Some(members) = members_mut(held) {
if !members.iter().any(|member| member.will_wake(waker)) {
members.push(waker.clone());
}
return;
}
let first = std::mem::replace(held, Waker::noop().clone());
*held = fan_out(vec![first, waker.clone()]);
}
fn fan_out(members: Vec<Waker>) -> Waker {
let data = Box::into_raw(Box::new(members)).cast_const().cast::<()>();
unsafe { Waker::from_raw(RawWaker::new(data, &FAN_OUT_VTABLE)) }
}
fn members_mut(waker: &mut Waker) -> Option<&mut Vec<Waker>> {
if !std::ptr::eq(waker.vtable(), &FAN_OUT_VTABLE) {
return None;
}
Some(unsafe { &mut *waker.data().cast::<Vec<Waker>>().cast_mut() })
}
unsafe fn clone_members(data: *const ()) -> RawWaker {
let members = unsafe { &*data.cast::<Vec<Waker>>() };
let data = Box::into_raw(Box::new(members.clone()))
.cast_const()
.cast::<()>();
RawWaker::new(data, &FAN_OUT_VTABLE)
}
unsafe fn wake_members(data: *const ()) {
let members = unsafe { Box::from_raw(data.cast::<Vec<Waker>>().cast_mut()) };
for member in *members {
member.wake();
}
}
unsafe fn wake_members_by_ref(data: *const ()) {
let members = unsafe { &*data.cast::<Vec<Waker>>() };
for member in members {
member.wake_by_ref();
}
}
unsafe fn drop_members(data: *const ()) {
drop(unsafe { Box::from_raw(data.cast::<Vec<Waker>>().cast_mut()) });
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, reason = "test scope")]
use std::sync::Arc;
use crate::counting_wake::counting_waker;
use super::register;
#[test]
fn a_single_waiter_stays_unwrapped() {
let (target, waker) = counting_waker();
let mut slot = None;
register(&mut slot, &waker);
register(&mut slot, &waker);
let held = slot.take().unwrap();
assert!(held.will_wake(&waker), "one waiter must not allocate");
held.wake();
assert_eq!(target.wakes(), 1);
}
#[test]
fn a_fan_out_wakes_each_member_once_by_ref_then_by_value() {
let members: Vec<_> = (0..3).map(|_| counting_waker()).collect();
let mut slot = None;
for _ in 0..2 {
for (_, waker) in &members {
register(&mut slot, waker);
}
}
let held = slot.take().unwrap();
held.wake_by_ref();
for (index, (target, _)) in members.iter().enumerate() {
assert_eq!(target.wakes(), 1, "member {index} by reference");
}
held.wake();
for (index, (target, _)) in members.iter().enumerate() {
assert_eq!(target.wakes(), 2, "member {index} by value");
}
}
#[test]
fn a_cloned_fan_out_is_independent_of_its_source() {
let members: Vec<_> = (0..2).map(|_| counting_waker()).collect();
let mut slot = None;
for (_, waker) in &members {
register(&mut slot, waker);
}
let held = slot.take().unwrap();
let copy = held.clone();
drop(held);
copy.wake();
for (index, (target, _)) in members.iter().enumerate() {
assert_eq!(target.wakes(), 1, "member {index}");
}
}
#[test]
fn dropping_a_fan_out_releases_every_member_without_waking() {
let members: Vec<_> = (0..3).map(|_| counting_waker()).collect();
let mut slot = None;
for (_, waker) in &members {
register(&mut slot, waker);
}
for (target, _) in &members {
assert_eq!(Arc::strong_count(target), 3);
}
drop(slot);
for (target, _) in &members {
assert_eq!(target.wakes(), 0);
assert_eq!(Arc::strong_count(target), 2);
}
}
}