Skip to main content

go_lib/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2//! # go-lib
3//!
4//! Go-style concurrency for Rust: goroutines, channels, `select`, `WaitGroup` —
5//! built on a port of the M:N scheduler from <https://github.com/golang/go>.
6//!
7//! No async runtime is used: the scheduler, channels, and parking primitives
8//! are ported from `src/runtime/` in the Go repo. Mutexes and read-write locks
9//! are taken straight from [`std::sync`] because their uncontended path is
10//! just an atomic CAS — porting Go's versions would be code without benefit.
11//! See [`runtime::syscall`] for the shim that keeps `std` blocking calls
12//! scheduler-safe.
13//!
14//! ## Public surface
15//! - `go!` / `select!` macros — spawn goroutines, multiplex channel ops
16//! - [`chan`] — buffered and unbuffered channels
17//! - [`net`] — goroutine-aware `TcpListener` / `TcpStream` *(v2.0)*
18//! - [`sync::WaitGroup`] — wait for a collection of goroutines
19//! - [`sync::Cond`] — goroutine-aware condition variable
20//! - [`sync::Mutex`] / [`sync::RwLock`] — re-exports of `std::sync`
21//! - [`context`] — cancellation and deadline propagation
22//! - [`set_panic_handler`] — customise goroutine-panic behaviour
23//! - [`set_gomaxprocs`] / [`gomaxprocs`] — runtime parallelism control
24//!
25//! ## Internals
26//! See [`runtime`] for the scheduler (G/M/P, parking, work stealing, sysmon,
27//! stack growth, async preemption, netpoll).
28//!
29//! ## v2.0 — new in this release
30//!
31//! - **Dynamic stack growth** (Step 3): goroutines start with an 8 KiB stack
32//!   and grow automatically up to 1 GiB via SIGSEGV guard-page detection and
33//!   `copystack` (conservative pointer adjustment).
34//! - **Async preemption** (Step 4): sysmon sends `SIGURG` to the M thread whose
35//!   goroutine has run > 10 ms.  The signal handler redirects execution to an
36//!   assembly trampoline that saves all registers, calls `async_preempt2`, and
37//!   restores state on resume — a transparent, non-cooperative yield.
38//! - **Netpoll / async I/O** (Step 5): `epoll` on Linux, `kqueue` on macOS,
39//!   IOCP on Windows.  Goroutines park on blocking I/O and are re-enqueued
40//!   when the operation is ready (Unix) or completes (Windows IOCP).
41//!   See the [`net`] module for `TcpListener` / `TcpStream`.
42//!
43//! ## Known limitations
44//!
45//! ### `defer` / `recover` / cross-goroutine `panic`
46//! Goroutine panics are caught and routed to [`set_panic_handler`]; the
47//! process does not abort.  Go's `recover()` (stopping panic propagation at a
48//! call-stack boundary) has no direct Rust equivalent — use `catch_unwind`
49//! inside the goroutine body when fine-grained recovery is needed.
50//!
51//! ### Race detector
52//! The Go race detector is a compiler/runtime feature with no Rust equivalent
53//! in this crate.  Use `cargo test --cfg loom` with the [loom model checker]
54//! for systematic concurrency testing.
55//!
56//! ## Unsafe conventions
57//! The runtime modules (`src/runtime/`) are a direct port of Go's C-adjacent
58//! runtime code.  Almost every function is `unsafe fn` because it operates on
59//! raw goroutine pointers and `mmap`'d memory.  Inner `unsafe {}` blocks are
60//! omitted for brevity (suppressed via `unsafe_op_in_unsafe_fn`) — the caller's
61//! obligation is documented in each function's `# Safety` section instead.
62#![deny(missing_docs)]
63// The runtime is a deliberate port of Go's low-level C-adjacent scheduler code.
64// Virtually every function is `unsafe fn`; requiring inner `unsafe {}` blocks
65// on every raw-pointer dereference would add noise without safety information.
66// Each `unsafe fn`'s contract is documented in its `# Safety` section instead.
67#![allow(unsafe_op_in_unsafe_fn)]
68
69pub mod chan;
70pub mod context;
71/// Goroutine-aware TCP networking (Step 5: netpoll integration).
72///
73/// See [`net::TcpListener`] and [`net::TcpStream`].
74///
75/// On Linux and macOS the backend is `epoll` / `kqueue` (readiness-based).
76/// On Windows the backend is I/O Completion Ports (IOCP): overlapped
77/// `WSARecv`/`WSASend` operations are issued and the goroutine parks until
78/// `GetQueuedCompletionStatusEx` signals completion.
79#[cfg(not(windows))]
80pub mod net;
81#[cfg(windows)]
82#[path = "net_windows.rs"]
83pub mod net;
84pub mod runtime;
85pub mod select;
86pub mod sync;
87
88mod go_macro;
89pub(crate) mod loom_shim;
90
91/// Initialise the go-lib scheduler and run `f` as the first goroutine.
92///
93/// Blocks the calling thread until `f` returns.  The scheduler threads
94/// (one per logical CPU) continue running in the background after `run`
95/// returns; they park themselves when there is no more work.
96///
97/// # Example
98///
99/// ```no_run
100/// go_lib::run(|| {
101///     println!("hello from a goroutine");
102/// });
103/// ```
104pub fn run<F: FnOnce() + Send + 'static>(f: F) {
105    runtime::sched::run_impl(f);
106}
107
108/// Yield the CPU, giving other goroutines a chance to run.
109///
110/// Moves the current goroutine to the back of the global run queue and
111/// re-enters the scheduler.  Execution resumes at the next `gosched()` call
112/// site once the goroutine is rescheduled.
113///
114/// CPU-bound loops should call `gosched()` periodically.  The background
115/// sysmon thread also sets a preemption hint after 10 ms, but because v1 has
116/// no stack-check traps the goroutine must call `gosched()` voluntarily for
117/// the hint to take effect.
118///
119/// # Panics
120///
121/// Panics if called from outside a goroutine (e.g. from `main` before
122/// calling [`run`]).
123///
124/// # Example
125///
126/// ```no_run
127/// go_lib::run(|| {
128///     for i in 0..1_000_000 {
129///         if i % 10_000 == 0 {
130///             go_lib::gosched(); // let other goroutines run
131///         }
132///     }
133/// });
134/// ```
135pub fn gosched() {
136    // SAFETY: we are on a goroutine stack (enforced by the debug_assert inside
137    // the internal gosched that current_g() is non-null).
138    unsafe { runtime::sched::gosched() }
139}
140
141/// Wrap a potentially-blocking operation so the go-lib scheduler can
142/// hand off this goroutine's P to another M while the OS thread is in the
143/// kernel.
144///
145/// Calls [`entersyscall`][runtime::syscall::entersyscall] before `f` and
146/// [`exitsyscall`][runtime::syscall::exitsyscall] after `f` returns.  This is
147/// a no-op when called outside the scheduler (before [`run`]).
148///
149/// # Example
150///
151/// ```no_run
152/// go_lib::run(|| {
153///     let data = go_lib::with_syscall(|| std::fs::read("file.txt"));
154/// });
155/// ```
156pub fn with_syscall<F, R>(f: F) -> R
157where
158    F: FnOnce() -> R,
159{
160    runtime::syscall::with_syscall(f)
161}
162
163/// Sleep the current goroutine for at least `d`.
164///
165/// Parks the goroutine and lets other goroutines run; the background timer
166/// thread calls [`goready`][runtime::park] when the duration elapses.
167///
168/// Passing `Duration::ZERO` yields to the scheduler without sleeping.
169///
170/// # Panics
171///
172/// Debug-panics if called from outside a goroutine.
173///
174/// # Example
175///
176/// ```no_run
177/// go_lib::run(|| {
178///     go_lib::sleep(std::time::Duration::from_millis(10));
179/// });
180/// ```
181pub fn sleep(d: std::time::Duration) {
182    // SAFETY: called from a goroutine context (checked by debug_assert in sleep).
183    unsafe { runtime::time::goroutine_sleep(d) }
184}
185
186/// Spawn a goroutine.  Called by the [`go!`] macro; not for direct use.
187///
188/// Must be called from within a running goroutine (i.e. inside [`run`]).
189///
190/// # Panics
191///
192/// Debug-panics if called from outside a goroutine context.
193#[doc(hidden)]
194pub fn __spawn<F: FnOnce() + Send + 'static>(f: F) {
195    runtime::sched::spawn_goroutine(f)
196}
197
198// ---------------------------------------------------------------------------
199// GOMAXPROCS
200// ---------------------------------------------------------------------------
201
202/// Return the current number of logical processors (GOMAXPROCS).
203///
204/// This equals the value set by the `GOMAXPROCS` environment variable at
205/// startup, or [`set_gomaxprocs`], or `available_parallelism` if neither was
206/// provided.
207pub fn gomaxprocs() -> usize {
208    runtime::sched::gomaxprocs()
209}
210
211/// Set the number of logical processors and return the previous value.
212///
213/// See [`runtime::sched::set_gomaxprocs`] for full semantics.
214///
215/// # Example
216///
217/// ```no_run
218/// let old = go_lib::set_gomaxprocs(2);
219/// println!("was {old}, now {}", go_lib::gomaxprocs());
220/// ```
221pub fn set_gomaxprocs(n: usize) -> usize {
222    runtime::sched::set_gomaxprocs(n)
223}
224
225// ---------------------------------------------------------------------------
226// Goroutine panic handler
227// ---------------------------------------------------------------------------
228
229/// Register a custom handler for goroutine panics.
230///
231/// By default, a panicking goroutine prints its payload to stderr and the
232/// scheduler continues running other goroutines — the process does **not**
233/// abort.
234///
235/// Calling `set_panic_handler` replaces the previous handler.  The handler
236/// receives the `Box<dyn Any + Send>` payload from `std::panic::catch_unwind`.
237///
238/// # Example
239///
240/// ```no_run
241/// go_lib::set_panic_handler(|payload| {
242///     if let Some(s) = payload.downcast_ref::<String>() {
243///         eprintln!("goroutine panicked: {s}");
244///     }
245/// });
246/// ```
247pub fn set_panic_handler<F>(f: F)
248where
249    F: Fn(Box<dyn std::any::Any + Send + 'static>) + Send + Sync + 'static,
250{
251    runtime::sched::set_panic_handler(f);
252}