kobako 0.27.0 → 0.29.0
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +241 -0
- data/Cargo.lock +132 -126
- data/README.md +240 -147
- data/ROADMAP.md +28 -22
- data/SECURITY.md +2 -2
- data/crates/kobako-runtime/CHANGELOG.md +117 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +16 -10
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +12 -2
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +43 -17
- data/crates/kobako-runtime/src/snapshot.rs +33 -15
- data/crates/kobako-runtime/src/yielder.rs +14 -6
- data/crates/kobako-transport/CHANGELOG.md +45 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +21 -12
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +103 -61
- data/crates/kobako-transport/src/envelope/request.rs +305 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
- data/crates/kobako-wasmtime/Cargo.toml +23 -16
- data/crates/kobako-wasmtime/README.md +6 -4
- data/crates/kobako-wasmtime/src/abi.rs +67 -36
- data/crates/kobako-wasmtime/src/ambient.rs +64 -5
- data/crates/kobako-wasmtime/src/cache.rs +204 -127
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +69 -15
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
- data/crates/kobako-wasmtime/src/driver.rs +174 -125
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
- data/crates/kobako-wasmtime/src/invocation.rs +84 -412
- data/crates/kobako-wasmtime/src/io.rs +89 -0
- data/crates/kobako-wasmtime/src/lib.rs +7 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +66 -59
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +177 -113
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +11 -13
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +190 -312
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +29 -0
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +17 -60
- data/lib/kobako/catalog/snippets.rb +41 -69
- data/lib/kobako/catalog.rb +13 -10
- data/lib/kobako/codec/decoder.rb +10 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +21 -11
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +10 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +83 -117
- data/lib/kobako/errors.rb +57 -132
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +25 -55
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +128 -137
- data/lib/kobako/sandbox_options.rb +71 -90
- data/lib/kobako/snippet/bytecode.rb +11 -0
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +2 -10
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +127 -206
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +10 -17
- data/lib/kobako/transport/reflection.rb +108 -63
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +20 -94
- data/lib/kobako/transport.rb +4 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +13 -0
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +3 -5
- data/sig/kobako/catalog/snippets.rbs +5 -5
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +4 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +15 -8
- data/sig/kobako/errors.rbs +10 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -45
- data/sig/kobako/sandbox.rbs +11 -1
- data/sig/kobako/sandbox_options.rbs +25 -7
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +15 -19
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +19 -4
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +5 -9
- data/sig/kobako/transport.rbs +9 -8
- metadata +75 -10
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/lib/kobako/snippet/binary.rb +0 -26
- data/rust-toolchain.toml +0 -15
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
//! A Filesystem a Host App writes in Ruby: any object answering `open`
|
|
2
|
+
//! and `stat`, asked through `gvl::reenter` on every call the guest makes.
|
|
3
|
+
//!
|
|
4
|
+
//! Its methods take the names and shapes of Ruby's own `File`, `Dir`, and
|
|
5
|
+
//! `IO`, so a Host App can hand an open call on to a real `::File`. A
|
|
6
|
+
//! change method the object lacks answers as read-only; any other method
|
|
7
|
+
//! it lacks answers as unsupported. A file's `EOFError` from `pread` is
|
|
8
|
+
//! its end, an `Errno` the guest has a class for reaches the guest as
|
|
9
|
+
//! that code, and any other `StandardError` reaches the guest as `EIO`.
|
|
10
|
+
|
|
11
|
+
use magnus::value::{Opaque, ReprValue};
|
|
12
|
+
use magnus::{kwargs, prelude::*, Error as MagnusError, RClass, RModule, RString, Ruby, Value};
|
|
13
|
+
|
|
14
|
+
use std::sync::Arc;
|
|
15
|
+
|
|
16
|
+
use kobako_runtime::filesystem::{
|
|
17
|
+
DirEntry, File, FileType, Filesystem, FilesystemError, Hold, Metadata, OpenOptions, Rename,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
use crate::runtime::pinned_value::PinnedValue;
|
|
21
|
+
use crate::runtime::{escape, gvl};
|
|
22
|
+
|
|
23
|
+
/// The Ruby object behind a Mount or a Layer. Whatever holds it on the
|
|
24
|
+
/// Ruby side keeps it alive and in place: the invocation pinning its
|
|
25
|
+
/// Mounts, or the Overlay marking its Layers.
|
|
26
|
+
pub(crate) struct RubyFilesystem {
|
|
27
|
+
object: Opaque<Value>,
|
|
28
|
+
/// The device a stat without a `dev` names, the object's own.
|
|
29
|
+
dev: u64,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl RubyFilesystem {
|
|
33
|
+
/// The object as a Filesystem, when it answers `open` and `stat`.
|
|
34
|
+
pub(crate) fn from_value(value: Value) -> Option<Self> {
|
|
35
|
+
let answers = |name| value.respond_to(name, false).unwrap_or(false);
|
|
36
|
+
(answers("open") && answers("stat")).then(|| Self {
|
|
37
|
+
object: Opaque::from(value),
|
|
38
|
+
dev: Metadata::unique_device(),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Ask `object` its `name` method with the GVL held, `answer` making the
|
|
44
|
+
/// call and reading what it returns before Ruby runs anything else. An
|
|
45
|
+
/// object lacking the method fails as `missing`.
|
|
46
|
+
fn ask<T>(
|
|
47
|
+
object: Opaque<Value>,
|
|
48
|
+
name: &str,
|
|
49
|
+
missing: FilesystemError,
|
|
50
|
+
answer: impl FnOnce(&Ruby, Value) -> Result<T, MagnusError>,
|
|
51
|
+
) -> Result<T, FilesystemError> {
|
|
52
|
+
ask_or(object, name, Err(missing), answer)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// As `ask`, an object lacking the method answering `missing`.
|
|
56
|
+
fn ask_or<T>(
|
|
57
|
+
object: Opaque<Value>,
|
|
58
|
+
name: &str,
|
|
59
|
+
missing: Result<T, FilesystemError>,
|
|
60
|
+
answer: impl FnOnce(&Ruby, Value) -> Result<T, MagnusError>,
|
|
61
|
+
) -> Result<T, FilesystemError> {
|
|
62
|
+
gvl::reenter(|| {
|
|
63
|
+
let ruby = Ruby::get().expect("Ruby handle unavailable in a Filesystem call");
|
|
64
|
+
let object: Value = ruby.get_inner(object);
|
|
65
|
+
if !object.respond_to(name, false).unwrap_or(false) {
|
|
66
|
+
return missing;
|
|
67
|
+
}
|
|
68
|
+
answer(&ruby, object).map_err(|err| match escape::hold(&ruby, err) {
|
|
69
|
+
Err(err) => to_error(err),
|
|
70
|
+
// The invocation ends before the guest reads this.
|
|
71
|
+
Ok(()) => FilesystemError::Other(String::new()),
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Ask `object` a change, answering nothing more than whether it was made.
|
|
77
|
+
fn change(
|
|
78
|
+
object: Opaque<Value>,
|
|
79
|
+
name: &str,
|
|
80
|
+
args: impl magnus::ArgList,
|
|
81
|
+
missing: FilesystemError,
|
|
82
|
+
) -> Result<(), FilesystemError> {
|
|
83
|
+
ask(object, name, missing, |_, object| {
|
|
84
|
+
object.funcall::<_, _, Value>(name, args).map(drop)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Ask `object` a change it may lack, an object lacking `name` having
|
|
89
|
+
/// nothing of its own to change, while one that answers and refuses still
|
|
90
|
+
/// fails, whatever `Errno` it raises.
|
|
91
|
+
fn change_if_answered(object: Opaque<Value>, name: &str) -> Result<(), FilesystemError> {
|
|
92
|
+
ask_or(object, name, Ok(()), |_, object| {
|
|
93
|
+
object.funcall::<_, _, Value>(name, ()).map(drop)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// The FilesystemError the nearest `Errno` class of `err` stands for, by
|
|
98
|
+
/// its POSIX name; anything else is `Other`, which reaches the guest as
|
|
99
|
+
/// `EIO`.
|
|
100
|
+
fn to_error(err: MagnusError) -> FilesystemError {
|
|
101
|
+
let named = err.value().and_then(|exception| {
|
|
102
|
+
exception
|
|
103
|
+
.class()
|
|
104
|
+
.ancestors()
|
|
105
|
+
.into_iter()
|
|
106
|
+
.find_map(|module| {
|
|
107
|
+
let name: Option<String> = module.funcall("name", ()).ok()?;
|
|
108
|
+
FilesystemError::from_posix_name(name?.strip_prefix("Errno::")?)
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
named.unwrap_or_else(|| FilesystemError::Other(err.to_string()))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// `File::Constants` flags for `options`, read from Ruby since their
|
|
115
|
+
/// values differ by platform.
|
|
116
|
+
fn to_flags(ruby: &Ruby, options: &OpenOptions) -> Result<i64, MagnusError> {
|
|
117
|
+
let constants: RModule = ruby
|
|
118
|
+
.class_object()
|
|
119
|
+
.const_get::<_, RClass>("File")?
|
|
120
|
+
.const_get("Constants")?;
|
|
121
|
+
let flag = |name| constants.const_get::<_, i64>(name);
|
|
122
|
+
let mut flags = match (options.read, options.write || options.truncate) {
|
|
123
|
+
(_, false) => flag("RDONLY")?,
|
|
124
|
+
(false, true) => flag("WRONLY")?,
|
|
125
|
+
(true, true) => flag("RDWR")?,
|
|
126
|
+
};
|
|
127
|
+
if options.create || options.create_new {
|
|
128
|
+
flags |= flag("CREAT")?;
|
|
129
|
+
}
|
|
130
|
+
if options.create_new {
|
|
131
|
+
flags |= flag("EXCL")?;
|
|
132
|
+
}
|
|
133
|
+
if options.truncate {
|
|
134
|
+
flags |= flag("TRUNC")?;
|
|
135
|
+
}
|
|
136
|
+
if options.nofollow {
|
|
137
|
+
flags |= flag("NOFOLLOW")?;
|
|
138
|
+
}
|
|
139
|
+
Ok(flags)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn join(dir: &str, name: &str) -> String {
|
|
143
|
+
if dir.is_empty() {
|
|
144
|
+
name.to_string()
|
|
145
|
+
} else {
|
|
146
|
+
format!("{dir}/{name}")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
impl Filesystem for RubyFilesystem {
|
|
151
|
+
fn open(&self, path: &str, options: &OpenOptions) -> Result<Box<dyn File>, FilesystemError> {
|
|
152
|
+
ask(
|
|
153
|
+
self.object,
|
|
154
|
+
"open",
|
|
155
|
+
FilesystemError::Unsupported,
|
|
156
|
+
|ruby, fs| {
|
|
157
|
+
let file: Value = fs.funcall("open", (path, to_flags(ruby, options)?))?;
|
|
158
|
+
Ok(Box::new(RubyFile::pin(ruby, file)) as Box<dyn File>)
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
|
|
164
|
+
let own_dev = self.dev;
|
|
165
|
+
ask(
|
|
166
|
+
self.object,
|
|
167
|
+
"stat",
|
|
168
|
+
FilesystemError::Unsupported,
|
|
169
|
+
|_, fs| {
|
|
170
|
+
let stat: Value = fs.funcall("stat", (path,))?;
|
|
171
|
+
let metadata = if stat.funcall::<_, _, bool>("directory?", ())? {
|
|
172
|
+
Metadata::new(FileType::Directory, 0)
|
|
173
|
+
} else {
|
|
174
|
+
Metadata::new(FileType::File, stat.funcall("size", ())?)
|
|
175
|
+
};
|
|
176
|
+
// A stat names its identity as `File::Stat` does; without a
|
|
177
|
+
// `dev` the Filesystem stands for a device of its own, so it
|
|
178
|
+
// shares an identity with no other Filesystem.
|
|
179
|
+
let dev = if stat.respond_to("dev", false)? {
|
|
180
|
+
stat.funcall("dev", ())?
|
|
181
|
+
} else {
|
|
182
|
+
own_dev
|
|
183
|
+
};
|
|
184
|
+
Ok(metadata.with_identity(dev, stat.funcall("ino", ())?))
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// A Ruby object answers `stat` alone, so it holds no links.
|
|
190
|
+
fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
|
|
191
|
+
self.metadata(path)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// A Ruby object matches names exactly, as its contract asks.
|
|
195
|
+
fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
|
|
196
|
+
Ok(path.to_string())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// A Ruby object holds no links to follow.
|
|
200
|
+
fn canonicalize_beneath(&self, _base: &str, path: &str) -> Result<String, FilesystemError> {
|
|
201
|
+
Ok(path.to_string())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// A Ruby object holds no links a Policy needs kept from being followed.
|
|
205
|
+
fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
|
|
206
|
+
None
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// A Ruby object keeps an `ino` its own while open, as its contract
|
|
210
|
+
/// asks, so nothing is held.
|
|
211
|
+
fn hold_directory(&self, _path: &str) -> Result<Option<Hold>, FilesystemError> {
|
|
212
|
+
Ok(None)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
216
|
+
let names: Vec<String> = ask(
|
|
217
|
+
self.object,
|
|
218
|
+
"children",
|
|
219
|
+
FilesystemError::Unsupported,
|
|
220
|
+
|_, fs| fs.funcall("children", (path,)),
|
|
221
|
+
)?;
|
|
222
|
+
names
|
|
223
|
+
.into_iter()
|
|
224
|
+
.map(|name| {
|
|
225
|
+
let file_type = self.metadata(&join(path, &name))?.file_type;
|
|
226
|
+
Ok(DirEntry::new(name, file_type))
|
|
227
|
+
})
|
|
228
|
+
.collect()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
232
|
+
change(
|
|
233
|
+
self.object,
|
|
234
|
+
"mkdir",
|
|
235
|
+
(path,),
|
|
236
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
241
|
+
change(
|
|
242
|
+
self.object,
|
|
243
|
+
"rmdir",
|
|
244
|
+
(path,),
|
|
245
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
|
|
250
|
+
change(
|
|
251
|
+
self.object,
|
|
252
|
+
"unlink",
|
|
253
|
+
(path,),
|
|
254
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// `rename(from, to, directory:)`: `true` moves only a directory,
|
|
259
|
+
/// `false` only a file, `nil` either. What stands at `from` meanwhile
|
|
260
|
+
/// is the object's to keep still.
|
|
261
|
+
fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
|
|
262
|
+
// A move the keyword cannot tell the object is refused rather than
|
|
263
|
+
// widened into one of either kind.
|
|
264
|
+
let directory = match rename {
|
|
265
|
+
Rename::Any => None,
|
|
266
|
+
Rename::File => Some(false),
|
|
267
|
+
Rename::Directory => Some(true),
|
|
268
|
+
_ => return Err(FilesystemError::Unsupported),
|
|
269
|
+
};
|
|
270
|
+
change(
|
|
271
|
+
self.object,
|
|
272
|
+
"rename",
|
|
273
|
+
(from, to, kwargs!("directory" => directory)),
|
|
274
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Every Invocation shares the Ruby object.
|
|
279
|
+
fn for_invocation(&self) -> Option<Arc<dyn Filesystem>> {
|
|
280
|
+
None
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// One file the Ruby object opened, which only the guest's descriptor
|
|
285
|
+
/// refers to.
|
|
286
|
+
struct RubyFile {
|
|
287
|
+
file: PinnedValue,
|
|
288
|
+
closed: bool,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
impl RubyFile {
|
|
292
|
+
fn pin(ruby: &Ruby, file: Value) -> Self {
|
|
293
|
+
Self {
|
|
294
|
+
file: PinnedValue::new(ruby, file),
|
|
295
|
+
closed: false,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// Ask the file to close, a file lacking `close` having nothing to
|
|
300
|
+
/// release.
|
|
301
|
+
fn release(&mut self) -> Result<(), FilesystemError> {
|
|
302
|
+
self.closed = true;
|
|
303
|
+
change_if_answered(self.file.get(), "close")
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
impl File for RubyFile {
|
|
308
|
+
fn read_at(&mut self, buf: &mut [u8], offset: u64) -> Result<usize, FilesystemError> {
|
|
309
|
+
ask(
|
|
310
|
+
self.file.get(),
|
|
311
|
+
"pread",
|
|
312
|
+
FilesystemError::Unsupported,
|
|
313
|
+
|ruby, file| {
|
|
314
|
+
// `IO#pread` raises `EOFError` at the end of a file, which reads as nothing.
|
|
315
|
+
let read: Option<RString> = match file.funcall("pread", (buf.len(), offset)) {
|
|
316
|
+
Err(err) if err.is_kind_of(ruby.exception_eof_error()) => None,
|
|
317
|
+
read => read?,
|
|
318
|
+
};
|
|
319
|
+
let Some(read) = read else { return Ok(0) };
|
|
320
|
+
// SAFETY: the bytes are copied out before Ruby runs again.
|
|
321
|
+
let bytes = unsafe { read.as_slice() };
|
|
322
|
+
let len = bytes.len().min(buf.len());
|
|
323
|
+
buf[..len].copy_from_slice(&bytes[..len]);
|
|
324
|
+
Ok(len)
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn write_at(&mut self, buf: &[u8], offset: u64) -> Result<usize, FilesystemError> {
|
|
330
|
+
ask(
|
|
331
|
+
self.file.get(),
|
|
332
|
+
"pwrite",
|
|
333
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
334
|
+
|ruby, file| file.funcall("pwrite", (ruby.str_from_slice(buf), offset)),
|
|
335
|
+
)
|
|
336
|
+
.and_then(|written: usize| match written {
|
|
337
|
+
written if written <= buf.len() => Ok(written),
|
|
338
|
+
written => Err(FilesystemError::Other(format!(
|
|
339
|
+
"pwrite answered {written} bytes written of the {} it was handed",
|
|
340
|
+
buf.len()
|
|
341
|
+
))),
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// The file's `append` writes all of `buf` at its end in one step and
|
|
346
|
+
/// answers where the file then ends, as `IO#pos` does after a write to
|
|
347
|
+
/// a file opened to append. A file lacking it cannot append, since its
|
|
348
|
+
/// `size` and `pwrite` are two steps another writer can land between.
|
|
349
|
+
fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
|
|
350
|
+
ask(
|
|
351
|
+
self.file.get(),
|
|
352
|
+
"append",
|
|
353
|
+
FilesystemError::Unsupported,
|
|
354
|
+
|ruby, file| file.funcall("append", (ruby.str_from_slice(buf),)),
|
|
355
|
+
)
|
|
356
|
+
.and_then(|end: u64| match end.checked_sub(buf.len() as u64) {
|
|
357
|
+
Some(at) => Ok((at, buf.len())),
|
|
358
|
+
None => Err(FilesystemError::Other(format!(
|
|
359
|
+
"append answered the file ending at {end}, before the {} bytes it wrote",
|
|
360
|
+
buf.len()
|
|
361
|
+
))),
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
fn metadata(&self) -> Result<Metadata, FilesystemError> {
|
|
366
|
+
ask(
|
|
367
|
+
self.file.get(),
|
|
368
|
+
"size",
|
|
369
|
+
FilesystemError::Unsupported,
|
|
370
|
+
|_, file| Ok(Metadata::new(FileType::File, file.funcall("size", ())?)),
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
fn set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
|
|
375
|
+
change(
|
|
376
|
+
self.file.get(),
|
|
377
|
+
"truncate",
|
|
378
|
+
(len,),
|
|
379
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/// A file lacking `fsync` has nothing of its own to flush.
|
|
384
|
+
fn sync(&mut self) -> Result<(), FilesystemError> {
|
|
385
|
+
change_if_answered(self.file.get(), "fsync")
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn close(mut self: Box<Self>) -> Result<(), FilesystemError> {
|
|
389
|
+
self.release()
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/// A file the guest never closed is closed here, its failure discarded.
|
|
394
|
+
impl Drop for RubyFile {
|
|
395
|
+
fn drop(&mut self) {
|
|
396
|
+
if !self.closed {
|
|
397
|
+
let _ = self.release();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//! The Ruby face of the Filesystem: `Kobako::Filesystem::Memory`,
|
|
2
|
+
//! `Temporary`, `Directory`, and `Overlay`, each standing for the
|
|
3
|
+
//! `kobako_runtime` Filesystem of that kind.
|
|
4
|
+
//!
|
|
5
|
+
//! A kind kobako ships runs in Rust; Ruby only picks it and the access.
|
|
6
|
+
//! Ruby is asked again while the guest runs through a mount's block, at
|
|
7
|
+
//! most once per open or change, and through a Filesystem written in
|
|
8
|
+
//! Ruby, once per call.
|
|
9
|
+
|
|
10
|
+
mod grants;
|
|
11
|
+
mod ruby;
|
|
12
|
+
|
|
13
|
+
use std::sync::Arc;
|
|
14
|
+
|
|
15
|
+
use magnus::value::{Opaque, ReprValue};
|
|
16
|
+
use magnus::{
|
|
17
|
+
function, gc, method, prelude::*, scan_args::get_kwargs, scan_args::scan_args,
|
|
18
|
+
DataTypeFunctions, Error as MagnusError, RHash, RModule, RString, Ruby, Symbol, TypedData,
|
|
19
|
+
Value,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
use ruby::RubyFilesystem;
|
|
23
|
+
|
|
24
|
+
use kobako_runtime::filesystem::{
|
|
25
|
+
Access, Blocking, Directory, Filesystem, Memory, Overlay, Temporary,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
pub(super) use grants::{to_grants, validate_mount};
|
|
29
|
+
|
|
30
|
+
macro_rules! filesystem_class {
|
|
31
|
+
($name:ident, $class:literal) => {
|
|
32
|
+
#[magnus::wrap(class = $class, free_immediately, size)]
|
|
33
|
+
pub(super) struct $name(Arc<dyn Filesystem>);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
filesystem_class!(MemoryFs, "Kobako::Filesystem::Memory");
|
|
38
|
+
filesystem_class!(TemporaryFs, "Kobako::Filesystem::Temporary");
|
|
39
|
+
filesystem_class!(DirectoryFs, "Kobako::Filesystem::Directory");
|
|
40
|
+
|
|
41
|
+
/// The Overlay itself rather than a shared handle, so `layer` can answer a
|
|
42
|
+
/// new one and leave this one as it was. It marks the Ruby object of
|
|
43
|
+
/// every Layer, since a Filesystem written in Ruby has nothing else
|
|
44
|
+
/// keeping it alive.
|
|
45
|
+
#[derive(magnus::TypedData)]
|
|
46
|
+
#[magnus(class = "Kobako::Filesystem::Overlay", free_immediately, size, mark)]
|
|
47
|
+
pub(super) struct OverlayFs {
|
|
48
|
+
overlay: Overlay,
|
|
49
|
+
layers: Vec<Opaque<Value>>,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl DataTypeFunctions for OverlayFs {
|
|
53
|
+
fn mark(&self, marker: &gc::Marker) {
|
|
54
|
+
marker.mark_slice(&self.layers);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub(super) fn register(ruby: &Ruby, kobako: RModule) -> Result<(), MagnusError> {
|
|
59
|
+
let module = kobako.define_module("Filesystem")?;
|
|
60
|
+
let memory = module.define_class("Memory", ruby.class_object())?;
|
|
61
|
+
memory.define_singleton_method("new", function!(memory_new, -1))?;
|
|
62
|
+
let temporary = module.define_class("Temporary", ruby.class_object())?;
|
|
63
|
+
temporary.define_singleton_method("new", function!(temporary_new, -1))?;
|
|
64
|
+
let directory = module.define_class("Directory", ruby.class_object())?;
|
|
65
|
+
directory.define_singleton_method("new", function!(directory_new, 1))?;
|
|
66
|
+
let overlay = module.define_class("Overlay", ruby.class_object())?;
|
|
67
|
+
overlay.define_singleton_method("new", function!(overlay_new, 0))?;
|
|
68
|
+
overlay.define_method("layer", method!(overlay_layer, -1))?;
|
|
69
|
+
MemoryFs::class(ruby);
|
|
70
|
+
TemporaryFs::class(ruby);
|
|
71
|
+
DirectoryFs::class(ruby);
|
|
72
|
+
OverlayFs::class(ruby);
|
|
73
|
+
Ok(())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// `Memory.new(files = {}, limit: 64 MiB, inodes:)`: each
|
|
77
|
+
/// `path => contents` pair becomes a file, its directories created along
|
|
78
|
+
/// the way. Ruby hands a bare `"path" => contents` over as keywords, so
|
|
79
|
+
/// every String keyword is a file too. Without `inodes:` the entries
|
|
80
|
+
/// follow the limit.
|
|
81
|
+
fn memory_new(args: &[Value]) -> Result<MemoryFs, MagnusError> {
|
|
82
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
83
|
+
let args = scan_args::<(), (Option<RHash>,), (), (), RHash, ()>(args)?;
|
|
84
|
+
let mut files = Vec::new();
|
|
85
|
+
let mut limit = Some(Memory::DEFAULT_LIMIT);
|
|
86
|
+
let mut inodes = None;
|
|
87
|
+
let limit_key = ruby.to_symbol("limit");
|
|
88
|
+
let inodes_key = ruby.to_symbol("inodes");
|
|
89
|
+
for hash in args.optional.0.into_iter().chain([args.keywords]) {
|
|
90
|
+
hash.foreach(|key: Value, value: Value| {
|
|
91
|
+
match Symbol::from_value(key) {
|
|
92
|
+
Some(key) if key.equal(limit_key)? => limit = to_limit(value)?,
|
|
93
|
+
Some(key) if key.equal(inodes_key)? => inodes = Some(to_limit(value)?),
|
|
94
|
+
Some(key) => {
|
|
95
|
+
return Err(MagnusError::new(
|
|
96
|
+
ruby.exception_arg_error(),
|
|
97
|
+
format!("unknown keyword: :{}", key.name()?),
|
|
98
|
+
))
|
|
99
|
+
}
|
|
100
|
+
None => files.push((
|
|
101
|
+
String::try_convert(key)?,
|
|
102
|
+
super::rstring_to_vec(RString::try_convert(value)?),
|
|
103
|
+
)),
|
|
104
|
+
}
|
|
105
|
+
Ok(magnus::r_hash::ForEach::Continue)
|
|
106
|
+
})?;
|
|
107
|
+
}
|
|
108
|
+
// A list no Memory holds is an `ArgumentError` the Host App can rescue.
|
|
109
|
+
let memory = Memory::try_from_iter(files)
|
|
110
|
+
.map_err(|error| MagnusError::new(ruby.exception_arg_error(), error.to_string()))?
|
|
111
|
+
.with_limit(limit);
|
|
112
|
+
Ok(MemoryFs(Arc::new(match inodes {
|
|
113
|
+
Some(inodes) => memory.with_inodes(inodes),
|
|
114
|
+
None => memory,
|
|
115
|
+
})))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// `Temporary.new(limit: 64 MiB, inodes:)`.
|
|
119
|
+
fn temporary_new(args: &[Value]) -> Result<TemporaryFs, MagnusError> {
|
|
120
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
121
|
+
let kwargs = get_kwargs::<_, (), (Option<Value>, Option<Value>), ()>(
|
|
122
|
+
args.keywords,
|
|
123
|
+
&[],
|
|
124
|
+
&["limit", "inodes"],
|
|
125
|
+
)?;
|
|
126
|
+
let (limit, inodes) = kwargs.optional;
|
|
127
|
+
let limit = match limit {
|
|
128
|
+
Some(limit) => to_limit(limit)?,
|
|
129
|
+
None => Some(Memory::DEFAULT_LIMIT),
|
|
130
|
+
};
|
|
131
|
+
let temporary = Temporary::new().with_limit(limit);
|
|
132
|
+
Ok(TemporaryFs(Arc::new(match inodes {
|
|
133
|
+
Some(inodes) => temporary.with_inodes(to_limit(inodes)?),
|
|
134
|
+
None => temporary,
|
|
135
|
+
})))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// A `limit:` or `inodes:` count, or `nil` for none.
|
|
139
|
+
fn to_limit(limit: Value) -> Result<Option<usize>, MagnusError> {
|
|
140
|
+
Option::<usize>::try_convert(limit)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn directory_new(path: String) -> Result<DirectoryFs, MagnusError> {
|
|
144
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
145
|
+
Directory::new(&path)
|
|
146
|
+
.map(|directory| DirectoryFs(Arc::new(directory)))
|
|
147
|
+
.map_err(|err| {
|
|
148
|
+
MagnusError::new(
|
|
149
|
+
ruby.exception_arg_error(),
|
|
150
|
+
format!("cannot open host directory {path}: {err}"),
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// `Overlay.new`, with no Layer yet.
|
|
156
|
+
fn overlay_new() -> OverlayFs {
|
|
157
|
+
OverlayFs {
|
|
158
|
+
overlay: Overlay::new().with_blocking(Arc::new(GvlFreeWait)),
|
|
159
|
+
layers: Vec::new(),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// An Overlay's thread waits for its lock without the GVL, so a holder
|
|
164
|
+
/// asking a Layer written in Ruby can take the GVL and finish.
|
|
165
|
+
struct GvlFreeWait;
|
|
166
|
+
|
|
167
|
+
impl Blocking for GvlFreeWait {
|
|
168
|
+
fn wait(&self, wait: &mut dyn FnMut()) {
|
|
169
|
+
super::gvl::region(super::gvl::holds(), wait);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// `layer(filesystem, access: :read)`, a new Overlay with `filesystem`
|
|
174
|
+
/// beneath this one's Layers.
|
|
175
|
+
fn overlay_layer(overlay: &OverlayFs, args: &[Value]) -> Result<OverlayFs, MagnusError> {
|
|
176
|
+
let args = scan_args::<(Value,), (), (), (), RHash, ()>(args)?;
|
|
177
|
+
let kwargs = get_kwargs::<_, (), (Option<Value>,), ()>(args.keywords, &[], &["access"])?;
|
|
178
|
+
let (filesystem,) = args.required;
|
|
179
|
+
let access = match kwargs.optional.0 {
|
|
180
|
+
Some(access) => to_access(access)?,
|
|
181
|
+
None => Access::Read,
|
|
182
|
+
};
|
|
183
|
+
let mut layers = overlay.layers.clone();
|
|
184
|
+
layers.push(Opaque::from(filesystem));
|
|
185
|
+
Ok(OverlayFs {
|
|
186
|
+
overlay: overlay
|
|
187
|
+
.overlay
|
|
188
|
+
.clone()
|
|
189
|
+
.layer(to_filesystem(filesystem)?, access),
|
|
190
|
+
layers,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn to_access(access: Value) -> Result<Access, MagnusError> {
|
|
195
|
+
let name = Symbol::from_value(access)
|
|
196
|
+
.map(|symbol| symbol.name())
|
|
197
|
+
.transpose()?;
|
|
198
|
+
match name.as_deref() {
|
|
199
|
+
Some("read") => Ok(Access::Read),
|
|
200
|
+
Some("read_write") => Ok(Access::ReadWrite),
|
|
201
|
+
_ => Err(MagnusError::new(
|
|
202
|
+
Ruby::get().expect("Ruby thread").exception_arg_error(),
|
|
203
|
+
format!(
|
|
204
|
+
"access must be :read or :read_write, got {}",
|
|
205
|
+
access.inspect()
|
|
206
|
+
),
|
|
207
|
+
)),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// The Filesystem a Ruby handle of any kobako kind stands for.
|
|
212
|
+
fn to_filesystem(value: Value) -> Result<Arc<dyn Filesystem>, MagnusError> {
|
|
213
|
+
if let Ok(fs) = <&MemoryFs>::try_convert(value) {
|
|
214
|
+
return Ok(fs.0.clone());
|
|
215
|
+
}
|
|
216
|
+
if let Ok(fs) = <&TemporaryFs>::try_convert(value) {
|
|
217
|
+
return Ok(fs.0.clone());
|
|
218
|
+
}
|
|
219
|
+
if let Ok(fs) = <&DirectoryFs>::try_convert(value) {
|
|
220
|
+
return Ok(fs.0.clone());
|
|
221
|
+
}
|
|
222
|
+
if let Ok(fs) = <&OverlayFs>::try_convert(value) {
|
|
223
|
+
return Ok(Arc::new(fs.overlay.clone()));
|
|
224
|
+
}
|
|
225
|
+
if let Some(fs) = RubyFilesystem::from_value(value) {
|
|
226
|
+
return Ok(Arc::new(fs));
|
|
227
|
+
}
|
|
228
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
229
|
+
Err(MagnusError::new(
|
|
230
|
+
ruby.exception_type_error(),
|
|
231
|
+
format!(
|
|
232
|
+
"expected a Kobako::Filesystem or an object answering open and stat, got {}",
|
|
233
|
+
value.class()
|
|
234
|
+
),
|
|
235
|
+
))
|
|
236
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Releasing Ruby's GVL for
|
|
1
|
+
//! Releasing Ruby's GVL for an invocation's guest span so distinct Sandboxes on
|
|
2
2
|
//! distinct Threads run their guest code in parallel.
|
|
3
3
|
//!
|
|
4
4
|
//! `region` brackets one invocation's guest span, releasing the GVL when the
|
|
@@ -22,8 +22,6 @@ thread_local! {
|
|
|
22
22
|
static GVL_RELEASED: Cell<bool> = const { Cell::new(false) };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/// Run `f` (one invocation's guest-execution span) and return its value,
|
|
26
|
-
/// releasing the GVL for the span iff `release`. When holding, `f` runs inline.
|
|
27
25
|
pub(crate) fn region<F, R>(release: bool, f: F) -> R
|
|
28
26
|
where
|
|
29
27
|
F: FnOnce() -> R,
|
|
@@ -37,10 +35,14 @@ where
|
|
|
37
35
|
})
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
///
|
|
41
|
-
///
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
/// Whether this thread holds the GVL: outside any released region, or
|
|
39
|
+
/// inside a dispatch that re-acquired it.
|
|
40
|
+
pub(crate) fn holds() -> bool {
|
|
41
|
+
!GVL_RELEASED.with(|g| g.get())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Runs inline when the GVL is already held — hold mode, or a nested
|
|
45
|
+
/// dispatch whose outer frame re-acquired it.
|
|
44
46
|
pub(crate) fn reenter<F, R>(f: F) -> R
|
|
45
47
|
where
|
|
46
48
|
F: FnOnce() -> R,
|
|
@@ -79,9 +81,8 @@ struct GvlCtx<F, R> {
|
|
|
79
81
|
result: Option<std::thread::Result<R>>,
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
///
|
|
83
|
-
///
|
|
84
|
-
/// unwinding across the C frame, and stashes the outcome for `call_via`.
|
|
84
|
+
/// Runs the closure under `catch_unwind`, so a panic is captured here rather
|
|
85
|
+
/// than unwinding across the C frame.
|
|
85
86
|
unsafe extern "C" fn trampoline<F, R>(data: *mut c_void) -> *mut c_void
|
|
86
87
|
where
|
|
87
88
|
F: FnOnce() -> R,
|
|
@@ -92,9 +93,6 @@ where
|
|
|
92
93
|
ptr::null_mut()
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
/// Drive `f` through one of the `rb_thread_call_*` entry points (`invoke`),
|
|
96
|
-
/// erasing it to the C callback shape and re-raising on the caller's thread any
|
|
97
|
-
/// panic the trampoline caught.
|
|
98
96
|
fn call_via<F, R>(
|
|
99
97
|
f: F,
|
|
100
98
|
invoke: impl FnOnce(unsafe extern "C" fn(*mut c_void) -> *mut c_void, *mut c_void) -> *mut c_void,
|