magnus/api.rs
1//! This module/file's name is a hack to get the `impl Ruby` defined here to
2//! show first in docs. This module shouldn't be exposed publicly.
3
4use std::{cell::RefCell, marker::PhantomData};
5
6use rb_sys::ruby_native_thread_p;
7
8// Ruby does not expose this publicly, but it is used in the fiddle gem via
9// this kind of hack, and although the function is marked experimental in
10// Ruby's source, that comment and the code have been unchanged singe 1.9.2,
11// 14 years ago as of writing.
12extern "C" {
13 fn ruby_thread_has_gvl_p() -> ::std::os::raw::c_int;
14}
15
16use crate::{error::RubyUnavailableError, value::ReprValue};
17
18#[derive(Clone, Copy)]
19enum RubyGvlState {
20 Locked,
21 Unlocked,
22 NonRubyThread,
23}
24
25thread_local! {
26 static RUBY_GVL_STATE: RefCell<Option<RubyGvlState>> = const { RefCell::new(None) };
27}
28
29impl RubyGvlState {
30 fn current() -> Self {
31 let current = if unsafe { ruby_thread_has_gvl_p() } != 0 {
32 Self::Locked
33 } else if unsafe { ruby_native_thread_p() != 0 } {
34 Self::Unlocked
35 } else {
36 Self::NonRubyThread
37 };
38 RUBY_GVL_STATE.with(|ruby_gvl_state| {
39 *ruby_gvl_state.borrow_mut() = Some(current);
40 });
41 current
42 }
43
44 fn cached() -> Self {
45 RUBY_GVL_STATE.with(|ruby_gvl_state| {
46 let x = *ruby_gvl_state.borrow();
47 match x {
48 // assumed not to change because there's currently no api to
49 // unlock.
50 Some(Self::Locked) => Self::Locked,
51 None => Self::current(),
52 // Don't expect without an api to unlock, so skip cache
53 Some(Self::Unlocked) => Self::current(),
54 // assumed not to change
55 Some(Self::NonRubyThread) => Self::NonRubyThread,
56 }
57 })
58 }
59
60 fn ok<T>(self, value: T) -> Result<T, RubyUnavailableError> {
61 match self {
62 Self::Locked => Ok(value),
63 Self::Unlocked => Err(RubyUnavailableError::GvlUnlocked),
64 Self::NonRubyThread => Err(RubyUnavailableError::NonRubyThread),
65 }
66 }
67}
68
69/// A handle to access Ruby's API.
70///
71/// Using Ruby's API requires the Ruby VM to be initialised and all access to be
72/// from a Ruby-created thread.
73///
74/// This structure allows safe access to Ruby's API as it should only be
75/// possible to acquire an instance in situations where Ruby's API is known to
76/// be available.
77///
78/// Many functions that take Ruby values as arguments are available directly
79/// without having to use a `Ruby` handle, as being able to provide a Ruby
80/// value is 'proof' the function is being called from a Ruby thread. Because
81/// of this most methods defined on `Ruby` deal with creating Ruby objects
82/// from Rust data.
83///
84/// ---
85///
86/// The methods available on `Ruby` are broken up into sections for easier
87/// navigation.
88///
89/// * [Accessing `Ruby`](#accessing-ruby) - how to get a `Ruby` handle
90/// * [Argument Parsing](#argument-parsing) - helpers for argument handling
91/// * [Blocks](#blocks) - working with Ruby blocks
92/// * [Conversion to `Value`](#conversion-to-value)
93/// * [Core Classes](#core-classes) - access built-in classes
94/// * [Core Exceptions](#core-exceptions) - access built-in exceptions
95/// * [Core Modules](#core-modules) - access built-in modules
96/// * [Embedding](#embedding) - functions relevant when embedding Ruby in Rust
97/// * [`Encoding`](#encoding) - string encoding
98/// * [Encoding Index](#encoding-index) - string encoding
99/// * [Errors](#errors)
100/// * [Extracting values from `Opaque`/`Lazy`](#extracting-values-from-opaquelazy)
101/// * [`false`](#false)
102/// * [`Fiber`](#fiber)
103/// * [`Fixnum`](#fixnum) - small/fast integers
104/// * [`Float`](#float)
105/// * [`Flonum`](#flonum) - lower precision/fast floats
106/// * [`GC`](#gc) - Garbage Collection
107/// * [Globals](#globals) - global variables, etc, plus current VM state such
108/// as calling the current `super` method.
109/// * [`Id`](#id) - low-level Symbol representation
110/// * [`Io`](#io-helper-functions) - IO helper functions
111/// * [`Integer`](#integer)
112/// * [`Mutex`](#mutex)
113/// * [`nil`](#nil)
114/// * [`Proc`](#proc) - Ruby's blocks as objects
115/// * [`Process`](#process) - external processes
116/// * [`Range`](#range)
117/// * [`RArray`](#rarray)
118/// * [`RbEncoding`](#rbencoding) - string encoding
119/// * [`RBignum`](#rbignum) - big integers
120/// * [`RFloat`](#rfloat)
121/// * [`RHash`](#rhash)
122/// * [`RModule`](#rmodule)
123/// * [`RRational`](#rrational)
124/// * [`RRegexp`](#rregexp)
125/// * [`RString`](#rstring)
126/// * [`RTypedData`](#rtypeddata) - wrapping Rust data in a Ruby object
127/// * [`StaticSymbol`](#staticsymbol) - non GC'd symbols
128/// * [`Struct`](#struct)
129/// * [`Symbol`](#symbol)
130/// * [`Thread`](#thread)
131/// * [`Time`](#time)
132/// * [`true`](#true)
133/// * [`typed_data::Obj`](#typed_dataobj) - wrapping Rust data in a Ruby object
134pub struct Ruby(PhantomData<*mut ()>);
135
136/// # Accessing `Ruby`
137///
138/// These functions allow you to obtain a `Ruby` handle only when the current
139/// thread is a Ruby thread.
140///
141/// Methods exposed to Ruby via the [`method`](macro@crate::method),
142/// [`function`](macro@crate::function) or [`init`](macro@crate::init) macros
143/// can also take an optional first argument of `&Ruby` to obtain a `Ruby`
144/// handle.
145impl Ruby {
146 /// Get a handle to Ruby's API.
147 ///
148 /// Returns a new handle to Ruby's API if it can be verified the current
149 /// thread is a Ruby thread.
150 ///
151 /// If the Ruby API is not useable, returns `Err(RubyUnavailableError)`.
152 pub fn get() -> Result<Self, RubyUnavailableError> {
153 RubyGvlState::cached().ok(Self(PhantomData))
154 }
155
156 /// Get a handle to Ruby's API.
157 ///
158 /// Returns a new handle to Ruby's API using a Ruby value as proof that the
159 /// current thread is a Ruby thread.
160 ///
161 /// Note that all Ruby values are [`Copy`], so this will not take ownership
162 /// of the passed value.
163 #[allow(unused_variables)]
164 pub fn get_with<T>(value: T) -> Self
165 where
166 T: ReprValue,
167 {
168 Self(PhantomData)
169 }
170
171 /// Get a handle to Ruby's API.
172 ///
173 /// # Safety
174 ///
175 /// This must only be called from a Ruby thread - that is one created by
176 /// Ruby, or the main thread after [`embed::init`](crate::embed::init) has
177 /// been called - and without having released the GVL.
178 #[inline]
179 pub unsafe fn get_unchecked() -> Self {
180 Self(PhantomData)
181 }
182}