1use core::future::Future;
23use std::fmt;
24use std::io;
25use std::sync::{Arc, OnceLock};
26use std::time::Duration;
27
28use kovan_channel::flavors::RecvDeadline;
29
30use super::blocking::Pool;
31use super::context;
32use super::sched::Sched;
33use super::shard::{Shard, Shared};
34use super::task::{self, ScheduleInfo, Task};
35use super::workers::Workers;
36
37mod builder;
38mod cores;
39mod enter;
40mod fallible;
41pub(crate) mod io_knobs;
42mod knobs;
43mod panic;
44mod placement;
45mod stats;
46pub use self::builder::Builder;
47pub use self::enter::{EnterGuard, RuntimeId, TryCurrentError, worker_index};
48pub(crate) use self::fallible::{try_current, try_spawn, try_spawn_blocking, try_spawn_local};
49pub use super::rng::RngSeed;
50pub use super::tuning::{TaskMeta, UnhandledPanic};
51
52pub(crate) struct Inner {
54 id: u64,
56 pub(crate) workers: Workers,
57 pub(crate) pool: Arc<Pool>,
58 local_margin: usize,
61 #[cfg(ring)]
63 rings: bool,
64 pub(crate) clock: crate::time::clock::Clock,
66 guests: OnceLock<kovan_map::HashMap<usize, Arc<Shared>>>,
70 task_hooks: Option<Arc<super::tuning::TaskHooks>>,
72 shutdown_on_panic: bool,
74 panicked: std::sync::atomic::AtomicBool,
75 on_panic: crate::sync::Notify,
77 rng_seed: Option<RngSeed>,
79 lingering: super::linger::Lingering,
81 pub(crate) io: io_knobs::IoKnobs,
83}
84
85pub struct Runtime {
91 inner: Arc<Inner>,
92 exits: kovan_channel::unbounded::Receiver<()>,
94}
95
96#[derive(Clone)]
99pub struct RuntimeHandle {
100 inner: Arc<Inner>,
101}
102
103impl Runtime {
104 pub fn builder() -> Builder {
106 Builder::new()
107 }
108
109 pub fn new() -> io::Result<Self> {
115 Builder::new().build()
116 }
117
118 pub fn handle(&self) -> RuntimeHandle {
120 RuntimeHandle {
121 inner: Arc::clone(&self.inner),
122 }
123 }
124
125 pub fn block_on<F: Future>(&self, future: F) -> F::Output {
127 block_on_with(&self.inner, future)
128 }
129
130 pub fn spawn<F>(&self, future: F) -> Task<F::Output>
132 where
133 F: Future + Send + 'static,
134 F::Output: Send + 'static,
135 {
136 spawn_with(&self.inner, future)
137 }
138
139 pub fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
141 where
142 F: FnOnce() -> T + Send + 'static,
143 T: Send + 'static,
144 {
145 self.inner.pool.spawn(f)
146 }
147
148 pub fn shutdown_timeout(mut self, timeout: Duration) {
153 let deadline = std::time::Instant::now() + timeout;
154 self.stop_and_join(Some(timeout));
155 self.finish_lingering(Some(deadline));
156 }
157
158 fn finish_lingering(&self, deadline: Option<std::time::Instant>) {
167 if self.inner.lingering.is_over() {
169 return;
170 }
171 if !self.inner.lingers_done() {
172 let linger = std::time::Instant::now() + self.inner.io.linger;
173 let deadline = deadline.map_or(linger, |d| d.min(linger));
174 let _ = block_on_here(
175 &self.inner,
176 crate::time::timeout_at(deadline, self.inner.run_lingers()),
177 );
178 }
179 self.inner.lingering.end();
180 }
181
182 fn stop_and_join(&mut self, timeout: Option<Duration>) {
183 self.inner.lingering.close();
185 self.inner.workers.stop_all();
186 let threads = self.inner.workers.take_threads();
187 if let Some(timeout) = timeout {
188 let deadline = std::time::Instant::now() + timeout;
189 for thread in &threads {
191 if thread.is_finished() {
192 continue;
193 }
194 if !matches!(self.exits.recv_deadline(deadline), RecvDeadline::Msg(())) {
195 break;
196 }
197 }
198 for thread in threads {
200 if thread.is_finished() {
201 let _ = thread.join();
202 }
203 }
204 } else {
205 for thread in threads {
206 let _ = thread.join();
207 }
208 }
209 }
210}
211
212#[cfg(test)]
213impl Runtime {
214 pub(crate) fn inner(&self) -> &Arc<Inner> {
215 &self.inner
216 }
217}
218
219impl Drop for Runtime {
220 fn drop(&mut self) {
221 self.stop_and_join(None);
222 self.finish_lingering(None);
223 }
224}
225
226impl fmt::Debug for Runtime {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.debug_struct("Runtime")
229 .field("worker_threads", &self.inner.workers.running())
230 .finish_non_exhaustive()
231 }
232}
233
234impl RuntimeHandle {
235 pub fn block_on<F: Future>(&self, future: F) -> F::Output {
237 block_on_with(&self.inner, future)
238 }
239
240 pub fn spawn<F>(&self, future: F) -> Task<F::Output>
242 where
243 F: Future + Send + 'static,
244 F::Output: Send + 'static,
245 {
246 spawn_with(&self.inner, future)
247 }
248
249 pub fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
251 where
252 F: FnOnce() -> T + Send + 'static,
253 T: Send + 'static,
254 {
255 self.inner.pool.spawn(f)
256 }
257
258 pub fn current() -> Self {
261 Self { inner: current() }
262 }
263
264 pub(crate) fn inner(&self) -> &Arc<Inner> {
265 &self.inner
266 }
267}
268
269impl fmt::Debug for RuntimeHandle {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 f.debug_struct("RuntimeHandle")
272 .field("worker_threads", &self.inner.workers.running())
273 .finish_non_exhaustive()
274 }
275}
276
277static DEFAULT: OnceLock<Runtime> = OnceLock::new();
279
280impl Inner {
281 pub(crate) fn guests(&self) -> &kovan_map::HashMap<usize, Arc<Shared>> {
283 self.guests
284 .get_or_init(|| kovan_map::HashMap::with_capacity(builder::GUESTS_CAPACITY))
285 }
286
287 pub(crate) fn guests_made(&self) -> Option<&kovan_map::HashMap<usize, Arc<Shared>>> {
289 self.guests.get()
290 }
291 pub(crate) fn id(&self) -> u64 {
292 self.id
293 }
294
295 pub(crate) fn lingering(&self) -> &super::linger::Lingering {
297 &self.lingering
298 }
299}
300
301#[expect(
303 clippy::panic,
304 reason = "no caller to hand the error to; documented under `# Panics`"
305)]
306pub(crate) fn default_runtime() -> &'static Runtime {
307 fallible::try_default()
308 .unwrap_or_else(|err| panic!("the default runtime could not start: {err}"))
309}
310
311pub(crate) fn current() -> Arc<Inner> {
313 context::runtime().unwrap_or_else(|| Arc::clone(&default_runtime().inner))
314}
315
316pub(crate) fn with_clock<R>(f: impl FnOnce(Option<&crate::time::clock::Clock>) -> R) -> R {
319 match context::runtime() {
320 Some(inner) => f(Some(&inner.clock)),
321 None => f(DEFAULT.get().map(|rt| &rt.inner.clock)),
322 }
323}
324
325fn running() -> Option<Arc<Inner>> {
328 context::runtime().or_else(|| DEFAULT.get().map(|rt| Arc::clone(&rt.inner)))
329}
330
331pub(crate) fn running_inner() -> Option<Arc<Inner>> {
333 running()
334}
335
336pub(crate) fn spawn_linger(
340 inner: &Arc<Inner>,
341 linger: super::linger::Guarded,
342 here: bool,
343) -> Result<(), super::linger::Guarded> {
344 if let Some(shard) = inner.workers.place(inner.id, inner.local_margin) {
345 spawn_on(inner, shard, linger).detach();
346 return Ok(());
347 }
348 let running = context::current(|current| current.is_some());
351 if here && (running || context::own_shard_if_made().is_some()) {
352 let _runtime = context::set_runtime(inner);
353 spawn_here(linger, false).detach();
354 return Ok(());
355 }
356 Err(linger)
357}
358
359pub(crate) fn running_service_shard() -> Option<Arc<Shared>> {
363 let inner = running()?;
364 inner
365 .workers
366 .place(inner.id, inner.local_margin)
367 .map(Arc::clone)
368}
369
370#[cfg(ring)]
374pub(crate) fn service_rings() -> bool {
375 let inner = current();
376 if inner.workers.running() > 0 {
377 inner.rings
378 } else {
379 super::backend::chosen().is_ok_and(|b| b.rings())
380 }
381}
382
383pub(crate) fn service_shard() -> Arc<Shared> {
387 let inner = current();
388 match inner.workers.place(inner.id, inner.local_margin) {
389 Some(shard) => Arc::clone(shard),
390 None => Arc::clone(&own_shard().shared),
391 }
392}
393
394#[expect(
396 clippy::panic,
397 reason = "no caller to hand the error to; documented under `# Panics`"
398)]
399fn own_shard() -> std::rc::Rc<Shard> {
400 match context::own_shard() {
401 Ok(shard) => shard,
402 Err(err) => panic!("this thread's shard could not start: {err}"),
403 }
404}
405
406#[inline(always)]
408fn spawn_with<F>(inner: &Inner, future: F) -> Task<F::Output>
409where
410 F: Future + Send + 'static,
411 F::Output: Send + 'static,
412{
413 let Some(shard) = inner.workers.place(inner.id, inner.local_margin) else {
414 return spawn_here(future, false);
416 };
417 spawn_on(inner, shard, future)
418}
419
420#[inline(always)]
422fn spawn_on<F>(inner: &Inner, shard: &Arc<Shared>, future: F) -> Task<F::Output>
423where
424 F: Future + Send + 'static,
425 F::Output: Send + 'static,
426{
427 let hooks = inner.task_hooks.as_ref();
428 let (runnable, task) = task::spawn(
429 future,
430 Sched::counted(Arc::clone(shard), None, hooks.cloned()),
431 );
432 spawned(hooks.map(|hooks| &**hooks), &task);
433 if let Some(runnable) = super::sched::queue_here(shard, runnable, ScheduleInfo::new(false)) {
435 runnable.schedule();
436 }
437 task
438}
439
440#[inline(always)]
443fn spawned<T>(hooks: Option<&super::tuning::TaskHooks>, task: &Task<T>) {
444 if let Some(hooks) = hooks {
445 spawned_with(hooks, task.id());
446 }
447}
448
449#[cold]
452#[inline(never)]
453fn spawned_with(hooks: &super::tuning::TaskHooks, id: task::Id) {
454 crate::task::name_now(id);
455 super::tuning::call(hooks.spawn.as_ref(), id);
456}
457
458fn context_hooks() -> Option<Arc<super::tuning::TaskHooks>> {
461 if !super::tuning::ANY_TASK_HOOKS.load(std::sync::atomic::Ordering::Acquire) {
462 return None;
463 }
464 context::runtime().and_then(|inner| inner.task_hooks.clone())
465}
466
467fn spawn_here<F>(future: F, bound: bool) -> Task<F::Output>
470where
471 F: Future + 'static,
472 F::Output: 'static,
473{
474 let place = |shard: &Shard| {
475 (
476 Arc::clone(&shard.shared),
477 bound.then(|| context::home_for(shard)),
478 )
479 };
480 let (shared, home) =
481 context::current(|current| current.map(place)).unwrap_or_else(|| place(&own_shard()));
482 let hooks = context_hooks();
483 let (runnable, task, bound_to) = if let Some(home) = home {
484 let at = Arc::as_ptr(&home);
485 let (runnable, task) = unsafe {
489 task::spawn_unchecked(future, Sched::counted(shared, Some(home), hooks.clone()))
490 };
491 (runnable, task, Some(at))
492 } else {
493 let (runnable, task) =
497 unsafe { task::spawn_unchecked(future, Sched::counted(shared, None, hooks.clone())) };
498 (runnable, task, None)
499 };
500 spawned(hooks.as_deref(), &task);
501 let left = match bound_to {
504 Some(home) => {
505 let home = unsafe { &*home };
508 super::sched::queue_home_here(home, runnable, ScheduleInfo::new(false))
509 }
510 None => Some(runnable),
511 };
512 if let Some(runnable) = left {
513 runnable.schedule();
514 }
515 task
516}
517
518fn block_on_with<F: Future>(inner: &Arc<Inner>, future: F) -> F::Output {
521 if inner.shutdown_on_panic {
522 return block_on_here(inner, panic::watched(inner, future));
523 }
524 block_on_here(inner, future)
525}
526
527fn block_on_here<F: Future>(inner: &Arc<Inner>, future: F) -> F::Output {
528 let _runtime = context::set_runtime(inner);
529 let mut future = core::pin::pin!(future);
531 if let Some(output) =
533 context::current(|current| current.map(|shard| shard.block_on(future.as_mut())))
534 {
535 return output;
536 }
537 let shard = own_shard();
538 let _current = context::enter(&shard);
539 let _guest = super::paused::Guest::enter(inner, &shard.shared);
540 let _seeded = inner
542 .rng_seed
543 .map(|seed| super::rng::Reseeded::new(seed, 0));
544 #[cfg(port)]
545 shard.take_port_back();
546 shard.block_on_then_leave(future.as_mut())
547}
548
549pub fn block_on<F: Future>(future: F) -> F::Output {
562 block_on_with(¤t(), future)
563}
564
565#[inline]
584pub fn spawn<F>(future: F) -> Task<F::Output>
585where
586 F: Future + Send + 'static,
587 F::Output: Send + 'static,
588{
589 let spawned = context::with_runtime(|inner| match inner {
592 Some(inner) if inner.task_hooks.is_none() => Ok(spawn_with(inner, future)),
593 _ => Err(future),
594 });
595 spawned.unwrap_or_else(spawn_current)
596}
597
598#[inline(never)]
601fn spawn_current<F>(future: F) -> Task<F::Output>
602where
603 F: Future + Send + 'static,
604 F::Output: Send + 'static,
605{
606 spawn_with(¤t(), future)
607}
608
609pub fn spawn_local<F>(future: F) -> Task<F::Output>
619where
620 F: Future + 'static,
621 F::Output: 'static,
622{
623 spawn_here(future, true)
624}
625
626pub fn spawn_blocking<F, T>(f: F) -> Task<T>
633where
634 F: FnOnce() -> T + Send + 'static,
635 T: Send + 'static,
636{
637 current().pool.spawn(f)
638}
639
640#[cfg(unix)]
644pub(crate) fn spawn_service<F>(future: F)
645where
646 F: Future<Output = ()> + Send + 'static,
647{
648 let shared = context::current(|current| current.map(|s| Arc::clone(&s.shared)))
649 .unwrap_or_else(service_shard);
650 let (runnable, task) = unsafe {
653 task::spawn_unchecked(future, Sched::uncounted(shared))
655 };
656 runnable.schedule();
657 task.detach();
658}
659
660#[cfg(port)]
664pub(crate) fn spawn_blocking_inside(f: impl FnOnce() + Send + 'static) -> bool {
665 match blocking_pool_inside() {
666 Some(pool) => {
667 pool.spawn(f).detach();
668 true
669 }
670 None => false,
671 }
672}
673
674#[cfg(completion)]
677pub(crate) fn blocking_pool_inside() -> Option<Arc<super::blocking::Pool>> {
678 context::runtime().map(|inner| Arc::clone(&inner.pool))
679}
680
681pub(crate) fn allowed_cores() -> Vec<usize> {
685 #[cfg(target_os = "linux")]
686 {
687 current_mask().map_or_else(Vec::new, |set| {
688 (0..libc::CPU_SETSIZE as usize)
689 .filter(|&cpu| unsafe { libc::CPU_ISSET(cpu, &set) })
691 .collect()
692 })
693 }
694 #[cfg(windows)]
695 {
696 use crate::syscore::windows::{GetCurrentProcess, GetProcessAffinityMask};
697 let (mut process, mut system) = (0usize, 0usize);
698 if unsafe { GetProcessAffinityMask(GetCurrentProcess(), &mut process, &mut system) } != 0 {
700 return (0..usize::BITS as usize)
701 .filter(|&cpu| process & (1 << cpu) != 0)
702 .collect();
703 }
704 Vec::new()
705 }
706 #[cfg(not(any(target_os = "linux", windows)))]
707 {
708 Vec::new()
709 }
710}
711
712#[cfg(target_os = "linux")]
714pub(crate) type CpuMask = libc::cpu_set_t;
715#[cfg(windows)]
717pub(crate) type CpuMask = usize;
718#[cfg(not(any(target_os = "linux", windows)))]
720pub(crate) type CpuMask = ();
721
722pub(crate) fn current_mask() -> Option<CpuMask> {
724 #[cfg(target_os = "linux")]
725 {
726 unsafe {
728 let mut set: libc::cpu_set_t = core::mem::zeroed();
729 (libc::sched_getaffinity(0, size_of::<libc::cpu_set_t>(), &mut set) == 0).then_some(set)
730 }
731 }
732 #[cfg(not(target_os = "linux"))]
733 None
734}
735
736pub(crate) fn apply_mask(mask: &CpuMask) {
739 #[cfg(target_os = "linux")]
740 let _ = unsafe { libc::sched_setaffinity(0, size_of::<libc::cpu_set_t>(), mask) };
742 #[cfg(windows)]
743 {
744 use crate::syscore::windows::{GetCurrentThread, SetThreadAffinityMask};
745 unsafe { SetThreadAffinityMask(GetCurrentThread(), *mask) };
747 }
748 #[cfg(not(any(target_os = "linux", windows)))]
749 let _ = mask;
750}
751
752pub(crate) fn pin_to(core: usize) {
755 #[cfg(target_os = "linux")]
756 unsafe {
758 let mut set: libc::cpu_set_t = core::mem::zeroed();
759 libc::CPU_SET(core, &mut set);
760 let _ = libc::sched_setaffinity(0, size_of::<libc::cpu_set_t>(), &set);
761 }
762 #[cfg(windows)]
763 if core < usize::BITS as usize {
764 use crate::syscore::windows::{GetCurrentThread, SetThreadAffinityMask};
765 unsafe { SetThreadAffinityMask(GetCurrentThread(), 1 << core) };
767 }
768 #[cfg(not(any(target_os = "linux", windows)))]
769 let _ = core;
770}