microsandbox-rb 0.17.1 → 0.17.2
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 +94 -8
- data/Cargo.lock +1 -1
- data/DESIGN.md +85 -36
- data/README.md +6 -6
- data/SECURITY.md +1 -1
- data/docs/execution.md +8 -1
- data/docs/lifecycle.md +33 -0
- data/docs/releasing.md +85 -34
- data/docs/runtime.md +3 -1
- data/docs/surface.md +13 -8
- data/ext/microsandbox/Cargo.toml +9 -5
- data/ext/microsandbox/src/agent.rs +13 -11
- data/ext/microsandbox/src/backend.rs +1 -1
- data/ext/microsandbox/src/exec.rs +12 -8
- data/ext/microsandbox/src/fs_stream.rs +19 -7
- data/ext/microsandbox/src/image.rs +2 -2
- data/ext/microsandbox/src/lib.rs +2 -2
- data/ext/microsandbox/src/runtime.rs +189 -13
- data/ext/microsandbox/src/sandbox.rs +88 -71
- data/ext/microsandbox/src/snapshot.rs +17 -11
- data/ext/microsandbox/src/ssh.rs +22 -16
- data/ext/microsandbox/src/stream.rs +6 -2
- data/ext/microsandbox/src/volume.rs +18 -16
- data/lib/microsandbox/sandbox.rb +75 -4
- data/lib/microsandbox/version.rb +1 -1
- data/sig/microsandbox.rbs +24 -0
- metadata +1 -1
data/ext/microsandbox/src/ssh.rs
CHANGED
|
@@ -52,7 +52,7 @@ impl SshClient {
|
|
|
52
52
|
let guard = inner.lock().await;
|
|
53
53
|
let client = guard.as_ref().ok_or_else(consumed)?;
|
|
54
54
|
client.exec_with(command, |b| b.tty(tty)).await
|
|
55
|
-
})
|
|
55
|
+
})?;
|
|
56
56
|
Ok(ssh_output_to_hash(result.map_err(error::to_ruby)?))
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -74,7 +74,7 @@ impl SshClient {
|
|
|
74
74
|
b
|
|
75
75
|
})
|
|
76
76
|
.await
|
|
77
|
-
})
|
|
77
|
+
})?;
|
|
78
78
|
result.map_err(error::to_ruby)
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -85,7 +85,7 @@ impl SshClient {
|
|
|
85
85
|
let guard = inner.lock().await;
|
|
86
86
|
let client = guard.as_ref().ok_or_else(consumed)?;
|
|
87
87
|
client.sftp().await
|
|
88
|
-
})
|
|
88
|
+
})?;
|
|
89
89
|
Ok(SftpClient::from_core(result.map_err(error::to_ruby)?))
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -101,7 +101,7 @@ impl SshClient {
|
|
|
101
101
|
Some(c) => c.close().await,
|
|
102
102
|
None => Ok(()),
|
|
103
103
|
}
|
|
104
|
-
})
|
|
104
|
+
})?;
|
|
105
105
|
result.map_err(error::to_ruby)
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -144,7 +144,7 @@ impl SftpClient {
|
|
|
144
144
|
let guard = inner.lock().await;
|
|
145
145
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
146
146
|
sftp.read(path).await.map_err(sftp_str)
|
|
147
|
-
})
|
|
147
|
+
})?;
|
|
148
148
|
Ok(ruby().str_from_slice(&result.map_err(error::base_error)?))
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -158,7 +158,7 @@ impl SftpClient {
|
|
|
158
158
|
file.write_all(&bytes).await.map_err(sftp_str)?;
|
|
159
159
|
file.shutdown().await.map_err(sftp_str)?;
|
|
160
160
|
Ok(())
|
|
161
|
-
})
|
|
161
|
+
})?;
|
|
162
162
|
result.map_err(error::base_error)
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -168,7 +168,7 @@ impl SftpClient {
|
|
|
168
168
|
let guard = inner.lock().await;
|
|
169
169
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
170
170
|
sftp.create_dir(path).await.map_err(sftp_str)
|
|
171
|
-
})
|
|
171
|
+
})?;
|
|
172
172
|
result.map_err(error::base_error)
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -178,7 +178,7 @@ impl SftpClient {
|
|
|
178
178
|
let guard = inner.lock().await;
|
|
179
179
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
180
180
|
sftp.remove_file(path).await.map_err(sftp_str)
|
|
181
|
-
})
|
|
181
|
+
})?;
|
|
182
182
|
result.map_err(error::base_error)
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -188,7 +188,7 @@ impl SftpClient {
|
|
|
188
188
|
let guard = inner.lock().await;
|
|
189
189
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
190
190
|
sftp.remove_dir(path).await.map_err(sftp_str)
|
|
191
|
-
})
|
|
191
|
+
})?;
|
|
192
192
|
result.map_err(error::base_error)
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -198,7 +198,7 @@ impl SftpClient {
|
|
|
198
198
|
let guard = inner.lock().await;
|
|
199
199
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
200
200
|
sftp.rename(old_path, new_path).await.map_err(sftp_str)
|
|
201
|
-
})
|
|
201
|
+
})?;
|
|
202
202
|
result.map_err(error::base_error)
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -208,7 +208,7 @@ impl SftpClient {
|
|
|
208
208
|
let guard = inner.lock().await;
|
|
209
209
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
210
210
|
sftp.symlink(target, link_path).await.map_err(sftp_str)
|
|
211
|
-
})
|
|
211
|
+
})?;
|
|
212
212
|
result.map_err(error::base_error)
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -218,7 +218,7 @@ impl SftpClient {
|
|
|
218
218
|
let guard = inner.lock().await;
|
|
219
219
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
220
220
|
sftp.canonicalize(path).await.map_err(sftp_str)
|
|
221
|
-
})
|
|
221
|
+
})?;
|
|
222
222
|
result.map_err(error::base_error)
|
|
223
223
|
}
|
|
224
224
|
|
|
@@ -228,7 +228,7 @@ impl SftpClient {
|
|
|
228
228
|
let guard = inner.lock().await;
|
|
229
229
|
let sftp = guard.as_ref().ok_or_else(|| sftp_str("session closed"))?;
|
|
230
230
|
sftp.read_link(path).await.map_err(sftp_str)
|
|
231
|
-
})
|
|
231
|
+
})?;
|
|
232
232
|
result.map_err(error::base_error)
|
|
233
233
|
}
|
|
234
234
|
|
|
@@ -243,7 +243,7 @@ impl SftpClient {
|
|
|
243
243
|
Some(s) => s.close().await.map_err(sftp_str),
|
|
244
244
|
None => Ok(()),
|
|
245
245
|
}
|
|
246
|
-
})
|
|
246
|
+
})?;
|
|
247
247
|
result.map_err(error::base_error)
|
|
248
248
|
}
|
|
249
249
|
}
|
|
@@ -276,7 +276,7 @@ impl SshServer {
|
|
|
276
276
|
Some(s) => s.serve_connection(SshStdioStream::new()).await,
|
|
277
277
|
None => Err(consumed()),
|
|
278
278
|
}
|
|
279
|
-
})
|
|
279
|
+
})?;
|
|
280
280
|
result.map_err(error::to_ruby)
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -285,19 +285,23 @@ impl SshServer {
|
|
|
285
285
|
let inner = Arc::clone(&self.inner);
|
|
286
286
|
block_on(async move {
|
|
287
287
|
inner.lock().await.take();
|
|
288
|
-
})
|
|
288
|
+
})?;
|
|
289
289
|
Ok(())
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
294
294
|
let client = native.define_class("SshClient", ruby.class_object())?;
|
|
295
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
296
|
+
crate::runtime::preload_class::<SshClient>(ruby);
|
|
295
297
|
client.define_method("exec", method!(SshClient::exec, 2))?;
|
|
296
298
|
client.define_method("attach", method!(SshClient::attach, 2))?;
|
|
297
299
|
client.define_method("sftp", method!(SshClient::sftp, 0))?;
|
|
298
300
|
client.define_method("close", method!(SshClient::close, 0))?;
|
|
299
301
|
|
|
300
302
|
let sftp = native.define_class("SftpClient", ruby.class_object())?;
|
|
303
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
304
|
+
crate::runtime::preload_class::<SftpClient>(ruby);
|
|
301
305
|
sftp.define_method("read", method!(SftpClient::read, 1))?;
|
|
302
306
|
sftp.define_method("write", method!(SftpClient::write, 2))?;
|
|
303
307
|
sftp.define_method("mkdir", method!(SftpClient::mkdir, 1))?;
|
|
@@ -310,6 +314,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
310
314
|
sftp.define_method("close", method!(SftpClient::close, 0))?;
|
|
311
315
|
|
|
312
316
|
let server = native.define_class("SshServer", ruby.class_object())?;
|
|
317
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
318
|
+
crate::runtime::preload_class::<SshServer>(ruby);
|
|
313
319
|
server.define_method("serve_connection", method!(SshServer::serve_connection, 0))?;
|
|
314
320
|
server.define_method("close", method!(SshServer::close, 0))?;
|
|
315
321
|
|
|
@@ -41,7 +41,7 @@ impl LogStream {
|
|
|
41
41
|
fn recv(&self) -> Result<Option<RHash>, Error> {
|
|
42
42
|
use futures::StreamExt;
|
|
43
43
|
let inner = Arc::clone(&self.inner);
|
|
44
|
-
match block_on(async move { inner.lock().await.next().await }) {
|
|
44
|
+
match block_on(async move { inner.lock().await.next().await })? {
|
|
45
45
|
Some(Ok(entry)) => Ok(Some(log_entry_to_hash(&entry))),
|
|
46
46
|
Some(Err(e)) => Err(error::to_ruby(e)),
|
|
47
47
|
None => Ok(None),
|
|
@@ -67,7 +67,7 @@ impl MetricsStream {
|
|
|
67
67
|
fn recv(&self) -> Result<Option<RHash>, Error> {
|
|
68
68
|
use futures::StreamExt;
|
|
69
69
|
let inner = Arc::clone(&self.inner);
|
|
70
|
-
match block_on(async move { inner.lock().await.next().await }) {
|
|
70
|
+
match block_on(async move { inner.lock().await.next().await })? {
|
|
71
71
|
Some(Ok(metrics)) => Ok(Some(metrics_to_hash(&metrics))),
|
|
72
72
|
Some(Err(e)) => Err(error::to_ruby(e)),
|
|
73
73
|
None => Ok(None),
|
|
@@ -77,9 +77,13 @@ impl MetricsStream {
|
|
|
77
77
|
|
|
78
78
|
pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
79
79
|
let logs = native.define_class("LogStream", ruby.class_object())?;
|
|
80
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
81
|
+
crate::runtime::preload_class::<LogStream>(ruby);
|
|
80
82
|
logs.define_method("recv", method!(LogStream::recv, 0))?;
|
|
81
83
|
|
|
82
84
|
let metrics = native.define_class("MetricsStream", ruby.class_object())?;
|
|
85
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
86
|
+
crate::runtime::preload_class::<MetricsStream>(ruby);
|
|
83
87
|
metrics.define_method("recv", method!(MetricsStream::recv, 0))?;
|
|
84
88
|
|
|
85
89
|
Ok(())
|
|
@@ -68,7 +68,7 @@ fn create(name: String, opts: RHash) -> Result<RHash, Error> {
|
|
|
68
68
|
builder = builder.label(k, v);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
let vol = block_on(builder.create())
|
|
71
|
+
let vol = block_on(builder.create())?.map_err(error::to_ruby)?;
|
|
72
72
|
let hash = ruby().hash_new();
|
|
73
73
|
hash.aset("name", vol.name().to_string())?;
|
|
74
74
|
// `path()` is fallible as of v0.5.8 (returns `Unsupported` for cloud
|
|
@@ -81,19 +81,19 @@ fn create(name: String, opts: RHash) -> Result<RHash, Error> {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
fn get(name: String) -> Result<RHash, Error> {
|
|
84
|
-
let handle = block_on(microsandbox::Volume::get(&name))
|
|
84
|
+
let handle = block_on(microsandbox::Volume::get(&name))?.map_err(error::to_ruby)?;
|
|
85
85
|
Ok(handle_to_hash(&handle))
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/// The backend's default volume (v0.6.9). Cloud-backend only — the local
|
|
89
89
|
/// backend rejects it (Unsupported) to avoid accidental host access.
|
|
90
90
|
fn get_default() -> Result<RHash, Error> {
|
|
91
|
-
let handle = block_on(microsandbox::Volume::get_default())
|
|
91
|
+
let handle = block_on(microsandbox::Volume::get_default())?.map_err(error::to_ruby)?;
|
|
92
92
|
Ok(handle_to_hash(&handle))
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
fn list() -> Result<RArray, Error> {
|
|
96
|
-
let handles = block_on(microsandbox::Volume::list())
|
|
96
|
+
let handles = block_on(microsandbox::Volume::list())?.map_err(error::to_ruby)?;
|
|
97
97
|
let arr = ruby().ary_new();
|
|
98
98
|
for h in handles.iter() {
|
|
99
99
|
arr.push(handle_to_hash(h))?;
|
|
@@ -102,7 +102,7 @@ fn list() -> Result<RArray, Error> {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
fn remove(name: String) -> Result<(), Error> {
|
|
105
|
-
block_on(microsandbox::Volume::remove(&name))
|
|
105
|
+
block_on(microsandbox::Volume::remove(&name))?.map_err(error::to_ruby)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/// Volume paths are relative to the volume root and never need a `..` segment,
|
|
@@ -151,14 +151,14 @@ impl VolumeFs {
|
|
|
151
151
|
fn read(&self, path: String) -> Result<RString, Error> {
|
|
152
152
|
let path = contained(&path)?;
|
|
153
153
|
let fs = self.fs();
|
|
154
|
-
let bytes = block_on(fs.read(path))
|
|
154
|
+
let bytes = block_on(fs.read(path))?.map_err(error::to_ruby)?;
|
|
155
155
|
Ok(ruby().str_from_slice(bytes.as_ref()))
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
fn read_text(&self, path: String) -> Result<String, Error> {
|
|
159
159
|
let path = contained(&path)?;
|
|
160
160
|
let fs = self.fs();
|
|
161
|
-
block_on(fs.read_to_string(path))
|
|
161
|
+
block_on(fs.read_to_string(path))?.map_err(error::to_ruby)
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
fn write(&self, path: String, data: RString) -> Result<(), Error> {
|
|
@@ -166,13 +166,13 @@ impl VolumeFs {
|
|
|
166
166
|
let bytes = unsafe { data.as_slice() }.to_vec();
|
|
167
167
|
let path = contained(&path)?;
|
|
168
168
|
let fs = self.fs();
|
|
169
|
-
block_on(fs.write(path, &bytes))
|
|
169
|
+
block_on(fs.write(path, &bytes))?.map_err(error::to_ruby)
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
fn list(&self, path: String) -> Result<RArray, Error> {
|
|
173
173
|
let path = contained(&path)?;
|
|
174
174
|
let fs = self.fs();
|
|
175
|
-
let entries = block_on(fs.list(path))
|
|
175
|
+
let entries = block_on(fs.list(path))?.map_err(error::to_ruby)?;
|
|
176
176
|
let arr = ruby().ary_new();
|
|
177
177
|
for entry in &entries {
|
|
178
178
|
arr.push(fs_entry_to_hash(entry))?;
|
|
@@ -183,45 +183,45 @@ impl VolumeFs {
|
|
|
183
183
|
fn mkdir(&self, path: String) -> Result<(), Error> {
|
|
184
184
|
let path = contained(&path)?;
|
|
185
185
|
let fs = self.fs();
|
|
186
|
-
block_on(fs.mkdir(path))
|
|
186
|
+
block_on(fs.mkdir(path))?.map_err(error::to_ruby)
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
fn remove_file(&self, path: String) -> Result<(), Error> {
|
|
190
190
|
let path = contained(&path)?;
|
|
191
191
|
let fs = self.fs();
|
|
192
|
-
block_on(fs.remove(path))
|
|
192
|
+
block_on(fs.remove(path))?.map_err(error::to_ruby)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
fn remove_dir(&self, path: String) -> Result<(), Error> {
|
|
196
196
|
let path = contained(&path)?;
|
|
197
197
|
let fs = self.fs();
|
|
198
|
-
block_on(fs.remove_dir(path))
|
|
198
|
+
block_on(fs.remove_dir(path))?.map_err(error::to_ruby)
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
fn exists(&self, path: String) -> Result<bool, Error> {
|
|
202
202
|
let path = contained(&path)?;
|
|
203
203
|
let fs = self.fs();
|
|
204
|
-
block_on(fs.exists(path))
|
|
204
|
+
block_on(fs.exists(path))?.map_err(error::to_ruby)
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
fn copy(&self, from: String, to: String) -> Result<(), Error> {
|
|
208
208
|
let from = contained(&from)?;
|
|
209
209
|
let to = contained(&to)?;
|
|
210
210
|
let fs = self.fs();
|
|
211
|
-
block_on(fs.copy(from, to))
|
|
211
|
+
block_on(fs.copy(from, to))?.map_err(error::to_ruby)
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
fn rename(&self, from: String, to: String) -> Result<(), Error> {
|
|
215
215
|
let from = contained(&from)?;
|
|
216
216
|
let to = contained(&to)?;
|
|
217
217
|
let fs = self.fs();
|
|
218
|
-
block_on(fs.rename(from, to))
|
|
218
|
+
block_on(fs.rename(from, to))?.map_err(error::to_ruby)
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
fn stat(&self, path: String) -> Result<RHash, Error> {
|
|
222
222
|
let path = contained(&path)?;
|
|
223
223
|
let fs = self.fs();
|
|
224
|
-
let meta = block_on(fs.stat(path))
|
|
224
|
+
let meta = block_on(fs.stat(path))?.map_err(error::to_ruby)?;
|
|
225
225
|
Ok(fs_metadata_to_hash(&meta))
|
|
226
226
|
}
|
|
227
227
|
}
|
|
@@ -241,6 +241,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
241
241
|
class.define_singleton_method("fs", function!(fs, 1))?;
|
|
242
242
|
|
|
243
243
|
let vfs = native.define_class("VolumeFs", ruby.class_object())?;
|
|
244
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
245
|
+
crate::runtime::preload_class::<VolumeFs>(ruby);
|
|
244
246
|
vfs.define_method("read", method!(VolumeFs::read, 1))?;
|
|
245
247
|
vfs.define_method("read_text", method!(VolumeFs::read_text, 1))?;
|
|
246
248
|
vfs.define_method("write", method!(VolumeFs::write, 2))?;
|
data/lib/microsandbox/sandbox.rb
CHANGED
|
@@ -496,7 +496,10 @@ module Microsandbox
|
|
|
496
496
|
def create(name, **kwargs, &block)
|
|
497
497
|
opts = build_create_opts(**kwargs)
|
|
498
498
|
Microsandbox.ensure_runtime!
|
|
499
|
-
|
|
499
|
+
# Everything Ruby-level happens BEFORE `acquire`: its interrupt mask must
|
|
500
|
+
# cover only the native call plus the wrap, never caller-supplied code.
|
|
501
|
+
sandbox_name = name.to_s
|
|
502
|
+
sandbox = acquire { Native::Sandbox.create(sandbox_name, opts) }
|
|
500
503
|
return sandbox unless block_given?
|
|
501
504
|
|
|
502
505
|
begin
|
|
@@ -553,7 +556,8 @@ module Microsandbox
|
|
|
553
556
|
def connect_or_create(name, **kwargs, &block)
|
|
554
557
|
opts = build_create_opts(**kwargs)
|
|
555
558
|
Microsandbox.ensure_runtime!
|
|
556
|
-
|
|
559
|
+
sandbox_name = name.to_s # outside the interrupt mask, like in {create}
|
|
560
|
+
sandbox = acquire { Native::Sandbox.connect_or_create(sandbox_name, opts) }
|
|
557
561
|
return sandbox unless block_given?
|
|
558
562
|
|
|
559
563
|
begin
|
|
@@ -588,6 +592,65 @@ module Microsandbox
|
|
|
588
592
|
PullSession.new(Native::Sandbox.create_with_progress(name.to_s, opts))
|
|
589
593
|
end
|
|
590
594
|
|
|
595
|
+
# @api private
|
|
596
|
+
# Wrap the native sandbox the block acquires, and stop it again if an
|
|
597
|
+
# interrupt cuts in before the caller has it.
|
|
598
|
+
#
|
|
599
|
+
# A native call cannot be interrupted while it runs, so a `Thread#raise`
|
|
600
|
+
# (what `Timeout.timeout` uses) or a `Thread#kill` that arrives during the
|
|
601
|
+
# block is delivered after it — at the next interrupt checkpoint, which is
|
|
602
|
+
# still in here, before the caller ever sees the object. Left alone the
|
|
603
|
+
# sandbox would be garbage and only GC would stop it, "eventually"; for
|
|
604
|
+
# `Timeout.timeout { Sandbox.create(...) }` that is not good enough. So:
|
|
605
|
+
#
|
|
606
|
+
# - the acquisition runs under `Thread.handle_interrupt(Object => :never)`,
|
|
607
|
+
# which holds a queued exception (or kill) back until the wrapper is
|
|
608
|
+
# assigned. The mask covers only the uninterruptible native call plus
|
|
609
|
+
# the wrap, so it changes no observable timing; runtime provisioning
|
|
610
|
+
# ({Microsandbox.ensure_runtime!}) and the block forms' `yield` stay
|
|
611
|
+
# outside it, with normal interrupt handling. For the same reason the
|
|
612
|
+
# block passed here must contain NOTHING but the native call: argument
|
|
613
|
+
# conversion (`name.to_s`, option Hashes) is the caller's own code and
|
|
614
|
+
# has to run before `acquire`, where it stays interruptible;
|
|
615
|
+
# - the `ensure` plus the `acquired` flag — not `rescue Exception; raise` —
|
|
616
|
+
# is what also covers `Thread#kill`, which no `rescue` can catch.
|
|
617
|
+
#
|
|
618
|
+
# A SIGINT (Ctrl-C on the main thread) is the exception: it is not queued
|
|
619
|
+
# like those two and cannot be masked. One that lands during the native
|
|
620
|
+
# call is raised the moment the call returns — the VM checks interrupts
|
|
621
|
+
# as it pops the native method's frame, before any Ruby code here can
|
|
622
|
+
# hold the result — so no assignment order would catch it; the sandbox
|
|
623
|
+
# is then torn down when the unreferenced object is garbage-collected.
|
|
624
|
+
#
|
|
625
|
+
# Ownership: {connect_or_create} can hand back a sandbox that was already
|
|
626
|
+
# running for someone else, and {start} restarts a persisted one. The
|
|
627
|
+
# cleanup therefore applies EXACTLY the predicate the block form of
|
|
628
|
+
# {connect_or_create} uses — {Sandbox#owns_lifecycle?} — in all three
|
|
629
|
+
# callers, {create} included: a sandbox this call does not own is never
|
|
630
|
+
# stopped here. Its reference is just let go (GC closes the handle, the
|
|
631
|
+
# sandbox keeps running).
|
|
632
|
+
# @yieldreturn [Native::Sandbox]
|
|
633
|
+
# @return [Sandbox]
|
|
634
|
+
def acquire
|
|
635
|
+
sandbox = nil
|
|
636
|
+
acquired = false
|
|
637
|
+
begin
|
|
638
|
+
Thread.handle_interrupt(Object => :never) do
|
|
639
|
+
sandbox = new(yield)
|
|
640
|
+
end
|
|
641
|
+
acquired = true
|
|
642
|
+
ensure
|
|
643
|
+
if sandbox && !acquired && sandbox.owns_lifecycle?
|
|
644
|
+
begin
|
|
645
|
+
sandbox.stop
|
|
646
|
+
rescue Microsandbox::Error
|
|
647
|
+
# best-effort cleanup; the interrupt is what the caller must see
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
sandbox
|
|
652
|
+
end
|
|
653
|
+
|
|
591
654
|
# @api private
|
|
592
655
|
# Shared keyword-option builder for {create}/{connect_or_create}/
|
|
593
656
|
# {create_with_progress}. Pure validation and normalization: the callers
|
|
@@ -705,7 +768,9 @@ module Microsandbox
|
|
|
705
768
|
# @return [Sandbox]
|
|
706
769
|
def start(name, detached: false)
|
|
707
770
|
Microsandbox.ensure_runtime!
|
|
708
|
-
|
|
771
|
+
sandbox_name = name.to_s # outside the interrupt mask, like in {create}
|
|
772
|
+
opts = {"detached" => detached}
|
|
773
|
+
acquire { Native::Sandbox.start(sandbox_name, opts) }
|
|
709
774
|
end
|
|
710
775
|
|
|
711
776
|
# Fetch a controllable handle for a sandbox by name (running or not).
|
|
@@ -1998,10 +2063,16 @@ module Microsandbox
|
|
|
1998
2063
|
# {Sandbox} is live — stop it when done. Memoized: the native result can be
|
|
1999
2064
|
# taken only once, so a later call returns the same {Sandbox}, or re-raises
|
|
2000
2065
|
# the same error if the create failed.
|
|
2066
|
+
#
|
|
2067
|
+
# Interrupt-safe like {Sandbox.create}: if a `Timeout.timeout` (or another
|
|
2068
|
+
# `Thread#raise`/`Thread#kill`) arrives while this is waiting, the sandbox
|
|
2069
|
+
# the session produced is stopped before the exception propagates rather
|
|
2070
|
+
# than left running with nothing referring to it. The result is consumed
|
|
2071
|
+
# by then, so a later call raises instead of returning a sandbox.
|
|
2001
2072
|
# @return [Sandbox]
|
|
2002
2073
|
def sandbox
|
|
2003
2074
|
raise @failure if @failure
|
|
2004
|
-
@sandbox ||= Sandbox.
|
|
2075
|
+
@sandbox ||= Sandbox.acquire { @native.result }
|
|
2005
2076
|
rescue Microsandbox::Error => e
|
|
2006
2077
|
@failure ||= e
|
|
2007
2078
|
raise
|
data/lib/microsandbox/version.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Microsandbox
|
|
|
8
8
|
# Versioning section of the README for the full gem-to-runtime map. Must equal
|
|
9
9
|
# the native ext's Cargo crate version (`Native.version`), enforced by
|
|
10
10
|
# spec/unit/version_spec.rb.
|
|
11
|
-
VERSION = "0.17.
|
|
11
|
+
VERSION = "0.17.2"
|
|
12
12
|
|
|
13
13
|
# The upstream microsandbox runtime release this gem build embeds — the `tag`
|
|
14
14
|
# pinned on the `microsandbox`/`microsandbox-network` git deps in
|
data/sig/microsandbox.rbs
CHANGED
|
@@ -82,6 +82,7 @@ module Microsandbox
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
class FsEntry
|
|
85
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
85
86
|
def path: () -> String
|
|
86
87
|
def name: () -> String
|
|
87
88
|
def type: () -> Symbol
|
|
@@ -94,6 +95,7 @@ module Microsandbox
|
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
class FsMetadata
|
|
98
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
97
99
|
def type: () -> Symbol
|
|
98
100
|
def size: () -> Integer
|
|
99
101
|
def mode: () -> Integer
|
|
@@ -106,6 +108,7 @@ module Microsandbox
|
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
class FS
|
|
111
|
+
def initialize: (untyped native) -> void
|
|
109
112
|
def read: (String path) -> String
|
|
110
113
|
def read_text: (String path) -> String
|
|
111
114
|
def write: (String path, String data) -> nil
|
|
@@ -125,17 +128,20 @@ module Microsandbox
|
|
|
125
128
|
|
|
126
129
|
class FsReadStream
|
|
127
130
|
include Enumerable[String]
|
|
131
|
+
def initialize: (untyped native) -> void
|
|
128
132
|
def each: () { (String) -> void } -> self
|
|
129
133
|
| () -> Enumerator[String, self]
|
|
130
134
|
def read: () -> String
|
|
131
135
|
end
|
|
132
136
|
|
|
133
137
|
class FsWriteSink
|
|
138
|
+
def initialize: (untyped native) -> void
|
|
134
139
|
def write: (String data) -> self
|
|
135
140
|
def close: () -> nil
|
|
136
141
|
end
|
|
137
142
|
|
|
138
143
|
class Metrics
|
|
144
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
139
145
|
def cpu_percent: () -> Float
|
|
140
146
|
def vcpu_time_ns: () -> Integer
|
|
141
147
|
def memory_bytes: () -> Integer
|
|
@@ -154,6 +160,7 @@ module Microsandbox
|
|
|
154
160
|
end
|
|
155
161
|
|
|
156
162
|
class LogEntry
|
|
163
|
+
def initialize: (Hash[String, untyped] entry) -> void
|
|
157
164
|
def source: () -> Symbol
|
|
158
165
|
def session_id: () -> Integer?
|
|
159
166
|
def cursor: () -> String
|
|
@@ -163,6 +170,7 @@ module Microsandbox
|
|
|
163
170
|
end
|
|
164
171
|
|
|
165
172
|
class SandboxHandle
|
|
173
|
+
def initialize: (untyped native) -> void
|
|
166
174
|
def name: () -> String
|
|
167
175
|
def id: () -> String
|
|
168
176
|
def status: () -> Symbol
|
|
@@ -200,6 +208,7 @@ module Microsandbox
|
|
|
200
208
|
SandboxInfo: singleton(SandboxHandle)
|
|
201
209
|
|
|
202
210
|
class SandboxStopResult
|
|
211
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
203
212
|
def name: () -> String
|
|
204
213
|
def status: () -> Symbol
|
|
205
214
|
def exit_code: () -> Integer?
|
|
@@ -290,6 +299,7 @@ module Microsandbox
|
|
|
290
299
|
def self.list_with: (?labels: Hash[untyped, untyped], ?limit: Integer?, ?cursor: String?) -> SandboxPage
|
|
291
300
|
def self.remove: (String name) -> nil
|
|
292
301
|
|
|
302
|
+
def initialize: (untyped native) -> void
|
|
293
303
|
def name: () -> String
|
|
294
304
|
def id: () -> String
|
|
295
305
|
def exec: (String command, ?Array[String] args, ?cwd: String?, ?user: String?,
|
|
@@ -354,17 +364,20 @@ module Microsandbox
|
|
|
354
364
|
|
|
355
365
|
class LogStream
|
|
356
366
|
include Enumerable[LogEntry]
|
|
367
|
+
def initialize: (untyped native) -> void
|
|
357
368
|
def each: () { (LogEntry) -> void } -> self
|
|
358
369
|
| () -> Enumerator[LogEntry, self]
|
|
359
370
|
end
|
|
360
371
|
|
|
361
372
|
class MetricsStream
|
|
362
373
|
include Enumerable[Metrics]
|
|
374
|
+
def initialize: (untyped native) -> void
|
|
363
375
|
def each: () { (Metrics) -> void } -> self
|
|
364
376
|
| () -> Enumerator[Metrics, self]
|
|
365
377
|
end
|
|
366
378
|
|
|
367
379
|
class ExecEvent
|
|
380
|
+
def initialize: (Hash[String, untyped] event) -> void
|
|
368
381
|
def type: () -> Symbol
|
|
369
382
|
def pid: () -> Integer?
|
|
370
383
|
def code: () -> Integer?
|
|
@@ -379,18 +392,21 @@ module Microsandbox
|
|
|
379
392
|
end
|
|
380
393
|
|
|
381
394
|
class ExitStatus
|
|
395
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
382
396
|
def exit_code: () -> Integer?
|
|
383
397
|
def success?: () -> bool
|
|
384
398
|
def failure?: () -> bool
|
|
385
399
|
end
|
|
386
400
|
|
|
387
401
|
class ExecStdin
|
|
402
|
+
def initialize: (untyped native) -> void
|
|
388
403
|
def write: (String data) -> self
|
|
389
404
|
def close: () -> nil
|
|
390
405
|
end
|
|
391
406
|
|
|
392
407
|
class ExecHandle
|
|
393
408
|
include Enumerable[ExecEvent]
|
|
409
|
+
def initialize: (untyped native) -> void
|
|
394
410
|
def id: () -> String
|
|
395
411
|
def each: () { (ExecEvent) -> void } -> self
|
|
396
412
|
| () -> Enumerator[ExecEvent, self]
|
|
@@ -403,6 +419,7 @@ module Microsandbox
|
|
|
403
419
|
end
|
|
404
420
|
|
|
405
421
|
class ImageInfo
|
|
422
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
406
423
|
def reference: () -> String
|
|
407
424
|
def size_bytes: () -> Integer?
|
|
408
425
|
def manifest_digest: () -> String?
|
|
@@ -414,6 +431,7 @@ module Microsandbox
|
|
|
414
431
|
end
|
|
415
432
|
|
|
416
433
|
class ImageDetail
|
|
434
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
417
435
|
def handle: () -> ImageInfo
|
|
418
436
|
def config: () -> Hash[String, untyped]?
|
|
419
437
|
def layers: () -> Array[Hash[String, untyped]]
|
|
@@ -421,6 +439,7 @@ module Microsandbox
|
|
|
421
439
|
end
|
|
422
440
|
|
|
423
441
|
class ImagePruneReport
|
|
442
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
424
443
|
def image_refs_removed: () -> Integer
|
|
425
444
|
def manifests_removed: () -> Integer
|
|
426
445
|
def layers_removed: () -> Integer
|
|
@@ -441,6 +460,7 @@ module Microsandbox
|
|
|
441
460
|
end
|
|
442
461
|
|
|
443
462
|
class VolumeInfo
|
|
463
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
444
464
|
def name: () -> String
|
|
445
465
|
def path: () -> String?
|
|
446
466
|
def kind: () -> Symbol?
|
|
@@ -481,6 +501,7 @@ module Microsandbox
|
|
|
481
501
|
end
|
|
482
502
|
|
|
483
503
|
class SnapshotInfo
|
|
504
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
484
505
|
def digest: () -> String
|
|
485
506
|
def path: () -> String
|
|
486
507
|
def name: () -> String?
|
|
@@ -506,6 +527,7 @@ module Microsandbox
|
|
|
506
527
|
end
|
|
507
528
|
|
|
508
529
|
class SnapshotVerifyReport
|
|
530
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
509
531
|
def digest: () -> String
|
|
510
532
|
def path: () -> String
|
|
511
533
|
def status: () -> Symbol
|
|
@@ -569,6 +591,7 @@ module Microsandbox
|
|
|
569
591
|
end
|
|
570
592
|
|
|
571
593
|
class BackendInfo
|
|
594
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
572
595
|
def kind: () -> Symbol
|
|
573
596
|
def api_url: () -> String?
|
|
574
597
|
def source: () -> Symbol
|
|
@@ -643,6 +666,7 @@ module Microsandbox
|
|
|
643
666
|
|
|
644
667
|
class AgentStream
|
|
645
668
|
include Enumerable[AgentFrame]
|
|
669
|
+
def initialize: (untyped native, Integer id, Integer handle) -> void
|
|
646
670
|
def id: () -> Integer
|
|
647
671
|
def recv: () -> AgentFrame?
|
|
648
672
|
def each: () { (AgentFrame) -> void } -> self
|