use core::cell::Cell;
pub(crate) struct Links {
prev: Cell<*const Links>,
next: Cell<*const Links>,
}
impl Links {
pub(crate) const fn new() -> Self {
Self {
prev: Cell::new(core::ptr::null()),
next: Cell::new(core::ptr::null()),
}
}
#[inline(always)]
pub(crate) fn is_listed(&self) -> bool {
!self.next.get().is_null()
}
#[inline(always)]
pub(crate) unsafe fn unlink(&self) {
let (prev, next) = (self.prev.get(), self.next.get());
unsafe {
(*prev).next.set(next);
(*next).prev.set(prev);
}
self.next.set(core::ptr::null());
}
}
pub(crate) struct TaskList {
head: Links,
}
unsafe impl Send for TaskList {}
unsafe impl Sync for TaskList {}
impl TaskList {
pub(crate) const fn new() -> Self {
Self { head: Links::new() }
}
#[inline(always)]
pub(crate) unsafe fn push(&self, task: &Links) {
let head: *const Links = &self.head;
let mut first = self.head.next.get();
if first.is_null() {
first = head;
}
task.prev.set(head);
task.next.set(first);
unsafe { (*first).prev.set(task) };
self.head.next.set(task);
}
pub(crate) unsafe fn cancel_all(&self) -> usize {
let waiting = TaskList::new();
let _back = Back {
from: &waiting,
to: self,
};
let mut dropped = 0;
while let Some(links) = self.first() {
match unsafe { super::close_listed(links) } {
Some(runnable) => {
drop(runnable);
dropped += 1;
}
None => {
unsafe {
links.unlink();
waiting.push(links);
}
}
}
}
dropped
}
pub(crate) unsafe fn forget_all(&self) {
while let Some(links) = self.first() {
unsafe { links.unlink() };
}
}
pub(crate) unsafe fn len(&self) -> usize {
let head: *const Links = &self.head;
let mut count = 0;
let mut at = self.head.next.get();
while !at.is_null() && !core::ptr::eq(at, head) {
count += 1;
at = unsafe { (*at).next.get() };
}
count
}
fn first(&self) -> Option<&Links> {
let first = self.head.next.get();
(!first.is_null() && !core::ptr::eq(first, &self.head)).then(|| unsafe { &*first })
}
#[inline(always)]
pub(crate) unsafe fn is_empty(&self) -> bool {
let first = self.head.next.get();
first.is_null() || core::ptr::eq(first, &self.head)
}
}
struct Back<'a> {
from: &'a TaskList,
to: &'a TaskList,
}
impl Drop for Back<'_> {
fn drop(&mut self) {
while let Some(links) = self.from.first() {
unsafe {
links.unlink();
self.to.push(links);
}
}
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::{Links, TaskList};
fn order(list: &TaskList) -> Vec<*const Links> {
let head: *const Links = &list.head;
let mut out = Vec::new();
let mut at = list.head.next.get();
while !at.is_null() && at != head {
out.push(at);
at = unsafe { (*at).next.get() };
}
out
}
fn ptr(links: &Links) -> *const Links {
links
}
#[test]
fn push_unlink_and_repush() {
let list = Box::new(TaskList::new());
let nodes: Vec<Box<Links>> = (0..3).map(|_| Box::new(Links::new())).collect();
unsafe {
assert!(list.is_empty());
for node in &nodes {
assert!(!node.is_listed());
list.push(node);
assert!(node.is_listed());
}
assert!(!list.is_empty());
let (a, b, c) = (ptr(&nodes[0]), ptr(&nodes[1]), ptr(&nodes[2]));
assert_eq!(order(&list), [c, b, a]);
nodes[1].unlink();
assert!(!nodes[1].is_listed());
assert_eq!(order(&list), [c, a]);
nodes[2].unlink();
assert!(!nodes[2].is_listed());
assert_eq!(order(&list), [a]);
nodes[0].unlink();
assert!(!nodes[0].is_listed());
assert!(list.is_empty());
assert!(order(&list).is_empty());
list.push(&nodes[1]);
assert_eq!(order(&list), [ptr(&nodes[1])]);
nodes[1].unlink();
assert!(list.is_empty());
}
}
#[test]
fn unlink_middle_last_then_first() {
let list = Box::new(TaskList::new());
let nodes: Vec<Box<Links>> = (0..3).map(|_| Box::new(Links::new())).collect();
unsafe {
for node in &nodes {
list.push(node);
}
let (a, b, c) = (ptr(&nodes[0]), ptr(&nodes[1]), ptr(&nodes[2]));
nodes[0].unlink();
assert_eq!(order(&list), [c, b]);
nodes[2].unlink();
assert_eq!(order(&list), [b]);
assert!(!nodes[0].is_listed() && !nodes[2].is_listed() && nodes[1].is_listed());
nodes[1].unlink();
assert!(list.is_empty());
let _ = a;
}
}
#[test]
fn len_and_forget_all() {
let list = Box::new(TaskList::new());
let nodes: Vec<Box<Links>> = (0..4).map(|_| Box::new(Links::new())).collect();
unsafe {
assert_eq!(list.len(), 0);
list.forget_all();
for node in &nodes {
list.push(node);
}
assert_eq!(list.len(), 4);
nodes[1].unlink();
assert_eq!(list.len(), 3);
list.forget_all();
assert_eq!(list.len(), 0);
assert!(list.is_empty());
assert!(nodes.iter().all(|node| !node.is_listed()));
list.push(&nodes[2]);
assert_eq!(list.len(), 1);
}
}
}