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