use std::sync::atomic::Ordering::*;
use std::sync::OnceLock;
use std::time::{Duration, Instant};
use super::g::STACK_PREEMPT;
use super::p::{PIDLE, PSYSCALL, PRUNNING};
use super::sched::{sched, startm};
const MIN_SLEEP_US: u64 = 20;
const MAX_SLEEP_US: u64 = 10_000;
const IDLE_THRESH: u64 = 50;
const FORCE_PREEMPT_NS: u64 = 10_000_000;
const FORCE_RETAKE_NS: u64 = 20_000;
const LONG_RETAKE_NS: u64 = 10_000_000;
#[derive(Clone, Default)]
struct SysmonTick {
schedtick: u32,
schedwhen: u64,
syscalltick: u32,
syscallwhen: u64,
}
pub(crate) fn start_sysmon() {
std::thread::Builder::new()
.name("go-sysmon".to_string())
.spawn(sysmon_loop)
.expect("start_sysmon: failed to spawn sysmon thread");
}
fn sysmon_loop() {
super::netpoll::netpoll_init();
let mut delay_us: u64 = MIN_SLEEP_US;
let mut idle: u64 = 0;
let mut ticks: Vec<SysmonTick> = Vec::new();
loop {
if idle == 0 {
delay_us = MIN_SLEEP_US;
} else if idle > IDLE_THRESH {
delay_us = (delay_us * 2).min(MAX_SLEEP_US);
}
std::thread::sleep(Duration::from_micros(delay_us));
{
let ready = unsafe { super::netpoll::netpoll_wait(0) };
if !ready.is_empty() {
idle = 0;
for gp in ready {
unsafe { super::park::goready(gp) };
}
}
}
let now_ns = monotonic_ns();
if retake(now_ns, &mut ticks) != 0 {
idle = 0; } else {
idle += 1;
}
}
}
fn retake(now_ns: u64, ticks: &mut Vec<SysmonTick>) -> u32 {
let sc = sched();
let allp: Vec<*mut super::p::P> = {
let inner = sc.inner.lock().unwrap();
if ticks.len() < inner.allp.len() {
ticks.resize(inner.allp.len(), SysmonTick::default());
}
inner.allp.clone()
};
let mut acted: u32 = 0;
for (i, &pp) in allp.iter().enumerate() {
if pp.is_null() {
continue;
}
let tick = &mut ticks[i];
let status = unsafe { (*pp).status.load(Acquire) };
if status == PRUNNING {
let schedtick = unsafe { (*pp).schedtick.load(Acquire) };
if tick.schedtick != schedtick {
tick.schedtick = schedtick;
tick.schedwhen = now_ns;
} else if now_ns.saturating_sub(tick.schedwhen) > FORCE_PREEMPT_NS {
unsafe { preemptone(pp) };
acted += 1;
}
}
if status == PSYSCALL {
let syscalltick = unsafe { (*pp).syscalltick.load(Acquire) };
if tick.syscalltick != syscalltick {
tick.syscalltick = syscalltick;
tick.syscallwhen = now_ns;
continue;
}
let elapsed = now_ns.saturating_sub(tick.syscallwhen);
if elapsed < FORCE_RETAKE_NS {
continue;
}
let run_q_empty = unsafe { (*pp).runq_size() == 0 };
if run_q_empty && elapsed < LONG_RETAKE_NS {
let spinning = sc.nmspinning.load(Relaxed);
let nmidle = sc.inner.lock().unwrap().nmidle;
if spinning + nmidle > 0 {
continue;
}
}
if unsafe {
(*pp).status
.compare_exchange(PSYSCALL, PIDLE, AcqRel, Relaxed)
.is_ok()
} {
unsafe { (*pp).syscalltick.fetch_add(1, Relaxed) };
acted += 1;
unsafe { startm(pp) };
}
}
}
acted
}
unsafe fn preemptone(pp: *mut super::p::P) {
let mp = unsafe { (*pp).m };
if mp.is_null() {
return;
}
let gp = unsafe { (*mp).curg };
if gp.is_null() {
return;
}
let pthread_id = unsafe { (*mp).pthread_id };
if pthread_id == 0 {
return;
}
unsafe {
(*gp).preempt = true;
(*gp).stackguard0 = STACK_PREEMPT;
}
#[cfg(not(windows))]
unsafe { libc::pthread_kill(pthread_id as libc::pthread_t, libc::SIGURG) };
}
fn monotonic_ns() -> u64 {
static ORIGIN: OnceLock<Instant> = OnceLock::new();
let origin = ORIGIN.get_or_init(Instant::now);
origin.elapsed().as_nanos() as u64
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
#[test]
fn monotonic_ns_is_monotonic() {
let t1 = monotonic_ns();
std::thread::sleep(Duration::from_millis(2));
let t2 = monotonic_ns();
assert!(t2 > t1, "monotonic_ns must be strictly increasing");
}
#[test]
fn sysmon_tick_default_is_zero() {
let t = SysmonTick::default();
assert_eq!(t.schedtick, 0);
assert_eq!(t.schedwhen, 0);
assert_eq!(t.syscalltick, 0);
assert_eq!(t.syscallwhen, 0);
}
#[test]
fn retake_finds_nothing_on_first_pass() {
let mut ticks = Vec::new();
let now = monotonic_ns();
let _ = retake(now, &mut ticks);
let n = retake(monotonic_ns(), &mut ticks);
assert_eq!(n, 0, "retake must return 0 when all Ps just started their tick");
}
#[cfg(not(windows))]
#[test]
fn preemptone_sets_flags() {
use crate::runtime::g::{Stack, G, STACK_PREEMPT};
use crate::runtime::m::M;
use crate::runtime::p::P;
use std::sync::atomic::Ordering::Release;
use std::ptr::addr_of_mut;
let mut g = G::new(Stack { lo: 0x100000, hi: 0x110000 }, 42);
let gp = addr_of_mut!(*g);
let p = Box::into_raw(P::new(7));
let m = Box::into_raw(unsafe { M::new(99) });
unsafe {
(*p).status.store(PRUNNING, Release);
(*p).m = m;
(*m).p = p;
(*m).curg = gp;
(*gp).m = m;
(*m).pthread_id = libc::pthread_self() as u64;
}
assert!(!unsafe { (*gp).preempt }, "preempt must be false before preemptone");
assert_ne!(
unsafe { (*gp).stackguard0 }, STACK_PREEMPT,
"stackguard0 must not be STACK_PREEMPT before preemptone"
);
unsafe { preemptone(p) };
assert!(unsafe { (*gp).preempt }, "preempt must be true after preemptone");
assert_eq!(
unsafe { (*gp).stackguard0 }, STACK_PREEMPT,
"stackguard0 must equal STACK_PREEMPT after preemptone"
);
let _ = unsafe { Box::from_raw(p) };
let _ = unsafe { Box::from_raw(m) };
}
#[test]
fn retake_reclaims_psyscall_p() {
use crate::runtime::p::{P, PIDLE, PSYSCALL};
use std::sync::atomic::Ordering::Release;
let p = Box::into_raw(P::new(99));
unsafe { (*p).status.store(PSYSCALL, Release) };
let status_before = unsafe { (*p).status.load(Acquire) };
assert_eq!(status_before, PSYSCALL, "precondition: P must start as PSYSCALL");
let retaken = unsafe {
(*p).status
.compare_exchange(PSYSCALL, PIDLE, AcqRel, Relaxed)
.is_ok()
};
assert!(retaken, "manual CAS PSYSCALL→PIDLE must succeed");
assert_eq!(
unsafe { (*p).status.load(Relaxed) },
PIDLE,
"P must be PIDLE after retake CAS"
);
let _ = unsafe { Box::from_raw(p) };
}
}