use std::ptr;
use super::g::{casgstatus, current_g, readgstatus, set_current_g, G, GPREEMPTED, GRUNNABLE, GRUNNING, GWAITING, WaitReason};
use super::m::current_m;
use super::sched::{sched, schedule, startm};
#[cfg(target_arch = "x86_64")]
use super::asm_amd64::mcall;
#[cfg(target_arch = "aarch64")]
use super::asm_arm64::mcall;
pub(crate) fn gopark(reason: WaitReason) {
#[cfg(not(windows))]
unsafe { super::m::block_sigurg() };
let gp = current_g();
debug_assert!(!gp.is_null(), "gopark: called from g0 or bare OS thread");
unsafe { (*gp).waitreason = reason };
unsafe { mcall(gp, park_fn) };
}
pub(crate) unsafe fn gopark_commit(
reason: WaitReason,
unlock_fn: unsafe fn(*mut u8),
unlock_arg: *mut u8,
) {
let gp = current_g();
debug_assert!(!gp.is_null(), "gopark_commit: called from g0 or bare OS thread");
unsafe {
(*gp).waitreason = reason;
(*gp).park_unlock_fn = Some(unlock_fn);
(*gp).park_unlock_arg = unlock_arg;
mcall(gp, park_fn);
}
}
unsafe extern "C" fn park_fn(gp: *mut G) {
#[cfg(not(windows))]
unsafe { super::m::unblock_sigurg() };
let m = current_m();
let unlock_fn = unsafe { (*gp).park_unlock_fn.take() };
let unlock_arg = unsafe {
let a = (*gp).park_unlock_arg;
(*gp).park_unlock_arg = ptr::null_mut();
a
};
if unlock_fn.is_some() {
unsafe { (*m).locks.fetch_sub(1, std::sync::atomic::Ordering::Relaxed) };
}
unsafe {
casgstatus(gp, GRUNNING, GWAITING);
(*gp).m = ptr::null_mut();
(*m).curg = ptr::null_mut();
set_current_g(ptr::null_mut());
if let Some(f) = unlock_fn {
f(unlock_arg);
}
}
unsafe { schedule() };
}
pub(crate) unsafe fn goready(gp: *mut G) {
loop {
let s = unsafe { readgstatus(gp) };
if s == GWAITING {
let won = unsafe {
(*gp).atomicstatus
.compare_exchange(s, GRUNNABLE, std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Relaxed)
.is_ok()
};
if won {
break;
}
continue; }
if s == GRUNNABLE {
return;
}
use super::g::GDEAD;
if s == GDEAD {
return;
}
debug_assert!(
s == GRUNNING || s == GPREEMPTED,
"goready: unexpected status {s} — expected GRUNNING, GPREEMPTED (transient), \
GWAITING, GRUNNABLE, or GDEAD"
);
std::hint::spin_loop();
}
let sc = sched();
let m = current_m();
let _lk = super::m::m_lock();
if !m.is_null() {
let p = unsafe { (*m).p };
if !p.is_null() {
unsafe { (*p).runqput(gp, true, &sc.global_run_q) };
unsafe { startm(ptr::null_mut()) };
return;
}
}
unsafe {
(*gp).schedlink = ptr::null_mut();
sc.global_run_q.push_batch(gp, gp, 1);
startm(ptr::null_mut());
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use crate::runtime::g::{Stack, G};
use crate::runtime::stack::GOROUTINE_STACK_BYTES;
#[allow(dead_code)] fn make_g(id: u64) -> Box<G> {
let lo = (id as usize + 1) << 20;
G::new(Stack { lo, hi: lo + GOROUTINE_STACK_BYTES }, id)
}
#[test]
#[go_lib::main]
fn goready_pushes_to_global_queue() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let ran = Arc::new(AtomicBool::new(false));
let ran2 = Arc::clone(&ran);
let ran3 = Arc::clone(&ran);
crate::runtime::sched::spawn_goroutine(move || {
ran2.store(true, Ordering::Release);
});
let deadline =
std::time::Instant::now() + std::time::Duration::from_secs(5);
while !ran3.load(Ordering::Acquire)
&& std::time::Instant::now() < deadline
{
crate::gosched();
}
assert!(ran.load(Ordering::Acquire), "goroutine should have run via goready path");
}
}