blob: 7bf948523b14d2f5199ab5f9ac11a76db9c373b4 [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! Kernel-specific [`ChainLockTransaction`] hooks, kcounters, state savers, and
//! [`SingleChainLockGuard`].
use core::cell::Cell;
use core::marker::PhantomData;
use core::mem::{MaybeUninit, offset_of};
use core::ops::Deref;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicU32, Ordering};
use concurrent::{
AtomicToken, SelectAccounting, SelectTracing, StateOption, StateSaver, Token, TransactionHooks,
};
use crate::arch_rs::{
InterruptSavedState, arch_interrupt_restore, arch_interrupt_save, curr_cpu_num, ints_disabled,
};
use crate::counters::define_kcounter;
use crate::kernel::percpu::PerCpu;
use crate::kernel::thread::{
eager_resched_disable, eager_resched_reenable, preempt_disable, preempt_reenable,
};
use crate::kernel::types::cpu_num_to_mask;
use crate::ktrace_rs::InstantBootTicks;
use crate::platform_rs::timer::{
DurationMonoTicks, timer_current_mono_ticks, timer_get_ticks_to_time_ratio,
};
use super::chainlock::{
CLT_DEBUG_CHECKS_ENABLED, CLT_TRACING_ENABLED, CallsiteInfo, ChainLock, ChainLockGuard,
ChainLockTransaction, ChainLockTransactionToken, EagerReschedDisable, IrqSave, NoIrqSave,
PreemptDisable,
};
use super::chainlock_proof::{ChainLockProof, ChainLocked};
define_kcounter!(CHAINLOCK_BACKOFFS, "chainlock.backoffs", Sum);
define_kcounter!(CHAINLOCK_BACKOFFS_MAX, "chainlock.backoffs_max", Max);
define_kcounter!(CHAINLOCK_CONTENTION_NS, "chainlock.contention_ns", Sum);
define_kcounter!(CHAINLOCK_CONTENTION_NS_MAX, "chainlock.contention_ns_max", Max);
#[inline]
fn on_backoff_counters(backoff_count: u64) {
CHAINLOCK_BACKOFFS.add(1);
CHAINLOCK_BACKOFFS_MAX.max(backoff_count as i64);
}
#[inline]
fn update_contention_counters(contention_ticks: DurationMonoTicks) {
let ticks_to_time = timer_get_ticks_to_time_ratio();
let contention_ns = ticks_to_time * contention_ticks.0;
CHAINLOCK_CONTENTION_NS.add(contention_ns);
CHAINLOCK_CONTENTION_NS_MAX.max(contention_ns);
}
/// FFI entry point called by C++ `ChainLockTransaction::OnBackoff()`.
// SAFETY:
// - `rust_chainlock_on_backoff` is a globally unique symbol name in the kernel image, declared
// only in `zircon/kernel/lib/kconcurrent/chainlock_transaction.cc`.
// - The `extern "C" fn(u64)` signature matches the C++ `extern "C" void
// rust_chainlock_on_backoff(uint64_t)` declaration.
#[unsafe(no_mangle)]
pub extern "C" fn rust_chainlock_on_backoff(backoff_count: u64) {
on_backoff_counters(backoff_count);
}
/// FFI entry point called by C++ `ChainLockTransaction::UpdateContentionCounters()`.
// SAFETY:
// - `rust_chainlock_update_contention_counters` is a globally unique symbol name in the kernel
// image, declared only in `zircon/kernel/lib/kconcurrent/chainlock_transaction.cc`.
// - `DurationMonoTicks` is `#[repr(transparent)]` over `i64`, so the `extern "C"
// fn(DurationMonoTicks)` signature has the exact same C ABI as the C++ `extern "C" void
// rust_chainlock_update_contention_counters(zx_duration_mono_ticks_t)` (`int64_t`) declaration.
#[unsafe(no_mangle)]
pub extern "C" fn rust_chainlock_update_contention_counters(contention_ticks: DurationMonoTicks) {
update_contention_counters(contention_ticks);
}
/// Kernel environment hooks for [`ChainLockTransaction`].
///
/// Adapts the environment-independent `concurrent` chain lock library to the Zircon kernel
/// environment.
pub struct KernelTransactionHooks;
// SAFETY:
// - `KernelTransactionHooks::active()` returns the exact pointer most recently passed to
// `KernelTransactionHooks::set_active()` on the current CPU via
// `PerCpu::get_current().active_cl_transaction()` (or `None` when null).
// - All kernel transaction state savers ensure interrupts are disabled (`IrqSave`,
// `PreemptDisable`, and `EagerReschedDisable` disable interrupts via `arch_interrupt_save()`,
// and `NoIrqSave::save_state()` enforces `assert!(ints_disabled())`) and keep interrupts
// disabled while a transaction is registered. Because interrupts are disabled on the current CPU
// while a transaction is active, neither same-CPU interrupt reentrancy nor cross-CPU thread
// migration can occur while registered, ensuring the backing `PerCpu` slot is strictly local to
// the current execution context.
unsafe impl TransactionHooks for KernelTransactionHooks {
type ContentionState = AtomicU32;
type CallsiteInfo = CallsiteInfo;
type Accounting = SelectAccounting<CLT_DEBUG_CHECKS_ENABLED>;
type Tracing = SelectTracing<CLT_TRACING_ENABLED, CallsiteInfo>;
type ExtraState = Cell<u64>;
#[inline]
fn active() -> core::option::Option<NonNull<ChainLockTransaction>> {
debug_assert!(ints_disabled());
NonNull::new(PerCpu::get_current().active_cl_transaction())
}
/// Sets the active [`ChainLockTransaction`] pointer for the current CPU.
///
/// # Safety
///
/// The caller must ensure that:
/// - Interrupts are disabled on the current CPU (`ints_disabled()`).
/// - `transaction` is either `None` or points to a valid, initialized, stack-pinned
/// [`ChainLockTransaction`] owned by the current execution context that remains valid and
/// free of mutable aliases (with interrupts disabled) until cleared or replaced by a
/// subsequent call to `set_active`.
#[inline]
unsafe fn set_active(transaction: core::option::Option<NonNull<ChainLockTransaction>>) {
debug_assert!(ints_disabled());
let ptr = transaction.map_or(core::ptr::null_mut(), NonNull::as_ptr);
// SAFETY: The caller of `TransactionHooks::set_active` guarantees that interrupts are
// disabled on the current CPU and that `transaction` is either `None` or points to a live,
// stack-pinned `ChainLockTransaction` without mutable aliases until cleared or replaced by
// a subsequent `set_active` call.
unsafe {
PerCpu::get_current().set_active_cl_transaction(ptr);
}
}
#[inline]
fn yield_cpu() {
core::hint::spin_loop();
}
#[inline]
fn allocate_reserved_token() -> Token {
debug_assert!(ints_disabled());
ChainLockTransaction::reserved_token(curr_cpu_num() as usize)
}
#[inline]
fn get_current_ticks() -> i64 {
timer_current_mono_ticks().0
}
#[inline]
fn on_finalized(contention_start_ticks: i64, callsite_info: CallsiteInfo) {
// Note: `get_current_ticks()` returns monotonic ticks (`InstantMonoTicks`), which is
// intentionally wrapped here as `InstantBootTicks` to match C++
// `ChainLockTransaction::GetCurrentTicks()` / `OnFinalized()`.
ktrace_macro::complete!(
"kernel:sched",
"lock_spin",
InstantBootTicks(contention_start_ticks),
"lock_id" => callsite_info.line_number,
"lock_class" => callsite_info
.label
.unwrap_or_else(|| ktrace_macro::resolve_string!("<unnamed>")),
"lock_type" => ktrace_macro::resolve_string!("ChainLockTransaction"),
"backoff_count" => {
assert!(ints_disabled());
let active = ChainLockTransaction::active()
.expect("on_finalized called without active transaction");
// SAFETY: `ints_disabled()` is asserted above and `active` was obtained from
// `ChainLockTransaction::active()`, which by `PerCpu::active_cl_transaction`'s
// safety invariant points to a valid, initialized, stack-pinned
// `ChainLockTransaction` on the current CPU with no mutable aliases.
unsafe { active.as_ref().backoff_count() }
},
);
update_contention_counters(DurationMonoTicks(
timer_current_mono_ticks().0.saturating_sub(contention_start_ticks),
));
}
#[inline]
fn on_backoff(transaction: &ChainLockTransaction) {
on_backoff_counters(transaction.backoff_count());
}
#[inline]
fn record_backoff_or_retry(
transaction: &ChainLockTransaction,
observed_token: Token,
lock_state: &AtomicToken,
contention_state: &Self::ContentionState,
) -> bool {
let percpu = PerCpu::get_current();
let current_cpu = curr_cpu_num();
let current_cpu_mask = cpu_num_to_mask(current_cpu);
// Observe the conflict id for the current cpu and add this cpu to the contention state of
// the chain lock instigating the backoff operation.
let conflict_id = percpu.chain_lock_conflict_id.load(Ordering::Acquire);
contention_state.fetch_or(current_cpu_mask, Ordering::AcqRel);
// If the lock state has the same token after observing the conflict id, this transaction
// needs to wait until the conflict is (potentially) resolved by the owner incrementing the
// conflict id when releasing the lock.
let current_token = lock_state.load(Ordering::Acquire);
if current_token == observed_token {
transaction.extra().set(conflict_id);
return true;
}
false
}
#[inline]
fn release_and_notify<F: FnOnce()>(
_transaction: &ChainLockTransaction,
contention_state: &Self::ContentionState,
do_release: F,
) {
// Sample the contention state before releasing the chainlock. The caller will ensure that
// contention state will not be modified until after do_release is called.
let mut wake_mask = contention_state.swap(0, Ordering::AcqRel);
// Perform the actual chainlock release operation, allowing contenders to attempt to
// acquire the lock. The contention state MUST NOT be accessed after the release to avoid
// use-after-free hazards!!
do_release();
// Use the sampled contention state to poke any backed-off contenders.
while wake_mask != 0 {
let cpu = wake_mask.trailing_zeros();
PerCpu::get(cpu).chain_lock_conflict_id.fetch_add(1, Ordering::Release);
wake_mask &= wake_mask - 1;
}
}
}
/// Waits for the observed contention to be resolved on the given `percpu` that was latched before
/// IRQ/preemption state was restored during relax.
#[inline]
fn wait_for_conflict_resolution(transaction: &ChainLockTransaction, percpu: &PerCpu) {
let conflict_id = transaction.extra().get();
if conflict_id != 0 {
// Wait for the contention to be resolved on the CPU where the contention by this
// transaction was originally observed. That might be a different CPU than the current
// thread is on, since the thread might be able to migrate during the relax phase of
// transaction backoff.
loop {
core::hint::spin_loop();
if percpu.chain_lock_conflict_id.load(Ordering::Acquire) != conflict_id {
break;
}
}
transaction.extra().set(0);
} else {
core::hint::spin_loop();
}
}
/// State saver for [`NoIrqSave`].
#[repr(C)]
#[derive(Default)]
pub struct NoIrqSaveStateSaver {
// Matches the 1-byte size of the empty C++ struct `StateSaver<StateOptions::NoIrqSave>`.
_byte: u8,
_not_send: PhantomData<*mut ()>,
}
zr::static_assert!(size_of::<NoIrqSaveStateSaver>() == 1);
zr::static_assert!(align_of::<NoIrqSaveStateSaver>() == 1);
impl StateSaver<ChainLockTransaction> for NoIrqSaveStateSaver {
#[inline]
fn relax(&mut self, transaction: &ChainLockTransaction) {
wait_for_conflict_resolution(transaction, PerCpu::get_current());
}
}
impl StateOption<ChainLockTransaction> for NoIrqSave {
type Saver = NoIrqSaveStateSaver;
#[inline]
fn save_state(&self) -> Self::Saver {
assert!(ints_disabled());
NoIrqSaveStateSaver { _byte: 0, _not_send: PhantomData }
}
}
/// State saver for [`IrqSave`].
#[repr(C)]
pub struct IrqSaveStateSaver {
// Safety invariant: Holds the interrupt state saved when interrupts were disabled by this
// saver (or most recently re-saved after `relax`).
interrupt_state: InterruptSavedState,
_not_send: PhantomData<*mut ()>,
}
zr::static_assert!(size_of::<IrqSaveStateSaver>() == size_of::<InterruptSavedState>());
zr::static_assert!(align_of::<IrqSaveStateSaver>() == align_of::<InterruptSavedState>());
impl IrqSaveStateSaver {
/// Saves current interrupt state and disables interrupts.
#[inline]
fn new() -> Self {
Self { interrupt_state: arch_interrupt_save(), _not_send: PhantomData }
}
}
impl Drop for IrqSaveStateSaver {
#[inline]
fn drop(&mut self) {
arch_interrupt_restore(self.interrupt_state);
}
}
impl StateSaver<ChainLockTransaction> for IrqSaveStateSaver {
#[inline]
fn relax(&mut self, transaction: &ChainLockTransaction) {
assert_eq!(ChainLockTransaction::active(), Some(NonNull::from(transaction)));
// Must be latched before enabling interrupts so that the correct contention state is
// monitored, even if the thread migrates during relax.
let percpu = PerCpu::get_current();
// SAFETY: `assert_eq!` above verified that `transaction` is currently the active
// transaction on this CPU, and no `ChainLockTransactionToken` is used while `transaction`
// is unregistered between here and `register()` below.
unsafe {
transaction.unregister();
}
arch_interrupt_restore(self.interrupt_state);
wait_for_conflict_resolution(transaction, percpu);
self.interrupt_state = arch_interrupt_save();
// SAFETY: `transaction` was verified above to have been registered as the active
// transaction prior to `unregister()` (so its caller upholds the stack-pinning invariant
// for the duration of the transaction), interrupts are disabled by `arch_interrupt_save()`
// above, and no transaction is currently registered on this CPU.
let _ = unsafe { transaction.register() };
}
}
impl StateOption<ChainLockTransaction> for IrqSave {
type Saver = IrqSaveStateSaver;
#[inline]
fn save_state(&self) -> Self::Saver {
IrqSaveStateSaver::new()
}
}
/// State saver for [`PreemptDisable`].
#[repr(C)]
pub struct PreemptDisableStateSaver {
// Safety invariant: Holds the interrupt state saved when interrupts were disabled by this
// saver (or most recently re-saved after `relax`), while preemption is also disabled.
interrupt_state: InterruptSavedState,
_not_send: PhantomData<*mut ()>,
}
zr::static_assert!(size_of::<PreemptDisableStateSaver>() == size_of::<InterruptSavedState>());
zr::static_assert!(align_of::<PreemptDisableStateSaver>() == align_of::<InterruptSavedState>());
impl PreemptDisableStateSaver {
/// Disables preemption and saves/disables interrupts.
#[inline]
fn new() -> Self {
preempt_disable();
let interrupt_state = arch_interrupt_save();
Self { interrupt_state, _not_send: PhantomData }
}
}
impl Drop for PreemptDisableStateSaver {
#[inline]
fn drop(&mut self) {
arch_interrupt_restore(self.interrupt_state);
preempt_reenable();
}
}
impl StateSaver<ChainLockTransaction> for PreemptDisableStateSaver {
#[inline]
fn relax(&mut self, transaction: &ChainLockTransaction) {
assert_eq!(ChainLockTransaction::active(), Some(NonNull::from(transaction)));
// Must be latched before enabling interrupts/preemption so that the correct contention
// state is monitored, even if the thread migrates during relax.
let percpu = PerCpu::get_current();
// SAFETY: `assert_eq!` above verified that `transaction` is currently the active
// transaction on this CPU, and no `ChainLockTransactionToken` is used while `transaction`
// is unregistered between here and `register()` below.
unsafe {
transaction.unregister();
}
arch_interrupt_restore(self.interrupt_state);
preempt_reenable();
wait_for_conflict_resolution(transaction, percpu);
preempt_disable();
self.interrupt_state = arch_interrupt_save();
// SAFETY: `transaction` was verified above to have been registered as the active
// transaction prior to `unregister()` (so its caller upholds the stack-pinning invariant
// for the duration of the transaction), interrupts are disabled by `arch_interrupt_save()`
// above, and no transaction is currently registered on this CPU.
let _ = unsafe { transaction.register() };
}
}
impl StateOption<ChainLockTransaction> for PreemptDisable {
type Saver = PreemptDisableStateSaver;
#[inline]
fn save_state(&self) -> Self::Saver {
PreemptDisableStateSaver::new()
}
}
/// State saver for [`EagerReschedDisable`].
#[repr(C)]
pub struct EagerReschedDisableStateSaver {
// Safety invariant: Holds the interrupt state saved when interrupts were disabled by this
// saver (or most recently re-saved after `relax`), while eager rescheduling is also disabled.
interrupt_state: InterruptSavedState,
_not_send: PhantomData<*mut ()>,
}
zr::static_assert!(size_of::<EagerReschedDisableStateSaver>() == size_of::<InterruptSavedState>());
zr::static_assert!(
align_of::<EagerReschedDisableStateSaver>() == align_of::<InterruptSavedState>()
);
impl EagerReschedDisableStateSaver {
/// Disables eager rescheduling and saves/disables interrupts.
#[inline]
fn new() -> Self {
eager_resched_disable();
let interrupt_state = arch_interrupt_save();
Self { interrupt_state, _not_send: PhantomData }
}
}
impl Drop for EagerReschedDisableStateSaver {
#[inline]
fn drop(&mut self) {
arch_interrupt_restore(self.interrupt_state);
eager_resched_reenable();
}
}
impl StateSaver<ChainLockTransaction> for EagerReschedDisableStateSaver {
#[inline]
fn relax(&mut self, transaction: &ChainLockTransaction) {
assert_eq!(ChainLockTransaction::active(), Some(NonNull::from(transaction)));
// Must be latched before enabling interrupts/eager resched so that the correct contention
// state is monitored, even if the thread migrates during relax.
let percpu = PerCpu::get_current();
// SAFETY: `assert_eq!` above verified that `transaction` is currently the active
// transaction on this CPU, and no `ChainLockTransactionToken` is used while `transaction`
// is unregistered between here and `register()` below.
unsafe {
transaction.unregister();
}
arch_interrupt_restore(self.interrupt_state);
eager_resched_reenable();
wait_for_conflict_resolution(transaction, percpu);
eager_resched_disable();
self.interrupt_state = arch_interrupt_save();
// SAFETY: `transaction` was verified above to have been registered as the active
// transaction prior to `unregister()` (so its caller upholds the stack-pinning invariant
// for the duration of the transaction), interrupts are disabled by `arch_interrupt_save()`
// above, and no transaction is currently registered on this CPU.
let _ = unsafe { transaction.register() };
}
}
impl StateOption<ChainLockTransaction> for EagerReschedDisable {
type Saver = EagerReschedDisableStateSaver;
#[inline]
fn save_state(&self) -> Self::Saver {
EagerReschedDisableStateSaver::new()
}
}
/// Scoped guard that saves state, starts and finalizes a [`ChainLockTransaction`], and acquires a
/// single [`ChainLock`].
#[repr(C)]
pub struct SingleChainLockGuard<'a, O: StateOption<ChainLockTransaction>> {
// The order of construction/destruction of these members is important. State must be saved
// before starting the transaction and the transaction must be started before acquiring the
// lock with the guard. Likewise, the guard needs to be released before completing the
// transaction and the transaction needs to be completed before restoring state.
//
// We use `ManuallyDrop` so that the struct field layout matches C++ (`state_saver_`,
// `transaction_`, `guard_`) while `SingleChainLockScope::drop` explicitly destroys them in
// reverse order (`guard`, `transaction`, `state_saver`).
//
// Safety invariant: Once initialized via `new_in_place`, all fields remain initialized and the
// struct remains pinned in place until `SingleChainLockScope::drop` drops `guard`,
// `transaction`, and `state_saver` in reverse order.
state_saver: core::mem::ManuallyDrop<O::Saver>,
transaction: core::mem::ManuallyDrop<ChainLockTransaction>,
guard: core::mem::ManuallyDrop<ChainLockGuard<'a>>,
token: ChainLockTransactionToken,
}
zr::static_assert!(offset_of!(SingleChainLockGuard<'static, IrqSave>, state_saver) == 0);
zr::static_assert!(offset_of!(SingleChainLockGuard<'static, IrqSave>, transaction) == 8);
impl<'a, O: StateOption<ChainLockTransaction>> SingleChainLockGuard<'a, O> {
/// Initializes a [`SingleChainLockGuard`] in-place in `slot` and returns a
/// [`SingleChainLockScope`] that drops it when leaving scope.
///
/// Prefer using the [`single_chain_lock_guard!`] macro or [`Self::scope`].
///
/// # Safety
///
/// The caller must ensure that the returned [`SingleChainLockScope`] is dropped (and not
/// passed to `core::mem::forget`) before `slot` is overwritten, moved, or deallocated.
#[inline]
pub unsafe fn new_in_place<'slot>(
slot: &'slot mut MaybeUninit<Self>,
option: O,
lock: &'a ChainLock,
callsite_info: CallsiteInfo,
) -> SingleChainLockScope<'slot, 'a, O> {
let state_saver = option.save_state();
let transaction = ChainLockTransaction::new_unregistered(callsite_info, 0, false);
let base_ptr = slot.as_mut_ptr();
// SAFETY:
// - `base_ptr` is derived from `slot: &'slot mut MaybeUninit<Self>`, so it is non-null,
// properly aligned, and valid for writes to all fields of `Self`.
// - `state_saver` and `transaction` are written into their pinned locations in `slot`
// before calling `register()`.
// - By this function's `# Safety` contract, the caller guarantees that the returned
// `SingleChainLockScope` is dropped before `slot` is overwritten, moved, or deallocated,
// so `(*base_ptr).transaction` remains alive and pinned at its address in `slot` until
// `SingleChainLockScope::drop` drops it, discharging `register()`'s pinning
// precondition.
// - `token` is written into `(*base_ptr).token` before borrowing `&(*base_ptr).token` for
// `ChainLockGuard::new` and `ChainLockTransaction::finalize`.
// - After `register()`, only disjoint sibling fields (`token`, `guard`) are accessed via
// raw field projections and no `&mut Self` covering `transaction` is created while it is
// registered in `PerCpu`.
// - `ChainLockGuard::new` and `ChainLockTransaction::finalize` are infallible and do not
// unwind once `transaction` is registered.
unsafe {
(&raw mut (*base_ptr).state_saver).write(core::mem::ManuallyDrop::new(state_saver));
(&raw mut (*base_ptr).transaction).write(core::mem::ManuallyDrop::new(transaction));
let token = (*base_ptr).transaction.register();
(&raw mut (*base_ptr).token).write(token);
let guard = ChainLockGuard::new(lock, &(*base_ptr).token);
(&raw mut (*base_ptr).guard).write(core::mem::ManuallyDrop::new(guard));
ChainLockTransaction::finalize(&(*base_ptr).token);
SingleChainLockScope { ptr: NonNull::new_unchecked(base_ptr), _marker: PhantomData }
}
}
/// Executes `f` while holding `lock` inside a finalized single-lock [`ChainLockTransaction`]
/// configured with `option`.
#[inline]
pub fn scope<R, F: FnOnce(&Self) -> R>(
option: O,
lock: &'a ChainLock,
callsite_info: CallsiteInfo,
f: F,
) -> R {
let mut slot = MaybeUninit::uninit();
// SAFETY: `guard` is dropped at the end of this function before `slot` goes out of scope.
let guard = unsafe { Self::new_in_place(&mut slot, option, lock, callsite_info) };
f(&guard)
}
/// Returns a reference to the [`ChainLockTransactionToken`] for this active transaction.
#[inline]
pub const fn token(&self) -> &ChainLockTransactionToken {
&self.token
}
/// Returns a reference to the active [`ChainLockTransaction`].
#[inline]
pub fn transaction(&self) -> &ChainLockTransaction {
&self.transaction
}
/// Returns a reference to the underlying [`ChainLockGuard`].
#[inline]
pub fn guard(&self) -> &ChainLockGuard<'a> {
&self.guard
}
}
/// RAII scope handle returned by [`SingleChainLockGuard::new_in_place`].
#[must_use = "if unused, the SingleChainLockGuard will immediately unlock"]
pub struct SingleChainLockScope<'slot, 'a, O: StateOption<ChainLockTransaction>> {
// Safety invariant: Points to a `SingleChainLockGuard` initialized in `'slot` by
// `SingleChainLockGuard::new_in_place` whose fields remain valid and free of mutable aliases
// until `drop`.
ptr: NonNull<SingleChainLockGuard<'a, O>>,
_marker: PhantomData<&'slot mut MaybeUninit<SingleChainLockGuard<'a, O>>>,
}
impl<'slot, 'a, O: StateOption<ChainLockTransaction>> Deref for SingleChainLockScope<'slot, 'a, O> {
type Target = SingleChainLockGuard<'a, O>;
#[inline]
fn deref(&self) -> &Self::Target {
// SAFETY: By `SingleChainLockScope` and `SingleChainLockGuard`'s safety invariants,
// `self.ptr` is properly aligned and points to an initialized `SingleChainLockGuard` valid
// for `'slot`, no mutable references to it exist while `self` is alive, and any mutation
// of `transaction` via `PerCpu::active_cl_transaction` occurs exclusively through
// `Cell`/`UnsafeCell` interior mutability.
unsafe { self.ptr.as_ref() }
}
}
impl<'slot, 'a, O: StateOption<ChainLockTransaction>> Drop for SingleChainLockScope<'slot, 'a, O> {
#[inline]
fn drop(&mut self) {
let base_ptr = self.ptr.as_ptr();
// SAFETY:
// - By `SingleChainLockScope` and `SingleChainLockGuard`'s safety invariants, each
// `ManuallyDrop` field at `base_ptr` was initialized in `new_in_place` and has not yet
// been dropped.
// - We drop the fields in reverse construction order (`guard`, then `transaction`, then
// `state_saver`) by projecting directly from `base_ptr`:
// 1. Dropping `(*base_ptr).guard` projects only to the disjoint `guard` field, avoiding
// any `&mut` reference covering `(*base_ptr).transaction` while `ChainLockGuard::drop`
// accesses `transaction` via `PerCpu::active_cl_transaction`.
// 2. Dropping `(*base_ptr).transaction` runs after `guard` is dropped while interrupts
// remain disabled via `state_saver`; `ChainLockTransaction::drop` clears
// `PerCpu::active_cl_transaction` without dereferencing the old pointer.
// 3. Dropping `(*base_ptr).state_saver` runs after `transaction` has been unregistered
// and dropped, safely restoring interrupt and preemption state.
unsafe {
core::mem::ManuallyDrop::drop(&mut (*base_ptr).guard);
core::mem::ManuallyDrop::drop(&mut (*base_ptr).transaction);
core::mem::ManuallyDrop::drop(&mut (*base_ptr).state_saver);
}
}
}
/// Acquires a single [`ChainLock`] inside a finalized [`ChainLockTransaction`] pinned on the
/// current stack frame for the enclosing lexical scope.
///
/// Because `$guard` is bound as a shared reference (`&SingleChainLockGuard`) rather than a
/// by-value RAII guard (to prevent safe callers from passing the stack-pinned scope guard to
/// [`core::mem::forget`]), the lock is always held for the entire enclosing lexical scope and
/// cannot be released early via `drop(guard)`. Use an inner block or
/// [`SingleChainLockGuard::scope`] if a shorter lock lifetime is needed.
///
/// # Example
/// ```ignore
/// single_chain_lock_guard!(guard, IRQ_SAVE_OPTION, &lock, clt_tag!("example"));
/// ```
#[macro_export]
macro_rules! single_chain_lock_guard {
($guard:ident, $option:expr, $lock:expr, $callsite_info:expr $(,)?) => {
let __single_cl_guard_option = $option;
let __single_cl_guard_lock = $lock;
let __single_cl_guard_callsite_info = $callsite_info;
let mut __single_cl_guard_slot = ::core::mem::MaybeUninit::uninit();
// SAFETY: `__single_cl_guard_scope` is bound in the same lexical scope immediately after
// `__single_cl_guard_slot` and cannot be named or passed to `core::mem::forget` by the
// caller, ensuring it is dropped before `__single_cl_guard_slot` goes out of scope.
let __single_cl_guard_scope = unsafe {
$crate::kconcurrent::SingleChainLockGuard::new_in_place(
&mut __single_cl_guard_slot,
__single_cl_guard_option,
__single_cl_guard_lock,
__single_cl_guard_callsite_info,
)
};
#[allow(unused_variables)]
let $guard: &$crate::kconcurrent::SingleChainLockGuard<'_, _> = &__single_cl_guard_scope;
};
}
pub use single_chain_lock_guard;
/// Scope handle created by [`single_chain_lock_guard_owned!`] that holds a [`ChainLocked`]
/// owner's lock and mints [`ChainLockProof`]s via [`Self::proof`].
#[must_use = "if unused, the owner's ChainLock will immediately be released"]
pub struct OwnedSingleChainLockScope<
'slot,
'a,
Owner: ChainLocked,
O: StateOption<ChainLockTransaction>,
> {
// Safety invariant: `scope`'s guard acquired `owner.lock()` in `new_in_place` and holds it
// until `scope` is dropped; nothing exposes the guard mutably.
scope: SingleChainLockScope<'slot, 'a, O>,
owner: &'a Owner,
}
impl<'slot, 'a, Owner: ChainLocked, O: StateOption<ChainLockTransaction>>
OwnedSingleChainLockScope<'slot, 'a, Owner, O>
{
/// Initializes a [`SingleChainLockGuard`] on `owner.lock()` in-place in `slot` and returns a
/// scope handle that drops it when leaving scope. Prefer [`single_chain_lock_guard_owned!`].
///
/// # Safety
///
/// As [`SingleChainLockGuard::new_in_place`]: the returned scope must be dropped (and not
/// passed to `core::mem::forget`) before `slot` is overwritten, moved, or deallocated.
#[inline]
pub unsafe fn new_in_place(
slot: &'slot mut MaybeUninit<SingleChainLockGuard<'a, O>>,
owner: &'a Owner,
option: O,
callsite_info: CallsiteInfo,
) -> Self {
// SAFETY: `as_chain_lock`: the raw lock is only acquired through the inner guard, which
// releases it once, on scope drop, after every proof (each borrows `self` mutably).
// `new_in_place`: the caller's contract, passed through to the returned scope.
let scope = unsafe {
SingleChainLockGuard::new_in_place(
slot,
option,
owner.lock().as_chain_lock(),
callsite_info,
)
};
Self { scope, owner }
}
/// Mints a proof that `owner.lock()` is held: this scope acquired it and holds it until
/// dropped; the `&mut` borrow allows one live proof per scope.
#[inline]
pub fn proof(&mut self) -> ChainLockProof<'_, Owner> {
// SAFETY: Acquired and held: the type's safety invariant. Lifetime and uniqueness: the
// proof is bounded by this `&mut self` borrow.
unsafe { ChainLockProof::from_acquiring_guard(self.owner) }
}
/// Returns the owner whose lock this scope holds.
#[inline]
pub const fn owner(&self) -> &'a Owner {
self.owner
}
/// Returns a reference to the [`ChainLockTransactionToken`] for this active transaction.
#[inline]
pub fn token(&self) -> &ChainLockTransactionToken {
self.scope.token()
}
/// Returns a reference to the active [`ChainLockTransaction`].
#[inline]
pub fn transaction(&self) -> &ChainLockTransaction {
self.scope.transaction()
}
}
/// Acquires `$owner.lock()` in a new transaction and binds `$scope` as
/// `&mut OwnedSingleChainLockScope`.
///
/// Because the scope acquires `$owner`'s lock directly, [`OwnedSingleChainLockScope::proof`]
/// mints a [`ChainLockProof`](crate::kconcurrent::ChainLockProof) for `$owner` safely and without
/// runtime checks.
///
/// # Example
/// ```ignore
/// single_chain_lock_guard_owned!(scope, &owner, IRQ_SAVE_OPTION, clt_tag!("example"));
/// let mut proof = scope.proof();
/// ```
#[macro_export]
macro_rules! single_chain_lock_guard_owned {
($scope:ident, $owner:expr, $option:expr, $callsite_info:expr $(,)?) => {
let mut __single_cl_guard_slot = ::core::mem::MaybeUninit::uninit();
let __single_cl_guard_owner = $owner;
let __single_cl_guard_option = $option;
let __single_cl_guard_callsite_info = $callsite_info;
// SAFETY: `__single_cl_guard_scope` is bound in the same lexical scope immediately after
// `__single_cl_guard_slot` and cannot be named or passed to `core::mem::forget` by the
// caller, ensuring it is dropped before `__single_cl_guard_slot` goes out of scope.
let mut __single_cl_guard_scope = unsafe {
$crate::kconcurrent::OwnedSingleChainLockScope::new_in_place(
&mut __single_cl_guard_slot,
__single_cl_guard_owner,
__single_cl_guard_option,
__single_cl_guard_callsite_info,
)
};
#[allow(unused_variables)]
let $scope: &mut $crate::kconcurrent::OwnedSingleChainLockScope<'_, '_, _, _> =
&mut __single_cl_guard_scope;
};
}
pub use single_chain_lock_guard_owned;
/// Tests for kernel [`ChainLock`] and [`ChainLockTransaction`] primitives.
#[cfg(ktest)]
#[unittest::suite(name = "kconcurrent_rust")]
mod tests {
use core::sync::atomic::AtomicUsize;
use concurrent::{Action, DONE, TransactionResult};
use unittest::{assert_false, assert_true, expect_eq, expect_true};
use crate::arch_rs::InterruptDisableGuard;
use crate::kconcurrent::{
EAGER_RESCHED_DISABLE_AND_IRQ_SAVE_OPTION, IRQ_SAVE_OPTION, NO_IRQ_SAVE_OPTION,
PREEMPT_DISABLE_AND_IRQ_SAVE_OPTION, SavedState, acquire_both_or_backoff, clt_tag,
};
use crate::kernel::mp::{MpIpiTarget, get_online_mask, sync_exec};
use crate::kernel::thread::{eager_resched_disable_count, preempt_disable_count};
/// Verifies type layouts and monotonic token generation.
#[test]
fn layout_and_token_generator() {
expect_eq!(size_of::<ChainLock>(), 16);
expect_eq!(align_of::<ChainLock>(), 8);
expect_eq!(size_of::<ChainLockGuard<'_>>(), 16);
expect_eq!(align_of::<ChainLockGuard<'_>>(), 8);
expect_eq!(size_of::<SavedState>(), 16);
expect_eq!(align_of::<SavedState>(), 8);
expect_eq!(size_of::<NoIrqSaveStateSaver>(), 1);
expect_eq!(size_of::<IrqSaveStateSaver>(), size_of::<InterruptSavedState>());
expect_eq!(size_of::<PreemptDisableStateSaver>(), size_of::<InterruptSavedState>());
expect_eq!(size_of::<EagerReschedDisableStateSaver>(), size_of::<InterruptSavedState>());
let t1 = ChainLockTransaction::next_token();
let t2 = ChainLockTransaction::next_token();
expect_true!(t1.is_valid());
expect_true!(!t1.is_reserved());
expect_true!(t2 > t1);
}
/// Verifies IRQ and preemption state options across transactions.
#[test]
fn state_options_irq_and_preempt() {
assert_false!(ints_disabled());
let initial_preempt = preempt_disable_count();
let initial_eager = eager_resched_disable_count();
// 1. IrqSave
let mut irq_disabled_in_tx = false;
let mut active_in_tx = false;
let mut preempt_in_tx = 0;
let mut eager_in_tx = 0;
let val = ChainLockTransaction::until_done(
IRQ_SAVE_OPTION,
clt_tag!("test_irq_save"),
|_token| -> TransactionResult<u32> {
irq_disabled_in_tx = ints_disabled();
active_in_tx = ChainLockTransaction::active().is_some();
preempt_in_tx = preempt_disable_count();
eager_in_tx = eager_resched_disable_count();
TransactionResult::Value(11)
},
);
expect_true!(irq_disabled_in_tx);
expect_true!(active_in_tx);
expect_eq!(preempt_in_tx, initial_preempt);
expect_eq!(eager_in_tx, initial_eager);
expect_eq!(val, 11);
assert_false!(ints_disabled());
// 2. NoIrqSave (must manually disable interrupts first)
{
let _irq_guard = InterruptDisableGuard::new();
assert_true!(ints_disabled());
expect_true!(ChainLockTransaction::active().is_none());
let mut no_irq_ok = false;
ChainLockTransaction::until_done(
NO_IRQ_SAVE_OPTION,
clt_tag!("test_no_irq_save"),
|_token| {
no_irq_ok = ints_disabled() && ChainLockTransaction::active().is_some();
DONE
},
);
expect_true!(no_irq_ok);
expect_true!(ChainLockTransaction::active().is_none());
assert_true!(ints_disabled());
}
assert_false!(ints_disabled());
// 3. PreemptDisable
ChainLockTransaction::until_done(
PREEMPT_DISABLE_AND_IRQ_SAVE_OPTION,
clt_tag!("test_preempt_disable"),
|_token| {
irq_disabled_in_tx = ints_disabled();
preempt_in_tx = preempt_disable_count();
eager_in_tx = eager_resched_disable_count();
DONE
},
);
expect_true!(irq_disabled_in_tx);
expect_eq!(preempt_in_tx, initial_preempt + 1);
expect_eq!(eager_in_tx, initial_eager);
assert_false!(ints_disabled());
expect_eq!(preempt_disable_count(), initial_preempt);
// 4. EagerReschedDisable
ChainLockTransaction::until_done(
EAGER_RESCHED_DISABLE_AND_IRQ_SAVE_OPTION,
clt_tag!("test_eager_resched_disable"),
|_token| {
irq_disabled_in_tx = ints_disabled();
preempt_in_tx = preempt_disable_count();
eager_in_tx = eager_resched_disable_count();
DONE
},
);
expect_true!(irq_disabled_in_tx);
expect_eq!(preempt_in_tx, initial_preempt);
expect_eq!(eager_in_tx, initial_eager + 1);
assert_false!(ints_disabled());
expect_eq!(eager_resched_disable_count(), initial_eager);
}
/// Verifies `SingleChainLockGuard` macro and scope helpers across state options.
#[test]
fn single_chain_lock_guard_variants() {
let lock = ChainLock::new();
assert_false!(ints_disabled());
let initial_preempt = preempt_disable_count();
let initial_eager = eager_resched_disable_count();
{
single_chain_lock_guard!(guard, IRQ_SAVE_OPTION, &lock, clt_tag!("single_irq_save"));
expect_true!(ints_disabled());
expect_true!(lock.is_held(guard.token()));
if ChainLockTransaction::DEBUG_ACCOUNTING_ENABLED {
expect_eq!(guard.transaction().locks_held(), 1);
expect_true!(guard.transaction().is_finalized());
}
}
assert_false!(ints_disabled());
expect_true!(lock.is_unlocked());
{
let _irq = InterruptDisableGuard::new();
single_chain_lock_guard!(
guard,
NO_IRQ_SAVE_OPTION,
&lock,
clt_tag!("single_no_irq_save")
);
expect_true!(ints_disabled());
expect_true!(lock.is_held(guard.token()));
}
assert_false!(ints_disabled());
expect_true!(lock.is_unlocked());
let mut scope_ok = false;
let res = SingleChainLockGuard::scope(
PREEMPT_DISABLE_AND_IRQ_SAVE_OPTION,
&lock,
clt_tag!("single_preempt"),
|guard| {
scope_ok = ints_disabled()
&& preempt_disable_count() == initial_preempt + 1
&& lock.is_held(guard.token());
42u32
},
);
expect_true!(scope_ok);
expect_eq!(res, 42);
assert_false!(ints_disabled());
expect_eq!(preempt_disable_count(), initial_preempt);
expect_true!(lock.is_unlocked());
let mut eager_scope_ok = false;
SingleChainLockGuard::scope(
EAGER_RESCHED_DISABLE_AND_IRQ_SAVE_OPTION,
&lock,
clt_tag!("single_eager"),
|guard| {
eager_scope_ok = ints_disabled()
&& eager_resched_disable_count() == initial_eager + 1
&& lock.is_held(guard.token());
},
);
expect_true!(eager_scope_ok);
assert_false!(ints_disabled());
expect_eq!(eager_resched_disable_count(), initial_eager);
expect_true!(lock.is_unlocked());
{
let lock_ptr: *const ChainLock = &lock;
// SAFETY: `lock_ptr` points to the live stack `lock`. Passing an `unsafe`
// expression as `$lock` verifies `single_chain_lock_guard!` evaluates its
// arguments outside its internal `unsafe` block without `unused_unsafe`.
single_chain_lock_guard!(
guard,
IRQ_SAVE_OPTION,
unsafe { &*lock_ptr },
clt_tag!("single_unsafe_expr")
);
expect_true!(ints_disabled());
expect_true!(lock.is_held(guard.token()));
}
assert_false!(ints_disabled());
expect_true!(lock.is_unlocked());
}
/// Verifies chaining multiple locks and restarting a transaction.
#[test]
fn transaction_chain_and_restart() {
let lock1 = ChainLock::new();
let lock2 = ChainLock::new();
let mut attempts = 0u32;
let mut held_on_restart = false;
let result = ChainLockTransaction::until_done(
IRQ_SAVE_OPTION,
clt_tag!("chain_and_restart"),
|token| -> TransactionResult<u32> {
attempts += 1;
if attempts == 1 {
lock1.acquire_first_in_chain(token);
assert!(lock2.acquire_or_backoff(token));
ChainLockTransaction::finalize(token);
lock2.release(token);
return Action::Restart.into();
}
// On restart, lock1 is still held and transaction is unfinalized.
held_on_restart = lock1.is_held(token);
assert!(lock2.acquire_or_backoff(token));
ChainLockTransaction::finalize(token);
lock2.release(token);
lock1.release(token);
TransactionResult::Value(attempts)
},
);
expect_true!(held_on_restart);
expect_eq!(result, 2);
expect_true!(lock1.is_unlocked());
expect_true!(lock2.is_unlocked());
}
/// Verifies saving transaction state, switching to a per-CPU reserved token, and restoring.
#[test]
fn save_and_restore_state() {
let lock = ChainLock::new();
let mut orig_not_reserved = false;
let mut saved_matches_orig = false;
let mut reserved_is_reserved = false;
let mut reserved_matches_cpu = false;
let mut synced_to_reserved = false;
let mut restored_matches_orig = false;
let mut synced_to_orig = false;
ChainLockTransaction::until_done(IRQ_SAVE_OPTION, clt_tag!("save_and_restore"), |token| {
lock.acquire_first_in_chain(token);
ChainLockTransaction::finalize(token);
let orig_token = ChainLockTransaction::active_ref(token).token();
orig_not_reserved = !orig_token.is_reserved();
let saved = ChainLockTransaction::save_state_and_use_reserved_token(token);
saved_matches_orig = saved.token() == orig_token;
let reserved_token = ChainLockTransaction::active_ref(token).token();
reserved_is_reserved = reserved_token.is_reserved();
reserved_matches_cpu = reserved_token.value() == (curr_cpu_num() as u64) + 1;
lock.sync_token(token);
synced_to_reserved = lock.state_for_testing() == reserved_token;
// Simulate context switch clearing active transaction and restoring it.
// SAFETY: Interrupts are disabled via `IRQ_SAVE_OPTION`, `token` is not used before
// `restore_state(saved)` on the next line, and passing `None` clears the active
// transaction pointer.
unsafe { KernelTransactionHooks::set_active(None) };
// SAFETY: `saved` was produced by `save_state_and_use_reserved_token` for the
// enclosing `until_done` transaction (which remains alive and stack-pinned for the
// duration of `until_done`), interrupts are disabled, and the active transaction slot
// was cleared above.
unsafe { ChainLockTransaction::restore_state(saved) };
restored_matches_orig = ChainLockTransaction::active_ref(token).token() == orig_token;
lock.sync_token(token);
synced_to_orig = lock.state_for_testing() == orig_token;
lock.release(token);
DONE
});
expect_true!(orig_not_reserved);
expect_true!(saved_matches_orig);
expect_true!(reserved_is_reserved);
expect_true!(reserved_matches_cpu);
expect_true!(synced_to_reserved);
expect_true!(restored_matches_orig);
expect_true!(synced_to_orig);
}
/// Verifies backoff recording, `chain_lock_conflict_id` latching, and conflict resolution.
#[test]
fn backoff_and_conflict_resolution() {
// Custom StateOption that simulates the lock owner releasing the lock and incrementing
// `chain_lock_conflict_id` during `relax()`.
struct SimulatedOwnerOption<'a> {
lock: &'a ChainLock,
owner_tx: &'a ChainLockTransaction,
relaxed: &'a Cell<bool>,
}
struct SimulatedOwnerSaver<'a> {
inner: IrqSaveStateSaver,
lock: &'a ChainLock,
owner_tx: &'a ChainLockTransaction,
relaxed: &'a Cell<bool>,
}
impl<'a> StateSaver<ChainLockTransaction> for SimulatedOwnerSaver<'a> {
fn relax(&mut self, transaction: &ChainLockTransaction) {
// Verify that `record_backoff_or_retry` latched the current CPU's non-zero
// `chain_lock_conflict_id`.
let latched_conflict_id = transaction.extra().get();
assert_ne!(latched_conflict_id, 0);
assert_eq!(self.lock.state_for_testing(), self.owner_tx.token());
// Temporarily switch the active transaction to `owner_tx` while interrupts remain
// disabled so `owner_tx` can release `self.lock` and notify contenders via
// `release_and_notify`.
// SAFETY: `transaction` is currently the active transaction on this CPU and no
// token for `transaction` is used until `transaction.register()` below.
unsafe { transaction.unregister() };
// SAFETY: `self.owner_tx` is pinned on the enclosing test stack frame and
// interrupts are disabled on this CPU.
let owner_token = unsafe { self.owner_tx.register() };
self.lock.release(&owner_token);
// SAFETY: `self.owner_tx` is currently the active transaction and `owner_token` is
// not used after this point.
unsafe { self.owner_tx.unregister() };
// SAFETY: `transaction` is pinned on `until_done`'s stack frame and interrupts are
// disabled on this CPU.
let _ = unsafe { transaction.register() };
assert_ne!(
PerCpu::get_current().chain_lock_conflict_id.load(Ordering::Relaxed),
latched_conflict_id
);
self.relaxed.set(true);
// Now delegate to `IrqSaveStateSaver::relax`, which will see that
// `chain_lock_conflict_id` has changed and return immediately.
self.inner.relax(transaction);
assert_eq!(transaction.extra().get(), 0);
}
}
impl<'a> StateOption<ChainLockTransaction> for SimulatedOwnerOption<'a> {
type Saver = SimulatedOwnerSaver<'a>;
fn save_state(&self) -> Self::Saver {
SimulatedOwnerSaver {
inner: IrqSaveStateSaver::new(),
lock: self.lock,
owner_tx: self.owner_tx,
relaxed: self.relaxed,
}
}
}
let lock = ChainLock::new();
// Allocate and acquire `lock` with a higher-priority transaction BEFORE the contender
// transaction starts so `owner_tx.token() < contender_tx.token()`.
let mut owner_tx = core::mem::ManuallyDrop::new(ChainLockTransaction::new_unregistered(
clt_tag!("owner_tx"),
0,
false,
));
{
let _irq = InterruptDisableGuard::new();
// SAFETY: `owner_tx` remains alive and pinned at its stack location for the duration
// of this test, and interrupts are disabled.
let owner_token = unsafe { owner_tx.register() };
lock.acquire_first_in_chain(&owner_token);
ChainLockTransaction::finalize(&owner_token);
// SAFETY: `owner_tx` is currently the active transaction and `owner_token` is not used
// after this point.
unsafe { owner_tx.unregister() };
}
let relaxed = Cell::new(false);
let mut observed_backoffs = 0u64;
let before_backoffs = CHAINLOCK_BACKOFFS.sum_across_all_cpus();
ChainLockTransaction::until_done(
SimulatedOwnerOption { lock: &lock, owner_tx: &owner_tx, relaxed: &relaxed },
clt_tag!("backoff_conflict_resolution"),
|token| -> TransactionResult<()> {
if !lock.acquire_or_backoff(token) {
return Action::Backoff.into();
}
observed_backoffs = ChainLockTransaction::active_ref(token).backoff_count();
ChainLockTransaction::finalize(token);
lock.release(token);
TransactionResult::DONE
},
);
{
let _irq = InterruptDisableGuard::new();
// SAFETY: `owner_tx` is still alive at its original stack location and interrupts are
// disabled; we re-register it before dropping it in-place so
// `ChainLockTransaction::drop` unregisters it cleanly.
unsafe {
let _ = owner_tx.register();
core::mem::ManuallyDrop::drop(&mut owner_tx);
}
}
expect_eq!(observed_backoffs, 1);
expect_true!(relaxed.get());
expect_true!(lock.is_unlocked());
expect_true!(CHAINLOCK_BACKOFFS.sum_across_all_cpus() > before_backoffs);
}
/// Verifies multi-CPU contention and wound-wait deadlock avoidance across online CPUs.
#[test]
fn multi_cpu_contention() {
let online_mask = get_online_mask();
if online_mask.count_ones() < 2 {
return true;
}
let lock_a = ChainLock::new();
let lock_b = ChainLock::new();
let counter_a = AtomicUsize::new(0);
let counter_b = AtomicUsize::new(0);
sync_exec(MpIpiTarget::Mask, online_mask, || {
let cpu = curr_cpu_num();
for _ in 0..50 {
// Note: `sync_exec` runs with interrupts disabled in IPI context, so we use
// `NO_IRQ_SAVE_OPTION`.
ChainLockTransaction::until_done(
NO_IRQ_SAVE_OPTION,
clt_tag!("multi_cpu_contention"),
|token| -> TransactionResult<()> {
let acquired = if (cpu & 1) == 0 {
acquire_both_or_backoff(&lock_a, &lock_b, token)
} else {
acquire_both_or_backoff(&lock_b, &lock_a, token)
};
if !acquired {
return Action::Backoff.into();
}
ChainLockTransaction::finalize(token);
counter_a.fetch_add(1, Ordering::Relaxed);
counter_b.fetch_add(1, Ordering::Relaxed);
lock_b.release(token);
lock_a.release(token);
TransactionResult::DONE
},
);
}
});
let expected = (online_mask.count_ones() as usize) * 50;
expect_eq!(counter_a.load(Ordering::Relaxed), expected);
expect_eq!(counter_b.load(Ordering::Relaxed), expected);
expect_true!(lock_a.is_unlocked());
expect_true!(lock_b.is_unlocked());
}
}