use std::sync::atomic::Ordering;
use std::sync::Mutex;
use super::g::G;
pub(crate) struct Sudog {
pub g: *mut G,
pub next: *mut Sudog,
pub prev: *mut Sudog,
pub elem: *mut u8,
pub is_select: bool,
pub success: bool,
pub boxed_elem: bool,
pub c: *mut u8,
}
unsafe impl Send for Sudog {}
unsafe impl Sync for Sudog {}
impl Sudog {
fn zeroed() -> Self {
Sudog {
g: std::ptr::null_mut(),
next: std::ptr::null_mut(),
prev: std::ptr::null_mut(),
elem: std::ptr::null_mut(),
is_select: false,
success: false,
boxed_elem: false,
c: std::ptr::null_mut(),
}
}
}
pub(crate) struct WaitQ {
pub first: *mut Sudog,
pub last: *mut Sudog,
}
unsafe impl Send for WaitQ {}
impl WaitQ {
pub(crate) const fn new() -> Self {
WaitQ { first: std::ptr::null_mut(), last: std::ptr::null_mut() }
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
self.first.is_null()
}
pub(crate) unsafe fn enqueue(&mut self, sgp: *mut Sudog) {
unsafe {
(*sgp).next = std::ptr::null_mut();
let last = self.last;
if last.is_null() {
(*sgp).prev = std::ptr::null_mut();
self.first = sgp;
self.last = sgp;
} else {
(*sgp).prev = last;
(*last).next = sgp;
self.last = sgp;
}
}
}
pub(crate) unsafe fn dequeue(&mut self) -> *mut Sudog {
loop {
let sgp = self.first;
if sgp.is_null() {
return std::ptr::null_mut();
}
let next = unsafe { (*sgp).next };
if next.is_null() {
self.first = std::ptr::null_mut();
self.last = std::ptr::null_mut();
} else {
unsafe { (*next).prev = std::ptr::null_mut() };
self.first = next;
unsafe { (*sgp).next = std::ptr::null_mut() };
}
unsafe { (*sgp).prev = std::ptr::null_mut() };
if unsafe { (*sgp).is_select } {
let gp = unsafe { (*sgp).g };
let won = !gp.is_null()
&& unsafe {
(*gp).selectdone
.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Relaxed)
}
.is_ok();
if !won {
continue;
}
}
return sgp;
}
}
pub(crate) unsafe fn dequeue_sudog(&mut self, sgp: *mut Sudog) {
let prev = unsafe { (*sgp).prev };
let next = unsafe { (*sgp).next };
if prev.is_null() && self.first != sgp {
return;
}
if !prev.is_null() {
unsafe { (*prev).next = next };
} else {
self.first = next;
}
if !next.is_null() {
unsafe { (*next).prev = prev };
} else {
self.last = prev;
}
unsafe {
(*sgp).next = std::ptr::null_mut();
(*sgp).prev = std::ptr::null_mut();
}
}
}
struct SudogCache {
head: *mut Sudog,
count: usize,
}
unsafe impl Send for SudogCache {}
const CACHE_CAP: usize = 1_024;
static SUDOG_CACHE: Mutex<SudogCache> = Mutex::new(SudogCache {
head: std::ptr::null_mut(),
count: 0,
});
pub(crate) fn acquire_sudog() -> *mut Sudog {
let mut cache = SUDOG_CACHE.lock().unwrap();
if !cache.head.is_null() {
let s = cache.head;
unsafe { cache.head = (*s).next };
cache.count -= 1;
drop(cache);
unsafe { std::ptr::write(s, Sudog::zeroed()) };
s
} else {
drop(cache); Box::into_raw(Box::new(Sudog::zeroed()))
}
}
pub(crate) unsafe fn release_sudog(s: *mut Sudog) {
debug_assert!(
unsafe { (*s).elem.is_null() },
"release_sudog: elem not cleared"
);
debug_assert!(
unsafe { (*s).g.is_null() },
"release_sudog: g not cleared"
);
debug_assert!(
unsafe { (*s).c.is_null() },
"release_sudog: c not cleared"
);
let mut cache = SUDOG_CACHE.lock().unwrap();
if cache.count < CACHE_CAP {
unsafe { (*s).next = cache.head };
cache.head = s;
cache.count += 1;
} else {
drop(cache);
let _ = unsafe { Box::from_raw(s) };
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use crate::runtime::g::GWAITING;
use std::sync::atomic::Ordering::Release;
fn make_g() -> *mut G {
use crate::runtime::g::{Stack, G};
Box::into_raw(G::new(Stack { lo: 0x100000, hi: 0x110000 }, 1))
}
#[test]
fn acquire_returns_zeroed_sudog() {
let s = acquire_sudog();
unsafe {
assert!((*s).g.is_null(), "g must be null");
assert!((*s).next.is_null(), "next must be null");
assert!((*s).prev.is_null(), "prev must be null");
assert!((*s).elem.is_null(), "elem must be null");
assert!(!(*s).is_select, "is_select must be false");
assert!(!(*s).success, "success must be false");
assert!((*s).c.is_null(), "c must be null");
}
unsafe { release_sudog(s) };
}
#[test]
fn released_sudog_is_reused() {
let s1 = acquire_sudog();
let addr1 = s1 as usize;
unsafe { release_sudog(s1) };
let s2 = acquire_sudog();
let addr2 = s2 as usize;
assert_eq!(addr1, addr2, "acquire should reuse the just-released sudog");
unsafe {
assert!((*s2).g.is_null(), "recycled sudog.g must be null");
assert!((*s2).elem.is_null(), "recycled sudog.elem must be null");
assert!((*s2).c.is_null(), "recycled sudog.c must be null");
}
unsafe { release_sudog(s2) };
}
#[test]
fn waitq_enqueue_into_empty() {
let gp = make_g();
let s = acquire_sudog();
unsafe { (*s).g = gp };
let mut q = WaitQ::new();
assert!(q.is_empty());
unsafe { q.enqueue(s) };
assert!(!q.is_empty());
assert_eq!(q.first, s);
assert_eq!(q.last, s);
unsafe {
assert!((*s).prev.is_null(), "first element has no prev");
assert!((*s).next.is_null(), "only element has no next");
}
unsafe {
(*s).g = std::ptr::null_mut();
(*s).c = std::ptr::null_mut();
release_sudog(s);
let _ = Box::from_raw(gp);
}
}
#[test]
fn waitq_fifo_order() {
let g1 = make_g();
let g2 = make_g();
let s1 = acquire_sudog();
let s2 = acquire_sudog();
unsafe {
(*s1).g = g1;
(*s2).g = g2;
(*g1).atomicstatus.store(GWAITING, Release);
(*g2).atomicstatus.store(GWAITING, Release);
}
let mut q = WaitQ::new();
unsafe {
q.enqueue(s1);
q.enqueue(s2);
}
let got1 = unsafe { q.dequeue() };
assert_eq!(got1, s1, "first dequeue must return s1");
let got2 = unsafe { q.dequeue() };
assert_eq!(got2, s2, "second dequeue must return s2");
assert!(q.is_empty(), "queue must be empty after both dequeues");
assert_eq!(unsafe { q.dequeue() }, std::ptr::null_mut());
unsafe {
assert!((*s1).next.is_null());
assert!((*s1).prev.is_null());
assert!((*s2).next.is_null());
assert!((*s2).prev.is_null());
}
unsafe {
(*s1).g = std::ptr::null_mut(); (*s1).c = std::ptr::null_mut();
(*s2).g = std::ptr::null_mut(); (*s2).c = std::ptr::null_mut();
release_sudog(s1); release_sudog(s2);
let _ = Box::from_raw(g1); let _ = Box::from_raw(g2);
}
}
#[test]
fn waitq_dequeue_skips_non_waiting() {
use crate::runtime::g::GWAITING;
let g_skip = make_g();
let g_take = make_g();
let s_skip = acquire_sudog();
let s_take = acquire_sudog();
unsafe {
(*s_skip).g = g_skip;
(*s_skip).is_select = true;
(*s_take).g = g_take;
(*s_take).is_select = true;
(*g_skip).atomicstatus.store(GWAITING, Release);
(*g_skip).selectdone.store(1, Release); (*g_take).atomicstatus.store(GWAITING, Release);
}
let mut q = WaitQ::new();
unsafe { q.enqueue(s_skip); q.enqueue(s_take); }
let got = unsafe { q.dequeue() };
assert_eq!(got, s_take, "dequeue must skip the select-lost sudog");
assert!(q.is_empty());
assert_eq!(
unsafe { (*g_take).selectdone.load(Ordering::Relaxed) },
1,
"dequeue must CAS selectdone to 1 for the winning sudog"
);
unsafe {
(*s_take).g = std::ptr::null_mut(); (*s_take).c = std::ptr::null_mut();
release_sudog(s_take);
let _ = Box::from_raw(s_skip);
let _ = Box::from_raw(g_skip); let _ = Box::from_raw(g_take);
}
}
#[test]
fn waitq_dequeue_sudog_middle() {
let g1 = make_g(); let g2 = make_g(); let g3 = make_g();
let s1 = acquire_sudog();
let s2 = acquire_sudog();
let s3 = acquire_sudog();
unsafe {
(*s1).g = g1; (*s2).g = g2; (*s3).g = g3;
(*g1).atomicstatus.store(GWAITING, Release);
(*g2).atomicstatus.store(GWAITING, Release);
(*g3).atomicstatus.store(GWAITING, Release);
}
let mut q = WaitQ::new();
unsafe { q.enqueue(s1); q.enqueue(s2); q.enqueue(s3); }
unsafe { q.dequeue_sudog(s2) };
assert_eq!(q.first, s1);
assert_eq!(q.last, s3);
unsafe {
assert_eq!((*s1).next, s3);
assert_eq!((*s3).prev, s1);
assert!((*s2).next.is_null());
assert!((*s2).prev.is_null());
}
let got1 = unsafe { q.dequeue() };
let got3 = unsafe { q.dequeue() };
assert_eq!(got1, s1);
assert_eq!(got3, s3);
assert!(q.is_empty());
unsafe {
for (s, g) in [(s1,g1),(s2,g2),(s3,g3)] {
(*s).g = std::ptr::null_mut(); (*s).c = std::ptr::null_mut();
release_sudog(s);
let _ = Box::from_raw(g);
}
}
}
}