#![allow(unsafe_code)]
use core::task::{Poll, Waker};
use super::contend::{Contend, Lock};
use super::internal::{AtomicUsize, Ordering, UnsafeCell};
use super::list::List;
use super::queue::{
Arrivals, FIRST_BIT, HANDOFF, Nudges, Policy, QUEUED, Step, WaitQueue, wants_drain,
};
use super::waiter::WakeList;
#[cfg(test)]
std::thread_local! {
static BETWEEN_TAKE_AND_GRANT: core::cell::Cell<Option<Box<dyn FnOnce()>>> =
const { core::cell::Cell::new(None) };
}
#[cfg(test)]
pub(crate) fn between_take_and_grant(hook: impl FnOnce() + 'static) {
BETWEEN_TAKE_AND_GRANT.set(Some(Box::new(hook)));
}
const FLAGS: usize = (1 << FIRST_BIT) - 1;
const GRANTING: usize = 1 << FIRST_BIT;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct OwnerId(usize);
impl OwnerId {
pub(crate) const ALIGN: usize = GRANTING << 1;
pub(crate) fn of<T>(owner: &T) -> Self {
const { assert!(core::mem::align_of::<T>() >= OwnerId::ALIGN) };
Self(core::ptr::from_ref(owner).addr())
}
pub(crate) fn word(self) -> usize {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum TryLock {
Taken,
Mine,
Busy,
}
pub(crate) struct OwnedLock {
queue: WaitQueue,
waiting: UnsafeCell<List>,
wake_all: AtomicUsize,
}
unsafe impl Send for OwnedLock {}
unsafe impl Sync for OwnedLock {}
impl OwnedLock {
pub(crate) fn new() -> Self {
Self {
queue: WaitQueue::new(0),
waiting: UnsafeCell::new(List::new()),
wake_all: AtomicUsize::new(0),
}
}
pub(crate) fn holder(&self) -> usize {
self.queue.state.load(Ordering::Acquire) & !FLAGS
}
pub(crate) fn try_lock(&self, owner: OwnerId) -> TryLock {
match self.try_take(owner.word()) {
Ok(()) => TryLock::Taken,
Err(seen) if seen & !FLAGS == owner.word() => TryLock::Mine,
Err(_) => TryLock::Busy,
}
}
#[cfg(all(loom, test))]
pub(crate) fn names(&self, owner: OwnerId) -> bool {
self.holder() & !GRANTING == owner.word()
}
pub(crate) fn unlock(&self, owner: OwnerId) -> bool {
let state = &self.queue.state;
let mut current = state.load(Ordering::Acquire);
loop {
if current & !FLAGS & !GRANTING != owner.word() {
return false;
}
match state.compare_exchange_weak(
current,
current & FLAGS,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if wants_drain(current) {
self.released();
}
return true;
}
Err(actual) => current = actual,
}
}
}
pub(crate) fn wake_all(&self) {
self.wake_all.store(1, Ordering::Release);
self.transition(Step::Drain);
}
fn granted(&self, owner: usize) {
let state = &self.queue.state;
let mut current = state.load(Ordering::Acquire);
while current & !FLAGS == owner | GRANTING {
match state.compare_exchange_weak(
current,
current & !GRANTING,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return,
Err(actual) => current = actual,
}
}
}
fn clear_holder(&self, owner: usize) {
let state = &self.queue.state;
let mut current = state.load(Ordering::Acquire);
while current & !FLAGS == owner {
match state.compare_exchange_weak(
current,
current & FLAGS,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return,
Err(actual) => current = actual,
}
}
}
#[cfg(all(test, not(loom)))]
pub(crate) fn linked(&self) -> usize {
self.waiting.with(|waiting| unsafe { (*waiting).len() })
}
}
impl Default for OwnedLock {
fn default() -> Self {
Self::new()
}
}
impl Lock for OwnedLock {
const SPINS: bool = false;
fn admit(&self, state: usize, need: usize) -> Option<usize> {
(state & !FLAGS == 0).then_some(state | need)
}
fn give_back(&self, need: usize) {
self.unlock(OwnerId(need));
}
}
impl Policy for OwnedLock {
fn queue(&self) -> &WaitQueue {
&self.queue
}
fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize {
let queue = &self.queue;
self.waiting.with_mut(|waiting| unsafe {
let waiting = &mut *waiting;
waiting.absorb(arrivals, queue);
waiting.tidy(queue);
if self.wake_all.swap(0, Ordering::AcqRel) != 0 {
let mut nudges = Nudges::new(woken, queue);
for node in waiting.iter() {
nudges.nudge(node);
}
nudges.finish();
}
let mut handed = false;
if queue.state.load(Ordering::Acquire) & HANDOFF != 0 {
while let Some(node) = waiting.front(queue) {
let owner = node.as_ref().payload();
if !self.take_for_waiter(owner | GRANTING) {
break;
}
#[cfg(test)]
if let Some(hook) = BETWEEN_TAKE_AND_GRANT.take() {
hook();
}
waiting.pop_front();
if woken.grant(node) {
self.granted(owner);
handed = true;
} else {
self.clear_holder(owner | GRANTING);
}
}
}
if !queue.hands_off()
&& let Some(front) = waiting.front(queue)
&& ((wake_front && !handed) || queue.state.load(Ordering::Acquire) & !FLAGS == 0)
{
let mut nudges = Nudges::new(woken, queue);
nudges.nudge(front);
nudges.finish();
}
if waiting.front(queue).is_none() {
QUEUED
} else {
0
}
})
}
}
impl Drop for OwnedLock {
fn drop(&mut self) {
let queue = &self.queue;
self.waiting
.with_mut(|waiting| unsafe { (*waiting).clear(queue) });
}
}
pub(crate) struct OwnedWait {
contend: Contend,
}
impl OwnedWait {
pub(crate) const fn new() -> Self {
Self {
contend: Contend::new(),
}
}
pub(crate) fn is_queued(&self) -> bool {
self.contend.is_queued()
}
pub(crate) fn poll(&mut self, lock: &OwnedLock, owner: OwnerId, waker: &Waker) -> Poll<()> {
self.contend.poll(lock, owner.word(), waker)
}
pub(crate) fn cancel(&mut self, lock: &OwnedLock, owner: OwnerId) {
self.contend.cancel(lock, owner.word());
}
}
impl Drop for OwnedWait {
fn drop(&mut self) {
debug_assert!(
!self.is_queued(),
"a queued lock wait was dropped without a cancel"
);
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::super::MAX_BYPASS;
use super::super::test_support::Polled;
use super::*;
#[repr(align(128))]
struct Token(#[allow(dead_code)] u8);
fn polled_wait<'a>(
lock: &'a OwnedLock,
owner: OwnerId,
wait: &'a mut OwnedWait,
) -> Polled<impl core::future::Future<Output = ()> + 'a> {
Polled::new(core::future::poll_fn(move |cx| {
wait.poll(lock, owner, cx.waker())
}))
}
#[test]
fn the_holder_is_named_in_the_take_and_only_it_unlocks() {
let (a, b) = (Token(0), Token(0));
let (a, b) = (OwnerId::of(&a), OwnerId::of(&b));
let lock = OwnedLock::new();
assert_eq!(lock.holder(), 0);
assert_eq!(lock.try_lock(a), TryLock::Taken);
assert_eq!(lock.holder(), a.word());
assert_eq!(lock.try_lock(a), TryLock::Mine, "a reads its own name");
assert_eq!(lock.try_lock(b), TryLock::Busy);
assert!(!lock.unlock(b), "b does not hold it");
assert_eq!(lock.holder(), a.word());
assert!(lock.unlock(a));
assert!(!lock.unlock(a), "a second unlock changes nothing");
assert_eq!(lock.try_lock(b), TryLock::Taken);
}
#[test]
fn a_release_nudges_the_front_and_a_barger_still_wins() {
let (a, b, c) = (Token(0), Token(0), Token(0));
let (a, b, c) = (OwnerId::of(&a), OwnerId::of(&b), OwnerId::of(&c));
let lock = OwnedLock::new();
assert_eq!(lock.try_lock(a), TryLock::Taken);
let mut wait = OwnedWait::new();
let mut waiter = polled_wait(&lock, b, &mut wait);
waiter.pending();
assert!(lock.unlock(a));
assert_eq!(waiter.wakes(), 1, "the release woke the front");
assert_eq!(lock.try_lock(c), TryLock::Taken, "a free lock is barged");
waiter.pending();
assert!(lock.unlock(c));
waiter.ready();
assert_eq!(lock.holder(), b.word());
}
#[test]
fn a_waiter_passed_over_max_bypass_times_is_handed_the_lock() {
let (a, b) = (Token(0), Token(0));
let (a, b) = (OwnerId::of(&a), OwnerId::of(&b));
let lock = OwnedLock::new();
assert_eq!(lock.try_lock(a), TryLock::Taken);
let mut wait = OwnedWait::new();
let mut waiter = polled_wait(&lock, b, &mut wait);
waiter.pending();
for round in 1..MAX_BYPASS {
assert!(lock.unlock(a));
assert_eq!(lock.try_lock(a), TryLock::Taken, "round {round}: barged");
waiter.pending();
}
assert!(lock.unlock(a));
assert_eq!(lock.try_lock(a), TryLock::Taken);
waiter.pending();
assert!(lock.unlock(a));
assert_eq!(lock.holder(), b.word(), "handed over in b's name");
assert_eq!(lock.try_lock(a), TryLock::Busy, "nobody barges a handoff");
waiter.ready();
assert!(lock.unlock(b));
assert_eq!(lock.try_lock(a), TryLock::Taken, "barging resumes");
}
#[test]
fn a_handoff_to_a_cancelled_waiter_passes_on() {
let (a, b, c) = (Token(0), Token(0), Token(0));
let (a, b, c) = (OwnerId::of(&a), OwnerId::of(&b), OwnerId::of(&c));
let lock = OwnedLock::new();
assert_eq!(lock.try_lock(a), TryLock::Taken);
let mut first = OwnedWait::new();
let mut second = OwnedWait::new();
{
let mut one = polled_wait(&lock, b, &mut first);
one.pending();
let mut two = polled_wait(&lock, c, &mut second);
two.pending();
}
for _ in 0..MAX_BYPASS {
assert!(lock.unlock(a));
assert_eq!(lock.try_lock(a), TryLock::Taken);
let mut one = polled_wait(&lock, b, &mut first);
let _ = one.poll();
}
assert!(lock.unlock(a));
assert_eq!(lock.holder(), b.word(), "handed to the owed waiter");
first.cancel(&lock, b);
let mut two = polled_wait(&lock, c, &mut second);
two.ready();
assert_eq!(lock.holder(), c.word());
assert!(lock.unlock(c));
assert_eq!(lock.holder(), 0);
}
#[test]
fn wake_all_nudges_every_waiter() {
let (a, b, c) = (Token(0), Token(0), Token(0));
let (a, b, c) = (OwnerId::of(&a), OwnerId::of(&b), OwnerId::of(&c));
let lock = OwnedLock::new();
assert_eq!(lock.try_lock(a), TryLock::Taken);
let mut first = OwnedWait::new();
let mut second = OwnedWait::new();
let mut one = polled_wait(&lock, b, &mut first);
let mut two = polled_wait(&lock, c, &mut second);
one.pending();
two.pending();
lock.wake_all();
assert_eq!((one.wakes(), two.wakes()), (1, 1));
one.pending();
two.pending();
drop((one, two));
first.cancel(&lock, b);
second.cancel(&lock, c);
assert!(lock.unlock(a));
assert_eq!(lock.try_lock(b), TryLock::Taken);
}
#[test]
fn owners_on_threads_never_overlap() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
const ITERATIONS: usize = if cfg!(miri) { 20 } else { 2_000 };
let lock = Arc::new(OwnedLock::new());
let inside = Arc::new(AtomicUsize::new(0));
let total = Arc::new(AtomicUsize::new(0));
let threads: Vec<_> = (0..4)
.map(|_| {
let (lock, inside, total) =
(Arc::clone(&lock), Arc::clone(&inside), Arc::clone(&total));
std::thread::spawn(move || {
let token = Token(0);
let me = OwnerId::of(&token);
for _ in 0..ITERATIONS {
if lock.try_lock(me) != TryLock::Taken {
let mut wait = OwnedWait::new();
crate::sync::test_support::block_on(core::future::poll_fn(|cx| {
wait.poll(&lock, me, cx.waker())
}));
}
assert_eq!(inside.fetch_add(1, Ordering::SeqCst), 0, "two holders");
total.fetch_add(1, Ordering::SeqCst);
inside.fetch_sub(1, Ordering::SeqCst);
assert!(lock.unlock(me));
}
})
})
.collect();
for thread in threads {
thread.join().expect("worker");
}
assert_eq!(total.load(Ordering::SeqCst), 4 * ITERATIONS);
assert_eq!(lock.holder(), 0);
assert_eq!(lock.linked(), 0, "no node left behind");
}
}