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` *(v0.2.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//! ## v0.3.1 — new in this release
30//!
31//! - **G state machine**: `casgstatus` centralises all goroutine status
32//! transitions. `GSYSCALL`, `GCOPYSTACK`, `GPREEMPTED`, and `GSCAN` are
33//! now wired into `entersyscall`/`exitsyscall`, `copystack`, and `preemptm`
34//! respectively, matching Go 1.14+ semantics.
35//! - **`systemstack`**: runs a closure on the M's g0 (system) stack via a
36//! naked-assembly RSP/SP switch. Implemented for both AMD64 (SysV + Windows
37//! x64) and AArch64 (AAPCS64).
38//!
39//! ## v0.2.0 — new in this release
40//!
41//! - **Dynamic stack growth** (Step 3): goroutines start with a 64 KiB stack
42//! and grow automatically up to 1 GiB via SIGSEGV guard-page detection and
43//! `copystack` (conservative pointer adjustment).
44//! - **Async preemption** (Step 4): sysmon sends `SIGURG` to the M thread whose
45//! goroutine has run > 10 ms. The signal handler redirects execution to an
46//! assembly trampoline that saves all registers, calls `async_preempt2`, and
47//! restores state on resume — a transparent, non-cooperative yield.
48//! - **Netpoll / async I/O** (Step 5): `epoll` on Linux, `kqueue` on macOS,
49//! IOCP on Windows. Goroutines park on blocking I/O and are re-enqueued
50//! when the operation is ready (Unix) or completes (Windows IOCP).
51//! See the [`net`] module for `TcpListener` / `TcpStream`.
52//!
53//! ## Known limitations
54//!
55//! ### `defer` / `recover` / cross-goroutine `panic`
56//! Goroutine panics are caught and routed to [`set_panic_handler`]; the
57//! process does not abort. Go's `recover()` (stopping panic propagation at a
58//! call-stack boundary) has no direct Rust equivalent — use `catch_unwind`
59//! inside the goroutine body when fine-grained recovery is needed.
60//!
61//! ### Race detector
62//! The Go race detector is a compiler/runtime feature with no Rust equivalent
63//! in this crate. Use `cargo test --cfg loom` with the [loom model checker]
64//! for systematic concurrency testing.
65//!
66//! ## Unsafe conventions
67//! The runtime modules (`src/runtime/`) are a direct port of Go's C-adjacent
68//! runtime code. Almost every function is `unsafe fn` because it operates on
69//! raw goroutine pointers and `mmap`'d memory. Inner `unsafe {}` blocks are
70//! omitted for brevity (suppressed via `unsafe_op_in_unsafe_fn`) — the caller's
71//! obligation is documented in each function's `# Safety` section instead.
72#![deny(missing_docs)]
73// The runtime is a deliberate port of Go's low-level C-adjacent scheduler code.
74// Virtually every function is `unsafe fn`; requiring inner `unsafe {}` blocks
75// on every raw-pointer dereference would add noise without safety information.
76// Each `unsafe fn`'s contract is documented in its `# Safety` section instead.
77#![allow(unsafe_op_in_unsafe_fn)]
78
79/// Attribute macro that wraps a function body in [`run`].
80///
81/// ```rust,ignore
82/// #[go_lib::run]
83/// fn main() {
84/// let (tx, rx) = go_lib::chan::chan::<i32>(0);
85/// go_lib::go!(move || tx.send(42));
86/// println!("{}", rx.recv().unwrap());
87/// }
88/// ```
89///
90/// See the [`go_lib_macros::run`][`main`] documentation for the full
91/// expansion rules and return-type support.
92pub use go_lib_macros::run;
93
94pub mod chan;
95pub mod context;
96/// Goroutine-aware TCP networking (Step 5: netpoll integration).
97///
98/// See [`net::TcpListener`] and [`net::TcpStream`].
99///
100/// On Linux and macOS the backend is `epoll` / `kqueue` (readiness-based).
101/// On Windows the backend is I/O Completion Ports (IOCP): overlapped
102/// `WSARecv`/`WSASend` operations are issued and the goroutine parks until
103/// `GetQueuedCompletionStatusEx` signals completion.
104#[cfg(not(windows))]
105pub mod net;
106#[cfg(windows)]
107#[path = "net_windows.rs"]
108pub mod net;
109pub mod runtime;
110pub mod select;
111pub mod sync;
112
113mod go_macro;
114pub(crate) mod loom_shim;
115
116/// Initialise the go-lib scheduler, run `f` as the first goroutine, and
117/// return whatever `f` returns.
118///
119/// Blocks the calling thread until `f` returns. The scheduler threads
120/// (one per logical CPU) continue running in the background after `run`
121/// returns; they park themselves when there is no more work.
122///
123/// # Parameters
124///
125/// `f` can capture any values it needs from the surrounding scope via a
126/// `move` closure — there is no need to pass parameters directly to `run`:
127///
128/// ```no_run
129/// let base = 10_i32;
130/// let result = go_lib::run(move || base * 2);
131/// assert_eq!(result, 20);
132/// ```
133///
134/// # Return values
135///
136/// The closure's return value is propagated back to the caller:
137///
138/// ```no_run
139/// let sum = go_lib::run(|| {
140/// let (tx, rx) = go_lib::chan::chan::<i32>(4);
141/// for i in 1..=4 { let t = tx.clone(); go_lib::__spawn(move || t.send(i)); }
142/// (0..4).filter_map(|_| rx.recv()).sum::<i32>()
143/// });
144/// assert_eq!(sum, 10);
145/// ```
146///
147/// When the closure returns `()` (the default), `run` behaves exactly as
148/// before — the return value can simply be ignored.
149///
150/// # Panics
151///
152/// Panics if `f` panics before producing a return value. A panicking
153/// goroutine is caught by the scheduler's `catch_unwind`; the panic payload
154/// is forwarded to [`set_panic_handler`] and the calling thread is woken
155/// with an `expect` failure.
156pub fn run<F, R>(f: F) -> R
157where
158 F: FnOnce() -> R + Send + 'static,
159 R: Send + 'static,
160{
161 runtime::sched::run_impl(f)
162}
163
164/// Yield the CPU, giving other goroutines a chance to run.
165///
166/// Moves the current goroutine to the back of the global run queue and
167/// re-enters the scheduler. Execution resumes at the next `gosched()` call
168/// site once the goroutine is rescheduled.
169///
170/// CPU-bound loops should call `gosched()` periodically. The background
171/// sysmon thread also sets a preemption hint after 10 ms, but because v1 has
172/// no stack-check traps the goroutine must call `gosched()` voluntarily for
173/// the hint to take effect.
174///
175/// # Panics
176///
177/// Panics if called from outside a goroutine (e.g. from `main` before
178/// calling [`run`]).
179///
180/// # Example
181///
182/// ```no_run
183/// go_lib::run(|| {
184/// for i in 0..1_000_000 {
185/// if i % 10_000 == 0 {
186/// go_lib::gosched(); // let other goroutines run
187/// }
188/// }
189/// });
190/// ```
191pub fn gosched() {
192 // SAFETY: we are on a goroutine stack (enforced by the debug_assert inside
193 // the internal gosched that current_g() is non-null).
194 unsafe { runtime::sched::gosched() }
195}
196
197/// Wrap a potentially-blocking operation so the go-lib scheduler can
198/// hand off this goroutine's P to another M while the OS thread is in the
199/// kernel.
200///
201/// Calls [`entersyscall`][runtime::syscall::entersyscall] before `f` and
202/// [`exitsyscall`][runtime::syscall::exitsyscall] after `f` returns. This is
203/// a no-op when called outside the scheduler (before [`run`]).
204///
205/// # Example
206///
207/// ```no_run
208/// go_lib::run(|| {
209/// let data = go_lib::with_syscall(|| std::fs::read("file.txt"));
210/// });
211/// ```
212pub fn with_syscall<F, R>(f: F) -> R
213where
214 F: FnOnce() -> R,
215{
216 runtime::syscall::with_syscall(f)
217}
218
219/// Sleep the current goroutine for at least `d`.
220///
221/// Parks the goroutine and lets other goroutines run; the background timer
222/// thread calls [`goready`][runtime::park] when the duration elapses.
223///
224/// Passing `Duration::ZERO` yields to the scheduler without sleeping.
225///
226/// # Panics
227///
228/// Debug-panics if called from outside a goroutine.
229///
230/// # Example
231///
232/// ```no_run
233/// go_lib::run(|| {
234/// go_lib::sleep(std::time::Duration::from_millis(10));
235/// });
236/// ```
237pub fn sleep(d: std::time::Duration) {
238 // SAFETY: called from a goroutine context (checked by debug_assert in sleep).
239 unsafe { runtime::time::goroutine_sleep(d) }
240}
241
242/// Spawn a goroutine. Called by the [`go!`] macro; not for direct use.
243///
244/// Must be called from within a running goroutine (i.e. inside [`run`]).
245///
246/// # Panics
247///
248/// Debug-panics if called from outside a goroutine context.
249#[doc(hidden)]
250pub fn __spawn<F: FnOnce() + Send + 'static>(f: F) {
251 runtime::sched::spawn_goroutine(f)
252}
253
254// ---------------------------------------------------------------------------
255// GOMAXPROCS
256// ---------------------------------------------------------------------------
257
258/// Return the current number of logical processors (GOMAXPROCS).
259///
260/// This equals the value set by the `GOMAXPROCS` environment variable at
261/// startup, or [`set_gomaxprocs`], or `available_parallelism` if neither was
262/// provided.
263pub fn gomaxprocs() -> usize {
264 runtime::sched::gomaxprocs()
265}
266
267/// Set the number of logical processors and return the previous value.
268///
269/// See [`runtime::sched::set_gomaxprocs`] for full semantics.
270///
271/// # Example
272///
273/// ```no_run
274/// let old = go_lib::set_gomaxprocs(2);
275/// println!("was {old}, now {}", go_lib::gomaxprocs());
276/// ```
277pub fn set_gomaxprocs(n: usize) -> usize {
278 runtime::sched::set_gomaxprocs(n)
279}
280
281// ---------------------------------------------------------------------------
282// Goroutine panic handler
283// ---------------------------------------------------------------------------
284
285/// Register a custom handler for goroutine panics.
286///
287/// By default, a panicking goroutine prints its payload to stderr and the
288/// scheduler continues running other goroutines — the process does **not**
289/// abort.
290///
291/// Calling `set_panic_handler` replaces the previous handler. The handler
292/// receives the `Box<dyn Any + Send>` payload from `std::panic::catch_unwind`.
293///
294/// # Example
295///
296/// ```no_run
297/// go_lib::set_panic_handler(|payload| {
298/// if let Some(s) = payload.downcast_ref::<String>() {
299/// eprintln!("goroutine panicked: {s}");
300/// }
301/// });
302/// ```
303pub fn set_panic_handler<F>(f: F)
304where
305 F: Fn(Box<dyn std::any::Any + Send + 'static>) + Send + Sync + 'static,
306{
307 runtime::sched::set_panic_handler(f);
308}