use super::{
guard::GuardInfo,
shard::{self, ShardRef, ARRIVED, KIDS_ONE, STOPPED},
sync::{AtomicU64, AtomicUsize},
};
use event_listener::{Event, EventListener, IntoNotification};
use std::{
ops::Deref,
slice,
sync::{atomic::Ordering, Arc, Weak},
};
#[derive(Debug)]
struct Parent {
node: Arc<Inner>,
shard: usize,
key: AtomicUsize,
}
const SHARDS_MASK: u64 = 0xFFFF_FFFF;
const PARENT_SHARD_SHIFT: u32 = 32;
const PARENT_SHARD_MASK: u64 = 0xFFFF;
#[derive(Debug)]
pub(crate) struct Inner {
stop_event: Event,
zero_event: Event,
state: AtomicU64,
swansong_count: AtomicUsize,
parent: Option<Parent>,
shards: Shards,
#[cfg(swansong_loom)]
zero_notifications: AtomicUsize,
}
impl Inner {
fn new(
this: &Weak<Self>,
parent: Option<Parent>,
shard_count: usize,
guard_report: bool,
) -> Self {
debug_assert!(shard_count.is_power_of_two());
let root = parent.is_none();
Self {
stop_event: Event::new(),
zero_event: Event::new(),
state: AtomicU64::new(0),
swansong_count: AtomicUsize::new(1),
parent,
shards: if shard_count == 1 {
Shards::One(ShardRef::new(Weak::clone(this), root, guard_report))
} else {
Shards::Many(
(0..shard_count)
.map(|_| ShardRef::new(Weak::clone(this), root, guard_report))
.collect(),
)
},
#[cfg(swansong_loom)]
zero_notifications: AtomicUsize::new(0),
}
}
pub(crate) fn new_root(guard_report: bool) -> Arc<Self> {
Arc::new_cyclic(|this| Self::new(this, None, shard::root_shard_count(), guard_report))
}
pub(crate) fn new_child(parent: &Arc<Self>) -> Arc<Self> {
let shard = parent.shard_index();
let link = Parent {
node: Arc::clone(parent),
shard,
key: AtomicUsize::new(usize::MAX),
};
let guard_report = parent.keeps_guard_report();
let inner = Arc::new_cyclic(|this| Self::new(this, Some(link), 1, guard_report));
let key = parent.shards[shard].register_child(Arc::downgrade(&inner));
if let Some(link) = &inner.parent {
link.key.store(key, Ordering::Relaxed);
}
if parent.is_stopped() {
inner.stop();
}
inner
}
pub(crate) fn is_stopped(&self) -> bool {
self.state.load(Ordering::SeqCst) & STOPPED != 0
}
pub(crate) fn is_stopped_relaxed(&self) -> bool {
self.state.load(Ordering::Relaxed) & STOPPED != 0
}
pub(crate) fn is_zero(&self) -> bool {
self.state.load(Ordering::SeqCst) & SHARDS_MASK == 0
}
pub(crate) fn is_zero_relaxed(&self) -> bool {
self.state.load(Ordering::Relaxed) & SHARDS_MASK == 0
}
#[cfg(all(test, swansong_loom))]
pub(crate) fn is_complete(&self) -> bool {
self.is_stopped() && self.is_zero()
}
pub(crate) fn is_root(&self) -> bool {
self.parent.is_none()
}
pub(crate) fn keeps_guard_report(&self) -> bool {
self.shards[0].keeps_guard_report()
}
pub(crate) fn listen_zero(&self) -> EventListener {
self.zero_event.listen()
}
pub(crate) fn listen_stop(&self) -> EventListener {
self.stop_event.listen()
}
fn notify_zero(&self) {
#[cfg(swansong_loom)]
self.zero_notifications.fetch_add(1, Ordering::SeqCst);
self.zero_event.notify(usize::MAX.relaxed());
}
#[cfg(all(test, swansong_loom))]
pub(crate) fn zero_notifications(&self) -> usize {
self.zero_notifications.load(Ordering::SeqCst)
}
fn shard_index(&self) -> usize {
shard::thread_index() & (self.shards.len() - 1)
}
pub(crate) fn shard(&self) -> &ShardRef {
&self.shards[self.shard_index()]
}
#[cfg(all(test, swansong_loom))]
pub(crate) fn shards(&self) -> &[ShardRef] {
&self.shards
}
pub(crate) fn swansong_clone(&self) {
self.swansong_count.fetch_add(1, Ordering::SeqCst);
}
pub(crate) fn swansong_drop(&self) -> bool {
self.swansong_count.fetch_sub(1, Ordering::SeqCst) == 1
}
pub(crate) fn stop(&self) {
log::trace!("intending to stop");
let previous = self.state.fetch_or(STOPPED, Ordering::SeqCst);
if previous & STOPPED != 0 {
log::trace!("was already stopped");
return;
}
log::trace!("stopped");
for shard in self.shards.iter() {
shard.stop(self);
}
self.stop_event.notify(usize::MAX.relaxed());
for child in self.live_children() {
child.stop();
}
if previous & SHARDS_MASK == 0 {
self.notify_zero();
}
}
pub(crate) fn arrive(&self) {
let Some(Parent { node: parent, .. }) = &self.parent else {
self.state.fetch_add(1, Ordering::SeqCst);
return;
};
let mut state = self.state.load(Ordering::SeqCst);
loop {
if state & ARRIVED == 0 {
debug_assert_eq!(state & !STOPPED, 0, "an unarrived child is empty");
let index = parent.shard_index();
let shard = &parent.shards[index];
shard.arrive(KIDS_ONE, parent);
let arrived = state | ARRIVED | (index as u64) << PARENT_SHARD_SHIFT | 1;
match self.state.compare_exchange(
state,
arrived,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => return,
Err(actual) => {
if shard.depart(KIDS_ONE) {
shard.flush(parent);
}
state = actual;
}
}
} else {
match self.state.compare_exchange_weak(
state,
state + 1,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => return,
Err(actual) => state = actual,
}
}
}
}
pub(crate) fn depart(&self) {
let state = self.state.fetch_sub(1, Ordering::SeqCst) - 1;
log::trace!("departed: {:#x} -> {state:#x}", state + 1);
if state & SHARDS_MASK != 0 {
return;
}
if state & STOPPED != 0 {
self.notify_zero();
}
let Some(Parent { node: parent, .. }) = &self.parent else {
return;
};
debug_assert!(state & ARRIVED != 0, "a counted child has arrived");
if self
.state
.compare_exchange(state, state & STOPPED, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
let index = usize::try_from((state >> PARENT_SHARD_SHIFT) & PARENT_SHARD_MASK)
.expect("shard index fits in usize");
let shard = &parent.shards[index];
if shard.depart(KIDS_ONE) {
shard.flush(parent);
}
}
}
fn live_children(&self) -> Vec<Arc<Inner>> {
self.shards
.iter()
.flat_map(|shard| shard.children())
.collect()
}
pub(crate) fn guard_count_subtree(&self) -> usize {
let local: usize = self.shards.iter().map(|shard| shard.guard_count()).sum();
let children_sum: usize = self
.live_children()
.iter()
.map(|child| child.guard_count_subtree())
.sum();
local + children_sum
}
pub(crate) fn guard_infos_subtree(&self, out: &mut Vec<Arc<GuardInfo>>) {
for shard in self.shards.iter() {
shard.guard_infos(out);
}
for child in self.live_children() {
child.guard_infos_subtree(out);
}
}
}
#[derive(Debug)]
enum Shards {
One(ShardRef),
Many(Box<[ShardRef]>),
}
impl Deref for Shards {
type Target = [ShardRef];
fn deref(&self) -> &[ShardRef] {
match self {
Self::One(shard) => slice::from_ref(shard),
Self::Many(shards) => shards,
}
}
}
impl Drop for Inner {
fn drop(&mut self) {
for shard in self.shards.iter() {
shard.stop(self);
}
if let Some(Parent { node, shard, key }) = &self.parent {
let key = key.load(Ordering::Relaxed);
if key != usize::MAX {
node.shards[*shard].deregister_child(key);
}
}
}
}
#[cfg(all(test, not(swansong_loom)))]
mod tests {
use super::*;
#[test]
fn children_registered_exactly_while_alive() {
let root = Inner::new_root(false);
let mut live = vec![];
for i in 0..1000 {
let child = Inner::new_child(&root);
if i % 10 == 0 {
live.push(child);
}
}
let registered = || {
root.shards
.iter()
.map(|shard| shard.child_count())
.sum::<usize>()
};
assert_eq!(registered(), live.len());
assert_eq!(root.live_children().len(), live.len());
drop(live);
assert_eq!(registered(), 0);
}
}