use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
#[repr(C, align(64))]
pub struct HandshakeHeader {
pub generation: AtomicU32,
pub strategy_tag: AtomicU32,
_pad0: [u8; 56],
pub in_flight: [AtomicU64; 2],
_pad1: [u8; 48],
}
impl HandshakeHeader {
pub const fn new() -> Self {
Self {
generation: AtomicU32::new(0),
strategy_tag: AtomicU32::new(0),
_pad0: [0; 56],
in_flight: [AtomicU64::new(0), AtomicU64::new(0)],
_pad1: [0; 48],
}
}
#[inline(always)]
pub fn enter_op(&self) -> u32 {
loop {
let current = self.generation.load(Ordering::Acquire);
let slot = (current & 1) as usize;
self.in_flight[slot].fetch_add(1, Ordering::AcqRel);
let recheck = self.generation.load(Ordering::Acquire);
if recheck == current {
return current;
}
self.in_flight[slot].fetch_sub(1, Ordering::AcqRel);
core::hint::spin_loop();
}
}
#[inline]
pub fn in_flight_count(&self, generation: u32) -> u64 {
self.in_flight[(generation & 1) as usize].load(Ordering::Acquire)
}
#[inline(always)]
pub fn exit_op(&self, captured_gen: u32) {
self.in_flight[(captured_gen & 1) as usize].fetch_sub(1, Ordering::Release);
}
#[inline(always)]
pub fn tag(&self) -> u32 {
self.strategy_tag.load(Ordering::Relaxed)
}
pub fn bump_generation(&self) -> u32 {
let old_value = self.generation.load(Ordering::Acquire);
self.generation.store(old_value.wrapping_add(1), Ordering::Release);
old_value
}
#[inline]
pub fn set_tag(&self, new_tag: u32) {
self.strategy_tag.store(new_tag, Ordering::Release);
}
pub fn migrate(&self, new_tag: u32) -> u32 {
let old_value = self.generation.load(Ordering::Acquire);
self.strategy_tag.store(new_tag, Ordering::Relaxed);
self.generation.store(old_value.wrapping_add(1), Ordering::Release);
old_value
}
pub fn drain(&self, generation: u32) {
let slot = (generation & 1) as usize;
while self.in_flight[slot].load(Ordering::Acquire) != 0 {
core::hint::spin_loop();
}
}
}
impl Default for HandshakeHeader {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_size_is_two_cache_lines() {
assert_eq!(core::mem::size_of::<HandshakeHeader>(), 128);
assert_eq!(core::mem::align_of::<HandshakeHeader>(), 64);
}
#[test]
fn enter_exit_balances() {
let h = HandshakeHeader::new();
let g = h.enter_op();
assert_eq!(g, 0);
assert_eq!(h.in_flight[0].load(Ordering::Relaxed), 1);
h.exit_op(g);
assert_eq!(h.in_flight[0].load(Ordering::Relaxed), 0);
}
#[test]
fn migrate_bumps_generation_and_swaps_tag() {
let h = HandshakeHeader::new();
assert_eq!(h.tag(), 0);
let old = h.migrate(7);
assert_eq!(old, 0);
assert_eq!(h.generation.load(Ordering::Acquire), 1);
assert_eq!(h.tag(), 7);
}
#[test]
fn bump_generation_does_not_touch_tag() {
let h = HandshakeHeader::new();
h.set_tag(3);
let old = h.bump_generation();
assert_eq!(old, 0);
assert_eq!(h.generation.load(Ordering::Acquire), 1);
assert_eq!(h.tag(), 3, "tag must not change on generation bump");
}
#[test]
fn set_tag_does_not_touch_generation() {
let h = HandshakeHeader::new();
h.set_tag(5);
assert_eq!(h.tag(), 5);
assert_eq!(h.generation.load(Ordering::Acquire), 0, "generation must not change on tag set");
}
#[test]
fn drain_returns_when_in_flight_zero() {
let h = HandshakeHeader::new();
h.drain(0);
let g = h.enter_op();
h.exit_op(g);
h.drain(0);
}
}