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