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//! Goroutines park on `EAGAIN` and are re-enqueued when the fd is ready.
40//! See the [`net`] module for `TcpListener` / `TcpStream`.
41//!
42//! ## Known limitations
43//!
44//! ### `defer` / `recover` / cross-goroutine `panic`
45//! Goroutine panics are caught and routed to [`set_panic_handler`]; the
46//! process does not abort. Go's `recover()` (stopping panic propagation at a
47//! call-stack boundary) has no direct Rust equivalent — use `catch_unwind`
48//! inside the goroutine body when fine-grained recovery is needed.
49//!
50//! ### Race detector
51//! The Go race detector is a compiler/runtime feature with no Rust equivalent
52//! in this crate. Use `cargo test --cfg loom` with the [loom model checker]
53//! for systematic concurrency testing.
54//!
55//! ## Unsafe conventions
56//! The runtime modules (`src/runtime/`) are a direct port of Go's C-adjacent
57//! runtime code. Almost every function is `unsafe fn` because it operates on
58//! raw goroutine pointers and `mmap`'d memory. Inner `unsafe {}` blocks are
59//! omitted for brevity (suppressed via `unsafe_op_in_unsafe_fn`) — the caller's
60//! obligation is documented in each function's `# Safety` section instead.
61#![deny(missing_docs)]
62// The runtime is a deliberate port of Go's low-level C-adjacent scheduler code.
63// Virtually every function is `unsafe fn`; requiring inner `unsafe {}` blocks
64// on every raw-pointer dereference would add noise without safety information.
65// Each `unsafe fn`'s contract is documented in its `# Safety` section instead.
66#![allow(unsafe_op_in_unsafe_fn)]
67
68pub mod chan;
69pub mod context;
70/// Goroutine-aware TCP networking (Step 5: netpoll integration).
71///
72/// See [`net::TcpListener`] and [`net::TcpStream`].
73///
74/// **Note**: The networking module currently requires a Unix platform
75/// (epoll/kqueue). On Windows it is not compiled.
76#[cfg(not(windows))]
77pub mod net;
78pub mod runtime;
79pub mod select;
80pub mod sync;
81
82mod go_macro;
83pub(crate) mod loom_shim;
84
85/// Initialise the go-lib scheduler and run `f` as the first goroutine.
86///
87/// Blocks the calling thread until `f` returns. The scheduler threads
88/// (one per logical CPU) continue running in the background after `run`
89/// returns; they park themselves when there is no more work.
90///
91/// # Example
92///
93/// ```no_run
94/// go_lib::run(|| {
95/// println!("hello from a goroutine");
96/// });
97/// ```
98pub fn run<F: FnOnce() + Send + 'static>(f: F) {
99 runtime::sched::run_impl(f);
100}
101
102/// Yield the CPU, giving other goroutines a chance to run.
103///
104/// Moves the current goroutine to the back of the global run queue and
105/// re-enters the scheduler. Execution resumes at the next `gosched()` call
106/// site once the goroutine is rescheduled.
107///
108/// CPU-bound loops should call `gosched()` periodically. The background
109/// sysmon thread also sets a preemption hint after 10 ms, but because v1 has
110/// no stack-check traps the goroutine must call `gosched()` voluntarily for
111/// the hint to take effect.
112///
113/// # Panics
114///
115/// Panics if called from outside a goroutine (e.g. from `main` before
116/// calling [`run`]).
117///
118/// # Example
119///
120/// ```no_run
121/// go_lib::run(|| {
122/// for i in 0..1_000_000 {
123/// if i % 10_000 == 0 {
124/// go_lib::gosched(); // let other goroutines run
125/// }
126/// }
127/// });
128/// ```
129pub fn gosched() {
130 // SAFETY: we are on a goroutine stack (enforced by the debug_assert inside
131 // the internal gosched that current_g() is non-null).
132 unsafe { runtime::sched::gosched() }
133}
134
135/// Wrap a potentially-blocking operation so the go-lib scheduler can
136/// hand off this goroutine's P to another M while the OS thread is in the
137/// kernel.
138///
139/// Calls [`entersyscall`][runtime::syscall::entersyscall] before `f` and
140/// [`exitsyscall`][runtime::syscall::exitsyscall] after `f` returns. This is
141/// a no-op when called outside the scheduler (before [`run`]).
142///
143/// # Example
144///
145/// ```no_run
146/// go_lib::run(|| {
147/// let data = go_lib::with_syscall(|| std::fs::read("file.txt"));
148/// });
149/// ```
150pub fn with_syscall<F, R>(f: F) -> R
151where
152 F: FnOnce() -> R,
153{
154 runtime::syscall::with_syscall(f)
155}
156
157/// Sleep the current goroutine for at least `d`.
158///
159/// Parks the goroutine and lets other goroutines run; the background timer
160/// thread calls [`goready`][runtime::park] when the duration elapses.
161///
162/// Passing `Duration::ZERO` yields to the scheduler without sleeping.
163///
164/// # Panics
165///
166/// Debug-panics if called from outside a goroutine.
167///
168/// # Example
169///
170/// ```no_run
171/// go_lib::run(|| {
172/// go_lib::sleep(std::time::Duration::from_millis(10));
173/// });
174/// ```
175pub fn sleep(d: std::time::Duration) {
176 // SAFETY: called from a goroutine context (checked by debug_assert in sleep).
177 unsafe { runtime::time::goroutine_sleep(d) }
178}
179
180/// Spawn a goroutine. Called by the [`go!`] macro; not for direct use.
181///
182/// Must be called from within a running goroutine (i.e. inside [`run`]).
183///
184/// # Panics
185///
186/// Debug-panics if called from outside a goroutine context.
187#[doc(hidden)]
188pub fn __spawn<F: FnOnce() + Send + 'static>(f: F) {
189 // SAFETY: callers are expected to be inside a goroutine context.
190 unsafe { runtime::sched::spawn_goroutine(f) }
191}
192
193// ---------------------------------------------------------------------------
194// GOMAXPROCS
195// ---------------------------------------------------------------------------
196
197/// Return the current number of logical processors (GOMAXPROCS).
198///
199/// This equals the value set by the `GOMAXPROCS` environment variable at
200/// startup, or [`set_gomaxprocs`], or `available_parallelism` if neither was
201/// provided.
202pub fn gomaxprocs() -> usize {
203 runtime::sched::gomaxprocs()
204}
205
206/// Set the number of logical processors and return the previous value.
207///
208/// See [`runtime::sched::set_gomaxprocs`] for full semantics.
209///
210/// # Example
211///
212/// ```no_run
213/// let old = go_lib::set_gomaxprocs(2);
214/// println!("was {old}, now {}", go_lib::gomaxprocs());
215/// ```
216pub fn set_gomaxprocs(n: usize) -> usize {
217 runtime::sched::set_gomaxprocs(n)
218}
219
220// ---------------------------------------------------------------------------
221// Goroutine panic handler
222// ---------------------------------------------------------------------------
223
224/// Register a custom handler for goroutine panics.
225///
226/// By default, a panicking goroutine prints its payload to stderr and the
227/// scheduler continues running other goroutines — the process does **not**
228/// abort.
229///
230/// Calling `set_panic_handler` replaces the previous handler. The handler
231/// receives the `Box<dyn Any + Send>` payload from `std::panic::catch_unwind`.
232///
233/// # Example
234///
235/// ```no_run
236/// go_lib::set_panic_handler(|payload| {
237/// if let Some(s) = payload.downcast_ref::<String>() {
238/// eprintln!("goroutine panicked: {s}");
239/// }
240/// });
241/// ```
242pub fn set_panic_handler<F>(f: F)
243where
244 F: Fn(Box<dyn std::any::Any + Send + 'static>) + Send + Sync + 'static,
245{
246 runtime::sched::set_panic_handler(f);
247}