microsandbox-rb 0.17.3 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -1
- data/Cargo.lock +34 -32
- data/DESIGN.md +134 -41
- data/README.md +2 -1
- data/docs/runtime.md +33 -3
- data/docs/surface.md +4 -1
- data/ext/microsandbox/Cargo.toml +9 -2
- data/ext/microsandbox/src/backend.rs +28 -24
- data/ext/microsandbox/src/error.rs +37 -37
- data/ext/microsandbox/src/lib.rs +50 -2
- data/ext/microsandbox/src/runtime.rs +173 -27
- data/lib/microsandbox/sandbox.rb +39 -20
- data/lib/microsandbox/version.rb +1 -1
- metadata +1 -1
data/ext/microsandbox/Cargo.toml
CHANGED
|
@@ -8,7 +8,7 @@ description = "Ruby SDK native extension for microsandbox — secure, fast micro
|
|
|
8
8
|
# returns this via env!("CARGO_PKG_VERSION") and version_spec.rb asserts equality.
|
|
9
9
|
# The core-crate dependencies below stay on their own tag, which is
|
|
10
10
|
# Microsandbox::RUNTIME_VERSION and moves independently of this version.
|
|
11
|
-
version = "0.17.
|
|
11
|
+
version = "0.17.4"
|
|
12
12
|
authors = ["Super Rad Company <development@superrad.company>"]
|
|
13
13
|
repository = "https://github.com/superradcompany/microsandbox"
|
|
14
14
|
license = "Apache-2.0"
|
|
@@ -25,12 +25,19 @@ crate-type = ["cdylib"]
|
|
|
25
25
|
[dependencies]
|
|
26
26
|
# "rb-sys" exposes `magnus::rb_sys::protect` (rb_protect), which the GVL bridge
|
|
27
27
|
# uses to check interrupts without letting Ruby longjmp over Rust frames.
|
|
28
|
-
|
|
28
|
+
# Default features are off: `old-api` (the free functions that conjure a `Ruby`
|
|
29
|
+
# handle unchecked in release builds) must not creep back, and `io` only gates
|
|
30
|
+
# `magnus::io`, which the extension does not use.
|
|
31
|
+
magnus = { version = "0.9", default-features = false, features = ["rb-sys"] }
|
|
29
32
|
# Low-level Ruby C API — used directly for rb_thread_call_without_gvl2 (GVL
|
|
30
33
|
# release during blocking sandbox calls) and rb_thread_check_ints. Kept in
|
|
31
34
|
# lock-step with magnus's own rb-sys.
|
|
32
35
|
rb-sys = "0.9"
|
|
33
36
|
|
|
37
|
+
# `pthread_atfork`, for the fork generation (runtime::ProcessIdentity). Already
|
|
38
|
+
# in the dependency graph; listed so the binding can call it directly.
|
|
39
|
+
libc = "0.2"
|
|
40
|
+
|
|
34
41
|
# The microsandbox core runtime, wrapped via FFI exactly like the official
|
|
35
42
|
# Python (pyo3) and Node (napi) SDKs. Pinned to the matching upstream tag so the
|
|
36
43
|
# gem is self-contained and buildable anywhere (CI, release containers, end-user
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
//! matching Python and Node.
|
|
17
17
|
|
|
18
18
|
use std::collections::HashMap;
|
|
19
|
-
use std::sync::atomic::{AtomicU32, Ordering};
|
|
19
|
+
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
|
20
20
|
use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError};
|
|
21
21
|
|
|
22
22
|
use magnus::{function, prelude::*, Error, RModule, Ruby};
|
|
23
23
|
use microsandbox::{Backend, MicrosandboxError, Operation};
|
|
24
24
|
|
|
25
25
|
use crate::error;
|
|
26
|
-
use crate::runtime::{block_on,
|
|
26
|
+
use crate::runtime::{block_on, ProcessIdentity};
|
|
27
27
|
|
|
28
28
|
/// Resolve the ambient default backend, requiring it to be local.
|
|
29
29
|
///
|
|
@@ -99,7 +99,7 @@ fn build_backend(
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
//--------------------------------------------------------------------------------------------------
|
|
102
|
-
// Fork safety: the backend
|
|
102
|
+
// Fork safety: the backend record
|
|
103
103
|
//--------------------------------------------------------------------------------------------------
|
|
104
104
|
//
|
|
105
105
|
// `fork(2)` copies only the calling thread. A child of a process that used the
|
|
@@ -114,7 +114,7 @@ fn build_backend(
|
|
|
114
114
|
// So the child never touches what it inherited: the first native call after a
|
|
115
115
|
// fork builds a fresh backend the same way the inherited one was chosen and
|
|
116
116
|
// swaps it in, and every inherited `Arc` is `mem::forget`-ed — leaked once per
|
|
117
|
-
//
|
|
117
|
+
// forked process — instead of dropped.
|
|
118
118
|
//
|
|
119
119
|
// Locking. The three mutexes below are only ever taken on a Ruby thread that
|
|
120
120
|
// holds the GVL, and nothing that runs while one is held calls into Ruby or
|
|
@@ -158,9 +158,11 @@ impl Selection {
|
|
|
158
158
|
|
|
159
159
|
static SELECTION: Mutex<Selection> = Mutex::new(Selection::Ladder);
|
|
160
160
|
|
|
161
|
-
/// The
|
|
162
|
-
///
|
|
163
|
-
|
|
161
|
+
/// The identity of the process whose default backend the core currently holds
|
|
162
|
+
/// ([`ProcessIdentity::to_bits`]: pid and fork generation in one word, so it
|
|
163
|
+
/// is read and written whole). 0 = no native operation has touched the
|
|
164
|
+
/// backend in this process lineage.
|
|
165
|
+
static BACKEND_OWNER: AtomicU64 = AtomicU64::new(0);
|
|
164
166
|
|
|
165
167
|
static REBUILD_LOCK: Mutex<()> = Mutex::new(());
|
|
166
168
|
|
|
@@ -183,7 +185,7 @@ fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
|
|
|
183
185
|
/// above).
|
|
184
186
|
pub fn ensure_this_process_backend() -> Result<(), Error> {
|
|
185
187
|
// The Ruby exception is built here, after the locks are released.
|
|
186
|
-
adopt_backend(
|
|
188
|
+
adopt_backend(ProcessIdentity::current(), None)
|
|
187
189
|
.map(|_| ())
|
|
188
190
|
.map_err(error::to_ruby)
|
|
189
191
|
}
|
|
@@ -204,23 +206,24 @@ pub fn ensure_this_process_backend() -> Result<(), Error> {
|
|
|
204
206
|
/// Returns `Some(inherited selection)` when `replacement` was installed here
|
|
205
207
|
/// (the caller has nothing left to install), `None` when the caller proceeds
|
|
206
208
|
/// as in any process that already owns its backend. On an error nothing has
|
|
207
|
-
/// changed, `
|
|
209
|
+
/// changed, `BACKEND_OWNER` included, so the next call tries again.
|
|
208
210
|
fn adopt_backend(
|
|
209
|
-
cur:
|
|
211
|
+
cur: ProcessIdentity,
|
|
210
212
|
replacement: Option<&Selection>,
|
|
211
213
|
) -> Result<Option<Selection>, MicrosandboxError> {
|
|
212
|
-
|
|
214
|
+
let cur_bits = cur.to_bits();
|
|
215
|
+
if BACKEND_OWNER.load(Ordering::Acquire) == cur_bits {
|
|
213
216
|
return Ok(None);
|
|
214
217
|
}
|
|
215
218
|
let _rebuild = lock(&REBUILD_LOCK);
|
|
216
|
-
let seen =
|
|
217
|
-
if seen ==
|
|
219
|
+
let seen = BACKEND_OWNER.load(Ordering::Acquire);
|
|
220
|
+
if seen == cur_bits {
|
|
218
221
|
return Ok(None);
|
|
219
222
|
}
|
|
220
223
|
if seen == 0 {
|
|
221
224
|
// Nothing was inherited; the core resolves its default lazily, in this
|
|
222
225
|
// process, on first use.
|
|
223
|
-
|
|
226
|
+
BACKEND_OWNER.store(cur_bits, Ordering::Release);
|
|
224
227
|
return Ok(None);
|
|
225
228
|
}
|
|
226
229
|
|
|
@@ -233,14 +236,14 @@ fn adopt_backend(
|
|
|
233
236
|
// The inherited default must never be dropped (see above).
|
|
234
237
|
std::mem::forget(microsandbox::swap_default_backend(fresh));
|
|
235
238
|
// Neither may the backends that open `with_backend` scopes saved for their
|
|
236
|
-
// restore. The entries themselves stay —
|
|
239
|
+
// restore. The entries themselves stay — owner and selection — so a scope
|
|
237
240
|
// the parent opened can still be closed here (see `pop_default_backend`).
|
|
238
241
|
for scope in lock(backend_scopes()).values_mut() {
|
|
239
242
|
std::mem::forget(scope.previous.take());
|
|
240
243
|
}
|
|
241
244
|
let inherited =
|
|
242
245
|
replacement.map(|selection| std::mem::replace(&mut *lock(&SELECTION), selection.clone()));
|
|
243
|
-
|
|
246
|
+
BACKEND_OWNER.store(cur_bits, Ordering::Release);
|
|
244
247
|
Ok(inherited)
|
|
245
248
|
}
|
|
246
249
|
|
|
@@ -261,7 +264,7 @@ fn set_default_backend(
|
|
|
261
264
|
// A forked child that starts by selecting a backend: the inherited default
|
|
262
265
|
// is forgotten and this selection installed in its place, without ever
|
|
263
266
|
// rebuilding the inherited one.
|
|
264
|
-
if adopt_backend(
|
|
267
|
+
if adopt_backend(ProcessIdentity::current(), Some(&selection))
|
|
265
268
|
.map_err(error::to_ruby)?
|
|
266
269
|
.is_some()
|
|
267
270
|
{
|
|
@@ -283,7 +286,7 @@ static BACKEND_SCOPES: OnceLock<Mutex<HashMap<u32, Scope>>> = OnceLock::new();
|
|
|
283
286
|
/// What an open `with_backend` scope restores when it closes.
|
|
284
287
|
struct Scope {
|
|
285
288
|
/// The process that opened the scope.
|
|
286
|
-
|
|
289
|
+
owner: ProcessIdentity,
|
|
287
290
|
/// The backend that was the default before the push, kept to be put back
|
|
288
291
|
/// by a pop in the same process. `None` when there is no such backend of
|
|
289
292
|
/// this process's own: a forked child took it out to leak it, or the push
|
|
@@ -308,7 +311,7 @@ fn push_default_backend(
|
|
|
308
311
|
api_key: Option<String>,
|
|
309
312
|
profile: Option<String>,
|
|
310
313
|
) -> Result<u32, Error> {
|
|
311
|
-
let cur =
|
|
314
|
+
let cur = ProcessIdentity::current();
|
|
312
315
|
let selection = Selection::Explicit {
|
|
313
316
|
kind,
|
|
314
317
|
url,
|
|
@@ -332,7 +335,7 @@ fn push_default_backend(
|
|
|
332
335
|
lock(backend_scopes()).insert(
|
|
333
336
|
token,
|
|
334
337
|
Scope {
|
|
335
|
-
|
|
338
|
+
owner: cur,
|
|
336
339
|
previous,
|
|
337
340
|
selection: before,
|
|
338
341
|
},
|
|
@@ -352,7 +355,7 @@ fn push_default_backend(
|
|
|
352
355
|
/// is a state the caller can describe and leave with `set_default_backend`;
|
|
353
356
|
/// a token nobody can retry (`with_backend` never hands it out) would not be.
|
|
354
357
|
fn pop_default_backend(token: u32) -> Result<(), Error> {
|
|
355
|
-
let cur =
|
|
358
|
+
let cur = ProcessIdentity::current();
|
|
356
359
|
// (The registry lock is not held while an exception is built.)
|
|
357
360
|
let Some(scope) = lock(backend_scopes()).remove(&token) else {
|
|
358
361
|
return Err(error::base_error("unknown backend scope token"));
|
|
@@ -384,7 +387,7 @@ fn pop_default_backend(token: u32) -> Result<(), Error> {
|
|
|
384
387
|
|
|
385
388
|
match scope.previous {
|
|
386
389
|
// Opened and closed in the same process: put the saved backend back.
|
|
387
|
-
Some(previous) if scope.
|
|
390
|
+
Some(previous) if scope.owner == cur => microsandbox::set_default_backend(previous),
|
|
388
391
|
// No backend of this process's own was saved (see `Scope::previous`):
|
|
389
392
|
// restore one built here from how the saved one had been chosen. On
|
|
390
393
|
// failure the temporary backend stays installed (see above). The
|
|
@@ -393,8 +396,9 @@ fn pop_default_backend(token: u32) -> Result<(), Error> {
|
|
|
393
396
|
// forked child ever drops a backend it might have inherited. That
|
|
394
397
|
// leaks at most one backend per scope closed this way.
|
|
395
398
|
previous => {
|
|
396
|
-
// `None`
|
|
397
|
-
//
|
|
399
|
+
// Always `None` here: a process that is not the scope's opener
|
|
400
|
+
// took the saved backend out when it adopted (`adopt_backend`).
|
|
401
|
+
// An inherited `Arc` would be leaked either way.
|
|
398
402
|
std::mem::forget(previous);
|
|
399
403
|
let fresh = scope.selection.build().map_err(error::to_ruby)?;
|
|
400
404
|
std::mem::forget(microsandbox::swap_default_backend(fresh));
|
|
@@ -9,6 +9,8 @@ use magnus::{value::ReprValue, Error, ExceptionClass, Module, RClass, RModule, R
|
|
|
9
9
|
use microsandbox::{AgentClientError, MicrosandboxError, Operation, UnsupportedReason};
|
|
10
10
|
use microsandbox_network::policy::BuildError;
|
|
11
11
|
|
|
12
|
+
use crate::runtime::ruby;
|
|
13
|
+
|
|
12
14
|
/// The Ruby class (relative to the `Microsandbox` module) for a core error.
|
|
13
15
|
/// `"Error"` is the base class; anything else is a named subclass.
|
|
14
16
|
fn class_name(err: &MicrosandboxError) -> &'static str {
|
|
@@ -88,16 +90,16 @@ fn exception_class(ruby: &Ruby, name: &str) -> Option<ExceptionClass> {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
/// Convert a core error into a Ruby exception, preserving the typed class.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
///
|
|
94
|
+
/// Every constructor in this module builds a Ruby exception class, so it must
|
|
95
|
+
/// run on a Ruby thread holding the GVL: in a bound method, never inside a
|
|
96
|
+
/// `block_on` future or a `nogvl` callback (those return the core error and
|
|
97
|
+
/// get mapped after `block_on` returns). There is no exception class to build
|
|
98
|
+
/// without the GVL, so [`ruby`] panics instead of handing out a handle that
|
|
99
|
+
/// isn't backed by one.
|
|
94
100
|
pub fn to_ruby(err: MicrosandboxError) -> Error {
|
|
95
101
|
let message = err.to_string();
|
|
96
|
-
let ruby =
|
|
97
|
-
Ok(ruby) => ruby,
|
|
98
|
-
// Not on a Ruby thread (should never happen from a bound method).
|
|
99
|
-
Err(_) => return Error::new(magnus::exception::runtime_error(), message),
|
|
100
|
-
};
|
|
102
|
+
let ruby = ruby();
|
|
101
103
|
|
|
102
104
|
// `Unsupported` gets a Ruby-idiom message (`sandbox.kill` instead of
|
|
103
105
|
// `Sandbox::kill`) plus structured `operation` / `hint` attributes on the
|
|
@@ -116,15 +118,8 @@ pub fn to_ruby(err: MicrosandboxError) -> Error {
|
|
|
116
118
|
/// backend but have no SDK [`Operation`] (Ruby-only diagnostic hooks such as
|
|
117
119
|
/// `Microsandbox.runtime_path`). `name` is the Ruby-facing API name. Mirrors
|
|
118
120
|
/// the Python SDK's name-based `local_only` helper.
|
|
119
|
-
#[allow(deprecated)]
|
|
120
121
|
pub fn local_only(name: &str) -> Error {
|
|
121
|
-
|
|
122
|
-
Ok(ruby) => unsupported_error(&ruby, name, "use a local backend"),
|
|
123
|
-
Err(_) => Error::new(
|
|
124
|
-
magnus::exception::runtime_error(),
|
|
125
|
-
format!("{name} is not supported by this backend: use a local backend"),
|
|
126
|
-
),
|
|
127
|
-
}
|
|
122
|
+
unsupported_error(&ruby(), name, "use a local backend")
|
|
128
123
|
}
|
|
129
124
|
|
|
130
125
|
/// Build a `Microsandbox::UnsupportedError` carrying the rendered message and
|
|
@@ -191,32 +186,37 @@ fn camel_to_snake(name: &str) -> String {
|
|
|
191
186
|
|
|
192
187
|
/// A plain `Microsandbox::Error` (base) with a custom message — used for
|
|
193
188
|
/// binding-level validation errors that have no core counterpart.
|
|
194
|
-
#[allow(deprecated)]
|
|
195
189
|
pub fn base_error(message: impl Into<String>) -> Error {
|
|
196
190
|
let message = message.into();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
Err(_) => Error::new(magnus::exception::runtime_error(), message),
|
|
191
|
+
let ruby = ruby();
|
|
192
|
+
match exception_class(&ruby, "Error") {
|
|
193
|
+
Some(class) => Error::new(class, message),
|
|
194
|
+
None => Error::new(ruby.exception_runtime_error(), message),
|
|
203
195
|
}
|
|
204
196
|
}
|
|
205
197
|
|
|
206
|
-
/// `Microsandbox::ForkedHandleError`: a handle created in process
|
|
207
|
-
/// used in
|
|
208
|
-
/// `class` is the Ruby class users know the handle
|
|
209
|
-
|
|
198
|
+
/// `Microsandbox::ForkedHandleError`: a handle created in the process with pid
|
|
199
|
+
/// `born` was used in a fork descendant with pid `cur` (see
|
|
200
|
+
/// `runtime::ForkGuarded`). `class` is the Ruby class users know the handle
|
|
201
|
+
/// by. The two pids are equal when the descendant was given its ancestor's
|
|
202
|
+
/// pid again (they differ in fork generation, which is not shown): that
|
|
203
|
+
/// ancestor has exited, so the message must not claim two processes or point
|
|
204
|
+
/// at the original.
|
|
210
205
|
pub fn forked_handle(class: &str, born: u32, cur: u32) -> Error {
|
|
211
|
-
let message =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
206
|
+
let message = if born == cur {
|
|
207
|
+
format!(
|
|
208
|
+
"{class} was created in an earlier process that also had pid {born} and cannot be \
|
|
209
|
+
used in this forked process; create a new handle after fork"
|
|
210
|
+
)
|
|
211
|
+
} else {
|
|
212
|
+
format!(
|
|
213
|
+
"{class} was created in process {born} and cannot be used in forked process {cur}; \
|
|
214
|
+
create a new handle after fork (the original keeps working in process {born})"
|
|
215
|
+
)
|
|
216
|
+
};
|
|
217
|
+
let ruby = ruby();
|
|
218
|
+
match exception_class(&ruby, "ForkedHandleError") {
|
|
219
|
+
Some(class) => Error::new(class, message),
|
|
220
|
+
None => Error::new(ruby.exception_runtime_error(), message),
|
|
221
221
|
}
|
|
222
222
|
}
|
data/ext/microsandbox/src/lib.rs
CHANGED
|
@@ -46,8 +46,10 @@ fn all_sandbox_metrics() -> Result<RHash, Error> {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/// Download and install the `msb` runtime + `libkrunfw` into `~/.microsandbox`.
|
|
49
|
+
/// Never touches the backend, so a forked child does not take the inherited
|
|
50
|
+
/// one over here (see `runtime::block_on_runtime_install`).
|
|
49
51
|
fn install() -> Result<(), Error> {
|
|
50
|
-
runtime::
|
|
52
|
+
runtime::block_on_runtime_install(microsandbox::setup::install())?.map_err(error::to_ruby)
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/// Customizable install via the core `Setup` builder. `opts`: base_dir (install
|
|
@@ -84,7 +86,8 @@ fn setup(opts: RHash) -> Result<(), Error> {
|
|
|
84
86
|
.force(force)
|
|
85
87
|
.build(),
|
|
86
88
|
};
|
|
87
|
-
|
|
89
|
+
// Backend-free, like `install`.
|
|
90
|
+
runtime::block_on_runtime_install(setup.install())?.map_err(error::to_ruby)
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
/// Whether the `msb` runtime + `libkrunfw` are installed and resolvable.
|
|
@@ -119,10 +122,52 @@ fn resolved_msb_path() -> Result<String, Error> {
|
|
|
119
122
|
Ok(path.to_string_lossy().into_owned())
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
/// This process's fork generation (see `runtime::ProcessIdentity`): how many
|
|
126
|
+
/// `fork`s led from the process that loaded the extension to this one. A
|
|
127
|
+
/// diagnostic for the fork specs, not part of the Ruby API (nothing in
|
|
128
|
+
/// `lib/` calls it, and `Native` is the extension's internal namespace).
|
|
129
|
+
fn fork_generation() -> u32 {
|
|
130
|
+
runtime::ProcessIdentity::current().generation()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Whether two given identities are the same process, by the two comparisons
|
|
134
|
+
/// every ownership decision uses: `PartialEq` (the runtime cell, handles,
|
|
135
|
+
/// `with_backend` scopes) and the packed word (the backend record). Returns
|
|
136
|
+
/// `[equal, same_bits]`. A spec-only diagnostic, like `fork_generation`:
|
|
137
|
+
/// pid reuse cannot be staged on demand, so this is where the specs pin that
|
|
138
|
+
/// both the pid and the generation take part. Pure: no state is read or
|
|
139
|
+
/// changed.
|
|
140
|
+
fn fork_identity_equal(pid_a: u32, gen_a: u32, pid_b: u32, gen_b: u32) -> (bool, bool) {
|
|
141
|
+
let a = runtime::ProcessIdentity::from_parts(pid_a, gen_a);
|
|
142
|
+
let b = runtime::ProcessIdentity::from_parts(pid_b, gen_b);
|
|
143
|
+
(a == b, a.to_bits() == b.to_bits())
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Run a handle's real ownership check (`ForkGuarded::get_in`) for a handle
|
|
147
|
+
/// created by the process (`born_pid`, `born_gen`) and used in (`cur_pid`,
|
|
148
|
+
/// `cur_gen`): `nil` when it is allowed, the `ForkedHandleError` that `get`
|
|
149
|
+
/// would raise otherwise — including the equal-pid wording. Spec-only and
|
|
150
|
+
/// pure, like `fork_identity_equal`; the guard holds no payload.
|
|
151
|
+
fn fork_identity_guard(
|
|
152
|
+
born_pid: u32,
|
|
153
|
+
born_gen: u32,
|
|
154
|
+
cur_pid: u32,
|
|
155
|
+
cur_gen: u32,
|
|
156
|
+
) -> Result<(), Error> {
|
|
157
|
+
let born = runtime::ProcessIdentity::from_parts(born_pid, born_gen);
|
|
158
|
+
let cur = runtime::ProcessIdentity::from_parts(cur_pid, cur_gen);
|
|
159
|
+
let guard = runtime::ForkGuarded::owned_by(born, "Microsandbox::VolumeFs", ());
|
|
160
|
+
guard.get_in(cur).map(|_| ())
|
|
161
|
+
}
|
|
162
|
+
|
|
122
163
|
/// magnus entry point. RubyGems loads this via `require "microsandbox/microsandbox_rb"`,
|
|
123
164
|
/// which calls `Init_microsandbox_rb` (matching the cdylib `[lib] name`).
|
|
124
165
|
#[magnus::init]
|
|
125
166
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
167
|
+
// Before anything can fork: from here on every fork's child gets a fork
|
|
168
|
+
// generation one higher than its parent's (see `runtime::ProcessIdentity`).
|
|
169
|
+
runtime::register_fork_handler();
|
|
170
|
+
|
|
126
171
|
let module = ruby.define_module("Microsandbox")?;
|
|
127
172
|
let native = module.define_module("Native")?;
|
|
128
173
|
|
|
@@ -137,6 +182,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
137
182
|
)?;
|
|
138
183
|
native.define_singleton_method("resolved_msb_path", function!(resolved_msb_path, 0))?;
|
|
139
184
|
native.define_singleton_method("all_sandbox_metrics", function!(all_sandbox_metrics, 0))?;
|
|
185
|
+
native.define_singleton_method("fork_generation", function!(fork_generation, 0))?;
|
|
186
|
+
native.define_singleton_method("fork_identity_equal", function!(fork_identity_equal, 4))?;
|
|
187
|
+
native.define_singleton_method("fork_identity_guard", function!(fork_identity_guard, 4))?;
|
|
140
188
|
|
|
141
189
|
backend::define(ruby, &native)?;
|
|
142
190
|
sandbox::define(ruby, &native)?;
|