use std::ptr::NonNull;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use subetha_core::{HandshakeHeader, ObservationRing};
use once_cell::sync::Lazy;
use parking_lot::{Mutex, RwLock};
pub mod bench_safe;
pub type InstanceId = u32;
pub const N_OP_KINDS: usize = 8;
pub const MAX_TRACKED_THREADS_PER_KIND: usize = 4;
#[derive(Debug, Clone, Copy)]
pub struct InstanceStats {
pub ops_observed: u64,
pub total_latency_ticks: u64,
pub contention_ops: u64,
pub op_kind_counts: [u64; N_OP_KINDS],
pub last_seen_us_ago: u64,
pub migrations_triggered: u64,
pub per_op_kind_distinct_threads: [[u32; MAX_TRACKED_THREADS_PER_KIND]; N_OP_KINDS],
pub per_op_kind_distinct_count: [u8; N_OP_KINDS],
}
impl Default for InstanceStats {
fn default() -> Self {
Self {
ops_observed: 0,
total_latency_ticks: 0,
contention_ops: 0,
op_kind_counts: [0; N_OP_KINDS],
last_seen_us_ago: 0,
migrations_triggered: 0,
per_op_kind_distinct_threads: [[0; MAX_TRACKED_THREADS_PER_KIND]; N_OP_KINDS],
per_op_kind_distinct_count: [0; N_OP_KINDS],
}
}
}
impl InstanceStats {
pub fn average_latency_ticks(&self) -> u64 {
self.total_latency_ticks.checked_div(self.ops_observed).unwrap_or(0)
}
pub fn contention_rate(&self) -> f64 {
if self.ops_observed == 0 {
0.0
} else {
self.contention_ops as f64 / self.ops_observed as f64
}
}
pub fn op_kind_total(&self) -> u64 {
self.op_kind_counts.iter().sum()
}
pub fn ratio_of(&self, kind: u16, total_kinds: &[u16]) -> f64 {
let k = (kind as usize).min(N_OP_KINDS - 1);
let kind_count = self.op_kind_counts[k];
let total: u64 = total_kinds.iter()
.map(|&i| self.op_kind_counts[(i as usize).min(N_OP_KINDS - 1)])
.sum();
if total == 0 {
0.0
} else {
kind_count as f64 / total as f64
}
}
pub fn distinct_threads_for(&self, kind: u16) -> u8 {
let k = (kind as usize).min(N_OP_KINDS - 1);
self.per_op_kind_distinct_count[k]
}
pub fn is_multi_thread_for(&self, kind: u16) -> bool {
self.distinct_threads_for(kind) >= 2
}
}
#[inline]
fn record_thread_for_op(
stats: &mut InstanceStats,
op_kind: u16,
tid: u32,
) {
if tid == 0 {
return;
}
let k = (op_kind as usize).min(N_OP_KINDS - 1);
let count = stats.per_op_kind_distinct_count[k];
if (count as usize) > MAX_TRACKED_THREADS_PER_KIND {
return;
}
let slots = &mut stats.per_op_kind_distinct_threads[k];
let n = (count as usize).min(MAX_TRACKED_THREADS_PER_KIND);
if slots[..n].contains(&tid) {
return;
}
if n < MAX_TRACKED_THREADS_PER_KIND {
slots[n] = tid;
stats.per_op_kind_distinct_count[k] = (n as u8) + 1;
} else {
stats.per_op_kind_distinct_count[k] = (MAX_TRACKED_THREADS_PER_KIND as u8) + 1;
}
}
pub trait Policy: Send + Sync + 'static {
fn decide(&self, stats: &InstanceStats, current_tag: u32) -> Option<u32>;
}
pub struct FixedPolicy(pub u32);
impl Policy for FixedPolicy {
fn decide(&self, _stats: &InstanceStats, _current_tag: u32) -> Option<u32> {
Some(self.0)
}
}
pub struct NoMigrationPolicy;
impl Policy for NoMigrationPolicy {
fn decide(&self, _stats: &InstanceStats, _current_tag: u32) -> Option<u32> {
None
}
}
struct Registration {
header: NonNull<HandshakeHeader>,
ring: NonNull<ObservationRing>,
instance: Option<NonNull<dyn AdaptiveInstance>>,
policy: Box<dyn Policy>,
stats: Mutex<InstanceStats>,
registered_at: Instant,
last_observation_at: Mutex<Option<Instant>>,
}
unsafe impl Send for Registration {}
unsafe impl Sync for Registration {}
const DRAIN_SAFETY_CAP: usize = 8192;
const POLL_INTERVAL: Duration = Duration::from_micros(200);
struct NodeSidecar {
instances: RwLock<Vec<Option<Registration>>>,
}
impl NodeSidecar {
fn new() -> Self {
Self { instances: RwLock::new(Vec::new()) }
}
}
pub struct Sidecar {
nodes: Vec<NodeSidecar>,
shutdown: Arc<AtomicBool>,
join_handles: Mutex<Vec<JoinHandle<()>>>,
instance_count: AtomicUsize,
max_instances: AtomicUsize,
}
pub const DEFAULT_MAX_INSTANCES: usize = 10_000;
const NODE_ID_BITS: u32 = 8;
const SLOT_MASK: u32 = (1 << (32 - NODE_ID_BITS)) - 1;
fn pack_id(node: u32, slot: u32) -> InstanceId {
(node << (32 - NODE_ID_BITS)) | (slot & SLOT_MASK)
}
fn unpack_id(id: InstanceId) -> (u32, u32) {
(id >> (32 - NODE_ID_BITS), id & SLOT_MASK)
}
impl Sidecar {
fn new() -> Arc<Self> {
let shutdown = Arc::new(AtomicBool::new(false));
let num_nodes = numa_node_count().max(1) as usize;
let mut nodes = Vec::with_capacity(num_nodes);
for _ in 0..num_nodes {
nodes.push(NodeSidecar::new());
}
let sidecar = Arc::new(Self {
nodes,
shutdown: shutdown.clone(),
join_handles: Mutex::new(Vec::with_capacity(num_nodes)),
instance_count: AtomicUsize::new(0),
max_instances: AtomicUsize::new(DEFAULT_MAX_INSTANCES),
});
let mut handles = Vec::with_capacity(num_nodes);
for node_idx in 0..num_nodes {
let runner = sidecar.clone();
let handle = thread::Builder::new()
.name(format!("subetha-sidecar-node{node_idx}"))
.spawn(move || runner.run_loop_for_node(node_idx))
.expect("failed to spawn subetha-sidecar node thread");
handles.push(handle);
}
*sidecar.join_handles.lock() = handles;
sidecar
}
fn run_loop_for_node(self: Arc<Self>, node_idx: usize) {
while !self.shutdown.load(Ordering::Acquire) {
self.scan_node(node_idx);
thread::sleep(POLL_INTERVAL);
}
}
fn scan_node(&self, node_idx: usize) {
let Some(node) = self.nodes.get(node_idx) else { return };
let guard = node.instances.read();
Self::scan_instances(&guard);
}
fn scan_instances(instances: &[Option<Registration>]) {
for reg_opt in instances.iter() {
let Some(reg) = reg_opt else { continue };
let ring = unsafe { reg.ring.as_ref() };
let header = unsafe { reg.header.as_ref() };
let mut drained_ops: u64 = 0;
let mut drained_lat: u64 = 0;
let mut drained_cont: u64 = 0;
let mut drained_kinds: [u64; N_OP_KINDS] = [0; N_OP_KINDS];
const DEDUPE_CAP: usize = N_OP_KINDS * MAX_TRACKED_THREADS_PER_KIND;
let mut tid_dedupe: [(u16, u32); DEDUPE_CAP] = [(0, 0); DEDUPE_CAP];
let mut tid_dedupe_len: usize = 0;
for _ in 0..DRAIN_SAFETY_CAP {
let Some(obs) = ring.pop() else { break };
drained_ops += 1;
drained_lat = drained_lat.saturating_add(obs.latency_ticks);
if obs.flags & 1 != 0 {
drained_cont += 1;
}
let k = (obs.op_kind as usize).min(N_OP_KINDS - 1);
drained_kinds[k] = drained_kinds[k].saturating_add(1);
if obs.producer_thread_id != 0 && tid_dedupe_len < DEDUPE_CAP {
let pair = (obs.op_kind, obs.producer_thread_id);
let seen = tid_dedupe[..tid_dedupe_len].contains(&pair);
if !seen {
tid_dedupe[tid_dedupe_len] = pair;
tid_dedupe_len += 1;
}
}
}
if drained_ops == 0 {
continue;
}
let stats_snapshot = {
let mut s = reg.stats.lock();
s.ops_observed = s.ops_observed.saturating_add(drained_ops);
s.total_latency_ticks = s.total_latency_ticks.saturating_add(drained_lat);
s.contention_ops = s.contention_ops.saturating_add(drained_cont);
for (slot, drained) in s.op_kind_counts.iter_mut().zip(drained_kinds.iter()) {
*slot = slot.saturating_add(*drained);
}
for &(op_kind, tid) in tid_dedupe[..tid_dedupe_len].iter() {
record_thread_for_op(&mut s, op_kind, tid);
}
let now = Instant::now();
*reg.last_observation_at.lock() = Some(now);
s.last_seen_us_ago = now
.duration_since(reg.registered_at)
.as_micros() as u64;
*s
};
let current_tag = header.tag();
if let Some(new_tag) = reg.policy.decide(&stats_snapshot, current_tag)
&& new_tag != current_tag {
if let Some(inst_ptr) = reg.instance {
let inst = unsafe { &*inst_ptr.as_ptr() };
inst.apply_migration(new_tag);
} else {
header.set_tag(new_tag);
}
let mut s = reg.stats.lock();
s.migrations_triggered = s.migrations_triggered.saturating_add(1);
}
}
}
pub unsafe fn register_raw(
&self,
header: NonNull<HandshakeHeader>,
ring: NonNull<ObservationRing>,
instance: Option<NonNull<dyn AdaptiveInstance>>,
policy: Box<dyn Policy>,
) -> InstanceId {
let cap = self.max_instances.load(Ordering::Acquire);
let prev = self.instance_count.fetch_add(1, Ordering::AcqRel);
if prev >= cap {
self.instance_count.fetch_sub(1, Ordering::AcqRel);
panic!(
"subetha-sidecar: instance cap ({cap}) exceeded.\n\
Likely cause: SidecarBox<Adaptive*> is being created \
inside a tight loop (criterion b.iter(), test fixture, \
or runaway production code). Move construction outside \
the loop and reuse the instance, or call \
Sidecar::set_max_instances() if the load is intentional."
);
}
unsafe { ring.as_ref().arm(); }
let reg = Registration {
header,
ring,
instance,
policy,
stats: Mutex::new(InstanceStats::default()),
registered_at: Instant::now(),
last_observation_at: Mutex::new(None),
};
let node_idx = (current_numa_node() as usize) % self.nodes.len();
let node = &self.nodes[node_idx];
let mut guard = node.instances.write();
for (slot_idx, slot) in guard.iter_mut().enumerate() {
if slot.is_none() {
*slot = Some(reg);
return pack_id(node_idx as u32, slot_idx as u32);
}
}
let slot_idx = guard.len();
guard.push(Some(reg));
pack_id(node_idx as u32, slot_idx as u32)
}
pub fn unregister(&self, id: InstanceId) {
let (node_idx, slot_idx) = unpack_id(id);
let Some(node) = self.nodes.get(node_idx as usize) else { return };
let mut guard = node.instances.write();
if let Some(slot) = guard.get_mut(slot_idx as usize)
&& slot.is_some() {
*slot = None;
self.instance_count.fetch_sub(1, Ordering::AcqRel);
}
}
pub fn instance_count(&self) -> usize {
self.instance_count.load(Ordering::Acquire)
}
pub fn max_instances(&self) -> usize {
self.max_instances.load(Ordering::Acquire)
}
pub fn set_max_instances(&self, cap: usize) {
self.max_instances.store(cap, Ordering::Release);
}
pub fn stats(&self, id: InstanceId) -> Option<InstanceStats> {
let (node_idx, slot_idx) = unpack_id(id);
let node = self.nodes.get(node_idx as usize)?;
let guard = node.instances.read();
guard.get(slot_idx as usize)?.as_ref().map(|r| *r.stats.lock())
}
pub fn scan_now(&self) {
for node_idx in 0..self.nodes.len() {
self.scan_node(node_idx);
}
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
}
impl Drop for Sidecar {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::Release);
let handles: Vec<JoinHandle<()>> = std::mem::take(&mut *self.join_handles.lock());
for h in handles {
h.join().ok();
}
}
}
static GLOBAL: Lazy<Arc<Sidecar>> = Lazy::new(|| {
let s = Sidecar::new();
register_sidecar_atexit();
s
});
fn register_sidecar_atexit() {
static REGISTERED: std::sync::Once = std::sync::Once::new();
REGISTERED.call_once(|| {
unsafe {
unsafe extern "C" {
fn atexit(cb: extern "C" fn()) -> i32;
}
atexit(sidecar_atexit_shutdown);
}
});
}
extern "C" fn sidecar_atexit_shutdown() {
if let Some(sidecar) = Lazy::get(&GLOBAL) {
sidecar.shutdown.store(true, Ordering::Release);
let handles: Vec<JoinHandle<()>> = std::mem::take(
&mut *sidecar.join_handles.lock(),
);
for h in handles {
h.join().ok();
}
for node in &sidecar.nodes {
let mut g = node.instances.write();
for slot in g.iter_mut() {
*slot = None;
}
}
eprintln!("[subetha-sidecar atexit] shutdown complete");
}
}
pub fn global() -> Arc<Sidecar> {
GLOBAL.clone()
}
pub fn numa_node_count() -> u32 {
#[cfg(target_os = "windows")]
{
unsafe {
let mut highest: u32 = 0;
unsafe extern "system" {
fn GetNumaHighestNodeNumber(HighestNodeNumber: *mut u32) -> i32;
}
let result = GetNumaHighestNodeNumber(&mut highest);
if result == 0 {
1
} else {
highest.saturating_add(1)
}
}
}
#[cfg(not(target_os = "windows"))]
{
1
}
}
pub fn current_numa_node() -> u32 {
#[cfg(target_os = "windows")]
{
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct ProcessorNumber {
group: u16,
number: u8,
reserved: u8,
}
unsafe {
unsafe extern "system" {
fn GetCurrentProcessorNumberEx(ProcNumber: *mut ProcessorNumber);
fn GetNumaProcessorNodeEx(
Processor: *const ProcessorNumber,
NodeNumber: *mut u16,
) -> i32;
}
let mut proc = ProcessorNumber::default();
GetCurrentProcessorNumberEx(&mut proc);
let mut node: u16 = 0;
if GetNumaProcessorNodeEx(&proc, &mut node) != 0 {
if node == u16::MAX { 0 } else { node as u32 }
} else {
0
}
}
}
#[cfg(not(target_os = "windows"))]
{
current_numa_node_linux()
}
}
#[cfg(not(target_os = "windows"))]
fn current_numa_node_linux() -> u32 {
use std::fs;
let stat = match fs::read_to_string("/proc/self/stat") {
Ok(s) => s,
Err(_) => return 0,
};
let after_comm = match stat.rfind(')') {
Some(i) => &stat[i + 1..],
None => return 0,
};
let cpu = match after_comm.split_whitespace().nth(36) {
Some(s) => match s.parse::<u32>() { Ok(v) => v, Err(_) => return 0 },
None => return 0,
};
let path = format!(
"/sys/devices/system/cpu/cpu{cpu}/topology/physical_package_id"
);
match fs::read_to_string(&path) {
Ok(s) => s.trim().parse::<u32>().unwrap_or(0),
Err(_) => 0,
}
}
pub struct SidecarHandle {
id: InstanceId,
sidecar: Arc<Sidecar>,
}
impl SidecarHandle {
pub fn id(&self) -> InstanceId {
self.id
}
pub fn stats(&self) -> Option<InstanceStats> {
self.sidecar.stats(self.id)
}
}
impl Drop for SidecarHandle {
fn drop(&mut self) {
self.sidecar.unregister(self.id);
}
}
pub trait AdaptiveInstance: Send + Sync + 'static {
fn header(&self) -> &HandshakeHeader;
fn ring(&self) -> &ObservationRing;
fn make_policy(&self) -> Box<dyn Policy>;
fn apply_migration(&self, new_tag: u32) {
self.header().set_tag(new_tag);
}
}
pub struct SidecarBox<T: AdaptiveInstance> {
handle: SidecarHandle,
inner: Box<T>,
}
impl<T: AdaptiveInstance> SidecarBox<T> {
pub fn new(value: T) -> Self {
let inner = Box::new(value);
let header = NonNull::from(inner.header());
let ring = NonNull::from(inner.ring());
let instance_ref: &dyn AdaptiveInstance = &*inner;
let instance_ptr: *const dyn AdaptiveInstance = instance_ref;
let instance = unsafe {
NonNull::new_unchecked(instance_ptr as *mut dyn AdaptiveInstance)
};
let policy = inner.make_policy();
let sidecar = global();
let id = unsafe { sidecar.register_raw(header, ring, Some(instance), policy) };
Self {
handle: SidecarHandle { id, sidecar },
inner,
}
}
pub fn id(&self) -> InstanceId {
self.handle.id
}
pub fn stats(&self) -> Option<InstanceStats> {
self.handle.stats()
}
}
impl<T: AdaptiveInstance> std::ops::Deref for SidecarBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
use subetha_core::Observation;
struct BareInstance {
header: HandshakeHeader,
ring: ObservationRing,
}
impl BareInstance {
fn new() -> Self {
Self {
header: HandshakeHeader::new(),
ring: ObservationRing::new(),
}
}
}
impl AdaptiveInstance for BareInstance {
fn header(&self) -> &HandshakeHeader { &self.header }
fn ring(&self) -> &ObservationRing { &self.ring }
fn make_policy(&self) -> Box<dyn Policy> { Box::new(NoMigrationPolicy) }
}
struct EscalatingPolicy {
threshold_ticks: u64,
escalate_to: u32,
}
impl Policy for EscalatingPolicy {
fn decide(&self, stats: &InstanceStats, current_tag: u32) -> Option<u32> {
if stats.average_latency_ticks() > self.threshold_ticks && current_tag < self.escalate_to {
Some(self.escalate_to)
} else {
None
}
}
}
struct EscalatingInstance {
header: HandshakeHeader,
ring: ObservationRing,
}
impl EscalatingInstance {
fn new() -> Self {
Self {
header: HandshakeHeader::new(),
ring: ObservationRing::new(),
}
}
}
impl AdaptiveInstance for EscalatingInstance {
fn header(&self) -> &HandshakeHeader { &self.header }
fn ring(&self) -> &ObservationRing { &self.ring }
fn make_policy(&self) -> Box<dyn Policy> {
Box::new(EscalatingPolicy {
threshold_ticks: 500,
escalate_to: 2,
})
}
}
#[test]
fn register_unregister_balances() {
let s = global();
let inst = Box::new(BareInstance::new());
let header = NonNull::from(inst.header());
let ring = NonNull::from(inst.ring());
let id = unsafe {
s.register_raw(header, ring, None, Box::new(NoMigrationPolicy))
};
assert!(s.stats(id).is_some());
s.unregister(id);
assert!(s.stats(id).is_none());
drop(inst);
}
#[test]
fn sidecar_drains_observations() {
let inst = SidecarBox::new(BareInstance::new());
for i in 0..10 {
assert!(inst.ring.push(Observation {
instance_id: 0,
op_kind: 1,
flags: 0,
latency_ticks: 100 + i,
..Observation::ZERO
}));
}
global().scan_now();
let stats = inst.stats().expect("instance should be registered");
assert_eq!(stats.ops_observed, 10);
assert!(stats.total_latency_ticks >= 1000);
}
#[test]
fn policy_migrates_strategy_when_threshold_crossed() {
let inst = SidecarBox::new(EscalatingInstance::new());
assert_eq!(inst.header().tag(), 0);
for _ in 0..50 {
inst.ring.push(Observation {
instance_id: 0,
op_kind: 1,
flags: 0,
latency_ticks: 5000,
..Observation::ZERO
});
}
global().scan_now();
assert_eq!(inst.header().tag(), 2,
"policy should have escalated tag to 2 after high-latency observations");
}
#[test]
fn unregister_blocks_safe_drop() {
for _ in 0..50 {
let inst = SidecarBox::new(BareInstance::new());
for _ in 0..100 {
inst.ring.push(Observation {
instance_id: 0,
op_kind: 1,
flags: 0,
latency_ticks: 10,
..Observation::ZERO
});
}
drop(inst);
}
}
#[test]
fn fixed_policy_sets_tag_immediately() {
struct Inst { h: HandshakeHeader, r: ObservationRing }
impl AdaptiveInstance for Inst {
fn header(&self) -> &HandshakeHeader { &self.h }
fn ring(&self) -> &ObservationRing { &self.r }
fn make_policy(&self) -> Box<dyn Policy> { Box::new(FixedPolicy(7)) }
}
let inst = SidecarBox::new(Inst {
h: HandshakeHeader::new(),
r: ObservationRing::new(),
});
inst.r.push(Observation { instance_id: 0, op_kind: 0, flags: 0, latency_ticks: 1, ..Observation::ZERO });
global().scan_now();
assert_eq!(inst.h.tag(), 7);
}
#[test]
fn instance_count_tracks_register_and_unregister() {
let s = Sidecar::new();
let start = s.instance_count();
let inst = Box::new(BareInstance::new());
let header = NonNull::from(inst.header());
let ring = NonNull::from(inst.ring());
let id = unsafe {
s.register_raw(header, ring, None, Box::new(NoMigrationPolicy))
};
assert_eq!(s.instance_count(), start + 1);
s.unregister(id);
assert_eq!(s.instance_count(), start);
}
#[test]
fn cap_panic_message_is_actionable() {
let s = Sidecar::new();
s.set_max_instances(2);
assert_eq!(s.max_instances(), 2);
let inst1 = Box::new(BareInstance::new());
let id1 = unsafe {
s.register_raw(
NonNull::from(inst1.header()),
NonNull::from(inst1.ring()),
None,
Box::new(NoMigrationPolicy),
)
};
let inst2 = Box::new(BareInstance::new());
let id2 = unsafe {
s.register_raw(
NonNull::from(inst2.header()),
NonNull::from(inst2.ring()),
None,
Box::new(NoMigrationPolicy),
)
};
let inst3 = Box::new(BareInstance::new());
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
unsafe {
s.register_raw(
NonNull::from(inst3.header()),
NonNull::from(inst3.ring()),
None,
Box::new(NoMigrationPolicy),
)
}
}));
let payload = result.expect_err("must panic when over cap");
let msg = payload.downcast_ref::<String>().map(String::as_str)
.or_else(|| payload.downcast_ref::<&'static str>().copied())
.expect("panic payload must be a string");
assert!(msg.contains("instance cap (2) exceeded"),
"panic must name the cap value: {msg}");
assert!(msg.contains("b.iter()") || msg.contains("loop"),
"panic must hint at b.iter() / loop misuse: {msg}");
assert!(msg.contains("set_max_instances"),
"panic must mention the escape hatch: {msg}");
assert_eq!(s.instance_count(), 2,
"count must roll back on cap-rejected register");
s.unregister(id1);
s.unregister(id2);
}
}