use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::future::{poll_fn, Future};
use std::io;
use std::marker::PhantomData;
use std::mem;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::panic;
use std::pin::{pin, Pin};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
use std::sync::{mpsc, Arc, Condvar, Mutex, MutexGuard};
use std::task::{Context, Poll, Waker};
use std::thread;
use std::time::{Duration, Instant};
use io_uring::{opcode, types};
use crate::buf::BufPool;
use crate::driver::{Driver, Park};
use crate::executor::{TaskSlot, TaskWaker, MAIN_TASK};
use crate::op::Op;
use crate::slab::Slab;
use crate::time::Timers;
fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
static NEXT_UID: AtomicUsize = AtomicUsize::new(1);
pub(crate) enum Inject {
Wake(usize),
Run(Box<dyn FnOnce() + Send>),
Shutdown,
}
pub(crate) struct WorkerShared {
pub(crate) uid: usize,
injector: Mutex<Vec<Inject>>,
pending: AtomicBool,
sleeping: AtomicBool,
closed: AtomicBool,
eventfd: OwnedFd,
}
impl WorkerShared {
fn new() -> io::Result<Arc<Self>> {
let fd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(Arc::new(Self {
uid: NEXT_UID.fetch_add(1, Ordering::Relaxed),
injector: Mutex::new(Vec::new()),
pending: AtomicBool::new(false),
sleeping: AtomicBool::new(false),
closed: AtomicBool::new(false),
eventfd: unsafe { OwnedFd::from_raw_fd(fd) },
}))
}
pub(crate) fn inject(&self, item: Inject) -> bool {
{
let mut queue = lock(&self.injector);
if self.closed.load(Ordering::Acquire) {
return false;
}
queue.push(item);
}
self.pending.store(true, Ordering::SeqCst);
if self.sleeping.swap(false, Ordering::SeqCst) {
let one: u64 = 1;
unsafe {
libc::write(
self.eventfd.as_raw_fd(),
&one as *const u64 as *const libc::c_void,
8,
);
}
}
true
}
fn close(&self) {
let dropped = {
let mut queue = lock(&self.injector);
self.closed.store(true, Ordering::Release);
mem::take(&mut *queue)
};
drop(dropped);
}
}
pub(crate) struct RuntimeShared {
workers: Vec<Arc<WorkerShared>>,
shutdown: AtomicBool,
}
impl RuntimeShared {
fn new(workers: usize) -> io::Result<Arc<Self>> {
let workers = (0..workers)
.map(|_| WorkerShared::new())
.collect::<io::Result<Vec<_>>>()?;
Ok(Arc::new(Self {
workers,
shutdown: AtomicBool::new(false),
}))
}
fn shutdown(&self) {
if self.shutdown.swap(true, Ordering::SeqCst) {
return;
}
for worker in &self.workers {
worker.inject(Inject::Shutdown);
}
}
}
#[derive(Clone)]
struct Config {
spin: Option<Duration>,
ring_entries: u32,
buf_count: u16,
buf_size: usize,
shutdown_grace: Duration,
}
pub(crate) struct Worker {
pub(crate) index: usize,
pub(crate) shared: Arc<WorkerShared>,
pub(crate) rt: Arc<RuntimeShared>,
pub(crate) driver: RefCell<Driver>,
pub(crate) tasks: RefCell<Slab<TaskSlot>>,
pub(crate) queue: RefCell<VecDeque<usize>>,
pub(crate) timers: RefCell<Timers>,
pub(crate) pool: Rc<BufPool>,
pub(crate) next_task_uid: Cell<u64>,
spin: Option<Duration>,
shutdown_waiters: RefCell<Slab<Waker>>,
shutdown_grace: Duration,
draining: Cell<bool>,
}
thread_local! {
static WORKER: RefCell<Option<Rc<Worker>>> = const { RefCell::new(None) };
static CURRENT_UID: Cell<usize> = const { Cell::new(0) };
}
pub(crate) fn current_uid() -> usize {
CURRENT_UID.try_with(|c| c.get()).unwrap_or(0)
}
pub(crate) fn try_with_worker<R>(f: impl FnOnce(&Worker) -> R) -> Option<R> {
WORKER
.try_with(|w| w.borrow().as_deref().map(f))
.ok()
.flatten()
}
pub(crate) fn with_worker<R>(f: impl FnOnce(&Worker) -> R) -> R {
try_with_worker(f).expect("giong: this must be called from inside a giong runtime")
}
impl Worker {
fn new(index: usize, rt: Arc<RuntimeShared>, cfg: &Config) -> io::Result<Rc<Self>> {
let shared = rt.workers[index].clone();
let pool = BufPool::new(cfg.buf_count, cfg.buf_size)?;
let driver = Driver::new(cfg.ring_entries, pool.clone(), shared.eventfd.as_raw_fd())?;
Ok(Rc::new(Self {
index,
shared,
rt,
driver: RefCell::new(driver),
tasks: RefCell::new(Slab::new()),
queue: RefCell::new(VecDeque::with_capacity(256)),
timers: RefCell::new(Timers::new()),
pool,
next_task_uid: Cell::new(1),
spin: cfg.spin,
shutdown_waiters: RefCell::new(Slab::new()),
shutdown_grace: cfg.shutdown_grace,
draining: Cell::new(false),
}))
}
fn run<F: Future>(self: Rc<Self>, fut: F) -> F::Output {
WORKER.with(|w| {
let mut slot = w.borrow_mut();
assert!(
slot.is_none(),
"giong: a runtime is already running on this thread"
);
*slot = Some(self.clone());
});
CURRENT_UID.with(|c| c.set(self.shared.uid));
struct Teardown(Rc<Worker>);
impl Drop for Teardown {
fn drop(&mut self) {
self.0.teardown();
}
}
let _teardown = Teardown(self.clone());
let mut fut = pin!(fut);
let main = TaskWaker::main(self.shared.clone());
let waker = Waker::from(main.clone());
let mut output = None;
self.queue.borrow_mut().push_back(MAIN_TASK);
self.drive(
&mut || {
if output.is_none() {
main.clear_queued();
let mut cx = Context::from_waker(&waker);
if let Poll::Ready(value) = fut.as_mut().poll(&mut cx) {
output = Some(value);
}
}
output.is_some()
},
&|| false,
);
self.drain(&main, &waker);
output.expect("giong: event loop exited before the main future completed")
}
fn drain(&self, main: &TaskWaker, waker: &Waker) {
if self.shutdown_grace.is_zero() || self.tasks.borrow().len() == 0 {
return;
}
self.draining.set(true);
self.wake_shutdown_waiters();
let mut deadline = pin!(crate::time::sleep(self.shutdown_grace));
let mut expired = false;
self.queue.borrow_mut().push_back(MAIN_TASK);
self.drive(
&mut || {
if !expired {
main.clear_queued();
let mut cx = Context::from_waker(waker);
expired = deadline.as_mut().poll(&mut cx).is_ready();
}
expired
},
&|| self.tasks.borrow().len() == 0,
);
}
fn wake_shutdown_waiters(&self) {
let waiters = self.shutdown_waiters.borrow_mut().take_all();
for waker in waiters {
waker.wake();
}
}
fn drive(&self, poll_main: &mut dyn FnMut() -> bool, idle: &dyn Fn() -> bool) {
let mut done = false;
loop {
self.drain_injected();
self.run_tasks(&mut || done = poll_main());
if done || idle() {
break;
}
if !self.queue.borrow().is_empty() {
let graveyard = self.driver.borrow_mut().park(Park::Poll);
drop(graveyard);
self.fire_timers();
continue;
}
if let Some(spin) = self.spin {
let graveyard = self.driver.borrow_mut().park(Park::Poll);
drop(graveyard);
self.fire_timers();
if !self.queue.borrow().is_empty() || self.spin_for_work(spin) {
continue;
}
}
let wait = match self.timers.borrow().next_deadline() {
None => Park::Wait(None),
Some(deadline) => {
let now = Instant::now();
if deadline <= now {
Park::Poll
} else {
Park::Wait(Some(deadline - now))
}
}
};
self.shared.sleeping.store(true, Ordering::SeqCst);
let mode = if self.shared.pending.load(Ordering::SeqCst) {
Park::Poll
} else {
wait
};
let graveyard = self.driver.borrow_mut().park(mode);
self.shared.sleeping.store(false, Ordering::SeqCst);
drop(graveyard);
self.fire_timers();
}
}
fn spin_for_work(&self, spin: Duration) -> bool {
let start = Instant::now();
let deadline = match self.timers.borrow().next_deadline() {
Some(timer) => timer.min(start + spin),
None => start + spin,
};
loop {
for _ in 0..64 {
if self.shared.pending.load(Ordering::Relaxed)
|| self.driver.borrow_mut().has_completions()
{
return true;
}
std::hint::spin_loop();
}
if Instant::now() >= deadline {
return self
.timers
.borrow()
.next_deadline()
.is_some_and(|timer| timer <= Instant::now());
}
}
}
fn drain_injected(&self) {
if !self.shared.pending.swap(false, Ordering::SeqCst) {
return;
}
let items = mem::take(&mut *lock(&self.shared.injector));
for item in items {
match item {
Inject::Wake(id) => self.queue.borrow_mut().push_back(id),
Inject::Run(f) => drop(panic::catch_unwind(panic::AssertUnwindSafe(f))),
Inject::Shutdown => self.wake_shutdown_waiters(),
}
}
}
fn fire_timers(&self) {
if self.timers.borrow().len() == 0 {
return;
}
let expired = self.timers.borrow_mut().expired(Instant::now());
for waker in expired {
waker.wake();
}
}
fn teardown(self: &Rc<Self>) {
self.shared.close();
self.drop_all_tasks();
let timers = self.timers.borrow_mut().clear();
drop(timers);
let waiters = self.shutdown_waiters.borrow_mut().take_all();
drop(waiters);
let (quiesced, graveyard) = {
let mut driver = self.driver.borrow_mut();
let quiesced = driver.shutdown();
(quiesced, driver.take_graveyard())
};
if quiesced {
drop(graveyard);
} else {
mem::forget(graveyard);
mem::forget(self.clone());
}
let _ = WORKER.try_with(|w| w.borrow_mut().take());
let _ = CURRENT_UID.try_with(|c| c.set(0));
}
}
#[derive(Clone, Debug)]
pub struct Builder {
workers: Option<usize>,
pin_cores: bool,
ring_entries: u32,
buf_count: u16,
buf_size: usize,
signals: bool,
spin: Option<Duration>,
shutdown_grace: Duration,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
pub fn new() -> Self {
Self {
workers: None,
pin_cores: true,
ring_entries: 1024,
buf_count: 512,
buf_size: 8 * 1024,
signals: false,
spin: None,
shutdown_grace: Duration::ZERO,
}
}
pub fn workers(mut self, n: usize) -> Self {
self.workers = Some(n.max(1));
self
}
pub fn pin_cores(mut self, pin: bool) -> Self {
self.pin_cores = pin;
self
}
pub fn ring_entries(mut self, entries: u32) -> Self {
self.ring_entries = entries.clamp(8, 32768).next_power_of_two();
self
}
pub fn recv_buffers(mut self, count: usize, size: usize) -> Self {
self.buf_count = count.clamp(1, 32768).next_power_of_two() as u16;
self.buf_size = size.max(64);
self
}
pub fn spin_before_park(mut self, duration: Duration) -> Self {
self.spin = (!duration.is_zero()).then_some(duration);
self
}
pub fn shutdown_on_signals(mut self, enable: bool) -> Self {
self.signals = enable;
self
}
pub fn shutdown_grace(mut self, grace: Duration) -> Self {
self.shutdown_grace = grace;
self
}
fn config(&self) -> Config {
Config {
spin: self.spin,
ring_entries: self.ring_entries,
buf_count: self.buf_count,
buf_size: self.buf_size,
shutdown_grace: self.shutdown_grace,
}
}
pub fn build(self) -> io::Result<Runtime> {
let workers = self.workers.unwrap_or_else(|| allowed_cpus().len().max(1));
let shared = RuntimeShared::new(workers)?;
Ok(Runtime {
builder: self,
shared,
})
}
pub fn block_on<F: Future>(self, fut: F) -> io::Result<F::Output> {
let rt = RuntimeShared::new(1)?;
let worker = Worker::new(0, rt, &self.config())?;
Ok(worker.run(fut))
}
}
pub fn block_on<F: Future>(fut: F) -> F::Output {
Builder::new()
.block_on(fut)
.expect("giong: failed to start the runtime")
}
pub struct Runtime {
builder: Builder,
shared: Arc<RuntimeShared>,
}
struct Gate {
state: Mutex<Option<bool>>,
cond: Condvar,
}
impl Gate {
fn open(&self, go: bool) {
*lock(&self.state) = Some(go);
self.cond.notify_all();
}
fn wait(&self) -> bool {
let mut state = lock(&self.state);
loop {
if let Some(go) = *state {
return go;
}
state = self.cond.wait(state).unwrap_or_else(|e| e.into_inner());
}
}
}
impl Runtime {
pub fn handle(&self) -> Handle {
Handle {
rt: self.shared.clone(),
}
}
pub fn run<F, Fut>(self, f: F) -> io::Result<()>
where
F: Fn(usize) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + 'static,
{
let n = self.shared.workers.len();
let cfg = self.builder.config();
let cpus = allowed_cpus();
let f = Arc::new(f);
let gate = Arc::new(Gate {
state: Mutex::new(None),
cond: Condvar::new(),
});
let signals = if self.builder.signals {
Some(SignalGuard::install()?)
} else {
None
};
let signal_fd = signals.as_ref().map(|s| s.fd);
let (tx, rx) = mpsc::channel::<io::Result<()>>();
let mut threads = Vec::with_capacity(n);
let mut spawn_err = None;
for index in 0..n {
let (f, gate, tx, cfg) = (f.clone(), gate.clone(), tx.clone(), cfg.clone());
let rt = self.shared.clone();
let cpu =
(self.builder.pin_cores && !cpus.is_empty()).then(|| cpus[index % cpus.len()]);
let signal_fd = if index == 0 { signal_fd } else { None };
let spawned = thread::Builder::new()
.name(format!("giong-{index}"))
.spawn(move || {
if let Some(cpu) = cpu {
pin_to_cpu(cpu);
}
let worker = match Worker::new(index, rt.clone(), &cfg) {
Ok(worker) => {
let _ = tx.send(Ok(()));
worker
}
Err(e) => {
let _ = tx.send(Err(e));
return;
}
};
drop(tx);
if !gate.wait() {
return;
}
struct StopOnPanic(Arc<RuntimeShared>);
impl Drop for StopOnPanic {
fn drop(&mut self) {
if thread::panicking() {
self.0.shutdown();
}
}
}
let _stop = StopOnPanic(rt);
worker.run(async move {
if let Some(fd) = signal_fd {
crate::spawn(watch_signals(fd));
}
f(index).await
});
});
match spawned {
Ok(t) => threads.push(t),
Err(e) => {
spawn_err = Some(e);
break;
}
}
}
drop(tx);
let mut first_err = spawn_err;
for _ in 0..threads.len() {
match rx.recv() {
Ok(Ok(())) => {}
Ok(Err(e)) => {
first_err.get_or_insert(e);
}
Err(_) => break,
}
}
gate.open(first_err.is_none());
let mut panic_payload = None;
for t in threads {
if let Err(payload) = t.join() {
panic_payload.get_or_insert(payload);
}
}
drop(signals);
if let Some(e) = first_err {
return Err(e);
}
if let Some(payload) = panic_payload {
panic::resume_unwind(payload);
}
Ok(())
}
}
static SIGNAL_FD: AtomicI32 = AtomicI32::new(-1);
static SIGNALS_IN_USE: AtomicBool = AtomicBool::new(false);
extern "C" fn on_signal(_signal: libc::c_int) {
let fd = SIGNAL_FD.load(Ordering::Relaxed);
if fd >= 0 {
let one: u64 = 1;
unsafe {
let errno = libc::__errno_location();
let saved = *errno;
libc::write(fd, &one as *const u64 as *const libc::c_void, 8);
*errno = saved;
}
}
}
struct SignalGuard {
fd: RawFd,
old_int: libc::sigaction,
old_term: libc::sigaction,
}
impl SignalGuard {
fn install() -> io::Result<Self> {
if SIGNALS_IN_USE.swap(true, Ordering::SeqCst) {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"another giong runtime in this process already handles signals",
));
}
unsafe {
let mut fd = SIGNAL_FD.load(Ordering::SeqCst);
if fd < 0 {
fd = libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK);
if fd < 0 {
SIGNALS_IN_USE.store(false, Ordering::SeqCst);
return Err(io::Error::last_os_error());
}
SIGNAL_FD.store(fd, Ordering::SeqCst);
}
let mut stale: u64 = 0;
libc::read(fd, &mut stale as *mut u64 as *mut libc::c_void, 8);
let mut action: libc::sigaction = mem::zeroed();
action.sa_sigaction = on_signal as extern "C" fn(libc::c_int) as usize;
action.sa_flags = libc::SA_RESTART;
libc::sigemptyset(&mut action.sa_mask);
let mut old_int: libc::sigaction = mem::zeroed();
let mut old_term: libc::sigaction = mem::zeroed();
if libc::sigaction(libc::SIGINT, &action, &mut old_int) != 0 {
let err = io::Error::last_os_error();
SIGNALS_IN_USE.store(false, Ordering::SeqCst);
return Err(err);
}
if libc::sigaction(libc::SIGTERM, &action, &mut old_term) != 0 {
let err = io::Error::last_os_error();
libc::sigaction(libc::SIGINT, &old_int, std::ptr::null_mut());
SIGNALS_IN_USE.store(false, Ordering::SeqCst);
return Err(err);
}
Ok(Self {
fd,
old_int,
old_term,
})
}
}
}
impl Drop for SignalGuard {
fn drop(&mut self) {
unsafe {
libc::sigaction(libc::SIGINT, &self.old_int, std::ptr::null_mut());
libc::sigaction(libc::SIGTERM, &self.old_term, std::ptr::null_mut());
}
SIGNALS_IN_USE.store(false, Ordering::SeqCst);
}
}
async fn watch_signals(fd: RawFd) {
until_shutdown(async {
let mut buf: Vec<u8> = Vec::with_capacity(8);
loop {
let sqe = opcode::Read::new(types::Fd(fd), buf.as_mut_ptr(), 8).build();
let (res, _, b) = Op::new(sqe, buf).await;
buf = b;
if res > 0 {
handle().shutdown();
return;
}
if res != -libc::EINTR && res != -libc::EAGAIN {
return;
}
}
})
.await;
}
#[derive(Clone)]
pub struct Handle {
rt: Arc<RuntimeShared>,
}
impl Handle {
pub fn workers(&self) -> usize {
self.rt.workers.len()
}
pub fn shutdown(&self) {
self.rt.shutdown();
}
pub fn is_shutdown(&self) -> bool {
self.rt.shutdown.load(Ordering::SeqCst)
}
pub fn spawn_on<F, Fut>(&self, worker: usize, f: F) -> bool
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = ()> + 'static,
{
match self.rt.workers.get(worker) {
Some(shared) => shared.inject(Inject::Run(Box::new(move || {
crate::spawn(f());
}))),
None => false,
}
}
}
impl std::fmt::Debug for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Handle")
.field("workers", &self.workers())
.finish()
}
}
pub fn handle() -> Handle {
with_worker(|w| Handle { rt: w.rt.clone() })
}
pub fn worker_id() -> usize {
with_worker(|w| w.index)
}
pub struct ShutdownSignal {
rt: Arc<RuntimeShared>,
key: Option<(usize, usize)>,
_not_send: PhantomData<*const ()>,
}
impl ShutdownSignal {
fn unregister(&mut self) {
if let Some((key, uid)) = self.key.take() {
if self.rt.shutdown.load(Ordering::SeqCst) {
return;
}
let waker = try_with_worker(|w| {
if w.shared.uid == uid && !w.draining.get() {
w.shutdown_waiters
.try_borrow_mut()
.ok()
.and_then(|mut waiters| waiters.remove(key))
} else {
None
}
});
drop(waker);
}
}
}
impl Future for ShutdownSignal {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.rt.shutdown.load(Ordering::SeqCst) || with_worker(|w| w.draining.get()) {
self.key = None;
return Poll::Ready(());
}
let registered = self.key;
let key = with_worker(|w| {
let uid = w.shared.uid;
let mut waiters = w.shutdown_waiters.borrow_mut();
if let Some((key, owner)) = registered {
if owner == uid {
if let Some(waker) = waiters.get_mut(key) {
if !waker.will_wake(cx.waker()) {
*waker = cx.waker().clone();
}
return (key, uid);
}
}
}
(waiters.insert(cx.waker().clone()), uid)
});
self.key = Some(key);
Poll::Pending
}
}
impl Drop for ShutdownSignal {
fn drop(&mut self) {
self.unregister();
}
}
pub fn shutdown_signal() -> ShutdownSignal {
ShutdownSignal {
rt: with_worker(|w| w.rt.clone()),
key: None,
_not_send: PhantomData,
}
}
pub fn is_shutting_down() -> bool {
with_worker(|w| w.rt.shutdown.load(Ordering::Relaxed) || w.draining.get())
}
pub async fn until_shutdown<F: Future>(fut: F) -> Option<F::Output> {
let mut fut = pin!(fut);
let mut signal = pin!(shutdown_signal());
poll_fn(|cx| {
if signal.as_mut().poll(cx).is_ready() {
return Poll::Ready(None);
}
fut.as_mut().poll(cx).map(Some)
})
.await
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Stats {
pub tasks: usize,
pub tracked_ops: usize,
pub inflight: usize,
pub free_recv_buffers: usize,
pub recv_buffers: usize,
pub timers: usize,
}
#[doc(hidden)]
pub fn stats() -> Stats {
with_worker(|w| {
let driver = w.driver.borrow();
Stats {
tasks: w.tasks.borrow().len(),
tracked_ops: driver.tracked_ops(),
inflight: driver.inflight(),
free_recv_buffers: w.pool.free(),
recv_buffers: w.pool.count() as usize,
timers: w.timers.borrow().len(),
}
})
}
fn allowed_cpus() -> Vec<usize> {
unsafe {
let mut set: libc::cpu_set_t = mem::zeroed();
if libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) != 0 {
let n = thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
return (0..n).collect();
}
(0..libc::CPU_SETSIZE as usize)
.filter(|&cpu| libc::CPU_ISSET(cpu, &set))
.collect()
}
}
fn pin_to_cpu(cpu: usize) {
unsafe {
let mut set: libc::cpu_set_t = mem::zeroed();
libc::CPU_SET(cpu, &mut set);
libc::sched_setaffinity(0, mem::size_of::<libc::cpu_set_t>(), &set);
}
}