microsandbox-rb 0.17.1 → 0.17.3
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 +138 -8
- data/Cargo.lock +1 -1
- data/DESIGN.md +183 -36
- data/README.md +8 -7
- data/SECURITY.md +1 -1
- data/docs/errors.md +1 -0
- data/docs/execution.md +8 -1
- data/docs/lifecycle.md +33 -0
- data/docs/releasing.md +85 -34
- data/docs/runtime.md +88 -1
- data/docs/surface.md +16 -8
- data/ext/microsandbox/Cargo.toml +9 -5
- data/ext/microsandbox/src/agent.rs +26 -21
- data/ext/microsandbox/src/backend.rs +291 -34
- data/ext/microsandbox/src/error.rs +18 -0
- data/ext/microsandbox/src/exec.rs +39 -21
- data/ext/microsandbox/src/fs_stream.rs +30 -15
- data/ext/microsandbox/src/host_path.rs +4 -3
- data/ext/microsandbox/src/image.rs +2 -2
- data/ext/microsandbox/src/lib.rs +3 -2
- data/ext/microsandbox/src/runtime.rs +314 -47
- data/ext/microsandbox/src/sandbox.rs +225 -124
- data/ext/microsandbox/src/snapshot.rs +24 -15
- data/ext/microsandbox/src/ssh.rs +48 -39
- data/ext/microsandbox/src/stream.rs +19 -9
- data/ext/microsandbox/src/volume.rs +40 -32
- data/lib/microsandbox/errors.rb +8 -0
- data/lib/microsandbox/sandbox.rb +75 -4
- data/lib/microsandbox/version.rb +1 -1
- data/lib/microsandbox.rb +20 -1
- data/sig/microsandbox.rbs +25 -0
- metadata +1 -1
|
@@ -41,7 +41,7 @@ use crate::conv;
|
|
|
41
41
|
use crate::error;
|
|
42
42
|
use crate::exec::ExecHandle;
|
|
43
43
|
use crate::host_path::{self, HostPaths};
|
|
44
|
-
use crate::runtime::{block_on, ruby};
|
|
44
|
+
use crate::runtime::{block_on, ruby, ForkGuarded};
|
|
45
45
|
use crate::stream::{LogStream, MetricsStream};
|
|
46
46
|
|
|
47
47
|
/// Convert a seconds `f64` into a `Duration`, surfacing a clean Ruby error
|
|
@@ -61,12 +61,14 @@ fn secs_to_duration(secs: f64) -> Result<Duration, Error> {
|
|
|
61
61
|
|
|
62
62
|
#[magnus::wrap(class = "Microsandbox::Native::Sandbox", free_immediately, size)]
|
|
63
63
|
pub struct Sandbox {
|
|
64
|
-
inner: microsandbox::Sandbox
|
|
64
|
+
inner: ForkGuarded<microsandbox::Sandbox>,
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
impl Sandbox {
|
|
68
68
|
fn from_inner(inner: microsandbox::Sandbox) -> Self {
|
|
69
|
-
Self {
|
|
69
|
+
Self {
|
|
70
|
+
inner: ForkGuarded::new("Microsandbox::Sandbox", inner),
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
//----------------------------------------------------------------------
|
|
@@ -76,10 +78,13 @@ impl Sandbox {
|
|
|
76
78
|
/// Build a configured `SandboxBuilder` from a string-keyed options Hash.
|
|
77
79
|
/// Shared by `create` (blocking) and `create_with_progress` (streaming pull).
|
|
78
80
|
fn build_builder(name: String, opts: RHash) -> Result<SandboxBuilder, Error> {
|
|
81
|
+
// `Sandbox::builder` reads the default backend synchronously (it
|
|
82
|
+
// overlays the local backend's global config), before any `block_on`.
|
|
83
|
+
crate::backend::ensure_this_process_backend()?;
|
|
79
84
|
let mut b = microsandbox::Sandbox::builder(name);
|
|
80
85
|
// Relative host paths are anchored to the current directory here, once,
|
|
81
86
|
// before the core persists them (see host_path.rs).
|
|
82
|
-
let host_paths = HostPaths::for_default_backend()
|
|
87
|
+
let host_paths = HostPaths::for_default_backend()?;
|
|
83
88
|
|
|
84
89
|
if let Some(v) = conv::opt_string(opts, "image")? {
|
|
85
90
|
if let Some(fstype) = conv::opt_string(opts, "fstype")? {
|
|
@@ -546,7 +551,7 @@ impl Sandbox {
|
|
|
546
551
|
/// Create and boot a sandbox. `opts` is a string-keyed options Hash.
|
|
547
552
|
fn create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
548
553
|
let b = Self::build_builder(name, opts)?;
|
|
549
|
-
let inner = block_on(b.create())
|
|
554
|
+
let inner = block_on(b.create())?.map_err(error::to_ruby)?;
|
|
550
555
|
Ok(Sandbox::from_inner(inner))
|
|
551
556
|
}
|
|
552
557
|
|
|
@@ -559,7 +564,7 @@ impl Sandbox {
|
|
|
559
564
|
// `create_with_pull_progress` spawns a tokio task, so it must run inside
|
|
560
565
|
// the runtime context even though the call itself is synchronous.
|
|
561
566
|
let (handle, join) =
|
|
562
|
-
block_on(async move { b.create_with_pull_progress() })
|
|
567
|
+
block_on(async move { b.create_with_pull_progress() })?.map_err(error::to_ruby)?;
|
|
563
568
|
Ok(PullSession::new(handle, join))
|
|
564
569
|
}
|
|
565
570
|
|
|
@@ -569,7 +574,7 @@ impl Sandbox {
|
|
|
569
574
|
/// the combination with `replace`.
|
|
570
575
|
fn connect_or_create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
571
576
|
let b = Self::build_builder(name, opts)?;
|
|
572
|
-
let inner = block_on(b.connect_or_create())
|
|
577
|
+
let inner = block_on(b.connect_or_create())?.map_err(error::to_ruby)?;
|
|
573
578
|
Ok(Sandbox::from_inner(inner))
|
|
574
579
|
}
|
|
575
580
|
|
|
@@ -577,9 +582,9 @@ impl Sandbox {
|
|
|
577
582
|
fn start(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
578
583
|
let detached = conv::opt_bool(opts, "detached")?;
|
|
579
584
|
let inner = if detached {
|
|
580
|
-
block_on(microsandbox::Sandbox::start_detached(&name))
|
|
585
|
+
block_on(microsandbox::Sandbox::start_detached(&name))?.map_err(error::to_ruby)?
|
|
581
586
|
} else {
|
|
582
|
-
block_on(microsandbox::Sandbox::start(&name))
|
|
587
|
+
block_on(microsandbox::Sandbox::start(&name))?.map_err(error::to_ruby)?
|
|
583
588
|
};
|
|
584
589
|
Ok(Sandbox::from_inner(inner))
|
|
585
590
|
}
|
|
@@ -587,7 +592,7 @@ impl Sandbox {
|
|
|
587
592
|
/// A controllable handle for a sandbox by name (running or not). Carries
|
|
588
593
|
/// metadata accessors and the full lifecycle surface (see `SbHandle`).
|
|
589
594
|
fn get(name: String) -> Result<SbHandle, Error> {
|
|
590
|
-
let handle = block_on(microsandbox::Sandbox::get(&name))
|
|
595
|
+
let handle = block_on(microsandbox::Sandbox::get(&name))?.map_err(error::to_ruby)?;
|
|
591
596
|
Ok(SbHandle::from_inner(handle))
|
|
592
597
|
}
|
|
593
598
|
|
|
@@ -607,7 +612,7 @@ impl Sandbox {
|
|
|
607
612
|
/// First page of sandboxes (default page size), as a page Hash of
|
|
608
613
|
/// controllable handles.
|
|
609
614
|
fn list() -> Result<RHash, Error> {
|
|
610
|
-
let page = block_on(microsandbox::Sandbox::list())
|
|
615
|
+
let page = block_on(microsandbox::Sandbox::list())?.map_err(error::to_ruby)?;
|
|
611
616
|
Self::page_to_hash(page)
|
|
612
617
|
}
|
|
613
618
|
|
|
@@ -626,44 +631,48 @@ impl Sandbox {
|
|
|
626
631
|
b = b.cursor(cursor);
|
|
627
632
|
}
|
|
628
633
|
b.labels(labels)
|
|
629
|
-
}))
|
|
634
|
+
}))?
|
|
630
635
|
.map_err(error::to_ruby)?;
|
|
631
636
|
Self::page_to_hash(page)
|
|
632
637
|
}
|
|
633
638
|
|
|
634
639
|
/// Remove a (stopped) sandbox by name.
|
|
635
640
|
fn remove(name: String) -> Result<(), Error> {
|
|
636
|
-
block_on(microsandbox::Sandbox::remove(&name))
|
|
641
|
+
block_on(microsandbox::Sandbox::remove(&name))?.map_err(error::to_ruby)
|
|
637
642
|
}
|
|
638
643
|
|
|
639
644
|
//----------------------------------------------------------------------
|
|
640
645
|
// Instance methods
|
|
641
646
|
//----------------------------------------------------------------------
|
|
642
647
|
|
|
648
|
+
/// Plain data, so (like `id`) it stays readable in a forked child, which
|
|
649
|
+
/// needs the name to open a handle of its own (`ForkGuarded::peek`).
|
|
643
650
|
fn name(&self) -> String {
|
|
644
|
-
self.inner.name().to_string()
|
|
651
|
+
self.inner.peek().name().to_string()
|
|
645
652
|
}
|
|
646
653
|
|
|
647
654
|
/// The opaque, backend-assigned identity of this persisted sandbox
|
|
648
655
|
/// (v0.6.16). Stable for the sandbox's lifetime, and different once the
|
|
649
656
|
/// same name is removed and recreated.
|
|
650
657
|
fn id(&self) -> String {
|
|
651
|
-
self.inner.id().to_string()
|
|
658
|
+
self.inner.peek().id().to_string()
|
|
652
659
|
}
|
|
653
660
|
|
|
654
661
|
/// Run a command (no shell). `args` is an Array of strings; `opts` is a
|
|
655
662
|
/// string-keyed Hash (cwd, user, env, timeout, tty, stdin).
|
|
656
663
|
fn exec(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<RHash, Error> {
|
|
664
|
+
let inner = self.inner.get()?;
|
|
657
665
|
let parsed = ExecOpts::parse(args, opts)?;
|
|
658
|
-
let output =
|
|
659
|
-
.map_err(error::to_ruby)?;
|
|
666
|
+
let output =
|
|
667
|
+
block_on(inner.exec_with(cmd, move |b| parsed.apply(b)))?.map_err(error::to_ruby)?;
|
|
660
668
|
exec_output_to_hash(output)
|
|
661
669
|
}
|
|
662
670
|
|
|
663
671
|
/// Run a shell script (pipes/redirects allowed).
|
|
664
672
|
fn shell(&self, script: String, opts: RHash) -> Result<RHash, Error> {
|
|
673
|
+
let inner = self.inner.get()?;
|
|
665
674
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
666
|
-
let output = block_on(
|
|
675
|
+
let output = block_on(inner.shell_with(script, move |b| parsed.apply(b)))?
|
|
667
676
|
.map_err(error::to_ruby)?;
|
|
668
677
|
exec_output_to_hash(output)
|
|
669
678
|
}
|
|
@@ -675,8 +684,9 @@ impl Sandbox {
|
|
|
675
684
|
args: Vec<String>,
|
|
676
685
|
opts: RHash,
|
|
677
686
|
) -> Result<ExecHandle, Error> {
|
|
687
|
+
let inner = self.inner.get()?;
|
|
678
688
|
let parsed = ExecOpts::parse(args, opts)?;
|
|
679
|
-
let handle = block_on(
|
|
689
|
+
let handle = block_on(inner.exec_stream_with(cmd, move |b| parsed.apply(b)))?
|
|
680
690
|
.map_err(error::to_ruby)?;
|
|
681
691
|
Ok(ExecHandle::from_core(handle))
|
|
682
692
|
}
|
|
@@ -687,31 +697,28 @@ impl Sandbox {
|
|
|
687
697
|
/// the command: cwd, user, env, timeout, tty, stdin, rlimits. Raises
|
|
688
698
|
/// NoDefaultCommandError when the image resolves no executable command.
|
|
689
699
|
fn exec_default(&self, opts: RHash) -> Result<RHash, Error> {
|
|
700
|
+
let inner = self.inner.get()?;
|
|
690
701
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
691
|
-
let output =
|
|
692
|
-
.map_err(error::to_ruby)?;
|
|
702
|
+
let output =
|
|
703
|
+
block_on(inner.exec_default_with(move |b| parsed.apply(b)))?.map_err(error::to_ruby)?;
|
|
693
704
|
exec_output_to_hash(output)
|
|
694
705
|
}
|
|
695
706
|
|
|
696
707
|
/// Streaming default-workload execution. Returns an ExecHandle.
|
|
697
708
|
fn exec_default_stream(&self, opts: RHash) -> Result<ExecHandle, Error> {
|
|
709
|
+
let inner = self.inner.get()?;
|
|
698
710
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
699
|
-
let handle = block_on(
|
|
700
|
-
|
|
701
|
-
.exec_default_stream_with(move |b| parsed.apply(b)),
|
|
702
|
-
)
|
|
703
|
-
.map_err(error::to_ruby)?;
|
|
711
|
+
let handle = block_on(inner.exec_default_stream_with(move |b| parsed.apply(b)))?
|
|
712
|
+
.map_err(error::to_ruby)?;
|
|
704
713
|
Ok(ExecHandle::from_core(handle))
|
|
705
714
|
}
|
|
706
715
|
|
|
707
716
|
/// Streaming shell execution.
|
|
708
717
|
fn shell_stream(&self, script: String, opts: RHash) -> Result<ExecHandle, Error> {
|
|
718
|
+
let inner = self.inner.get()?;
|
|
709
719
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
710
|
-
let handle = block_on(
|
|
711
|
-
|
|
712
|
-
.shell_stream_with(script, move |b| parsed.apply(b)),
|
|
713
|
-
)
|
|
714
|
-
.map_err(error::to_ruby)?;
|
|
720
|
+
let handle = block_on(inner.shell_stream_with(script, move |b| parsed.apply(b)))?
|
|
721
|
+
.map_err(error::to_ruby)?;
|
|
715
722
|
Ok(ExecHandle::from_core(handle))
|
|
716
723
|
}
|
|
717
724
|
|
|
@@ -731,29 +738,34 @@ impl Sandbox {
|
|
|
731
738
|
/// Fine-grained control — a custom timeout or fire-and-return `request_*` —
|
|
732
739
|
/// lives on `SandboxHandle`, obtained via `Sandbox.get`.
|
|
733
740
|
fn stop(&self) -> Result<(), Error> {
|
|
734
|
-
|
|
741
|
+
let inner = self.inner.get()?;
|
|
742
|
+
block_on(inner.stop())?.map_err(error::to_ruby)
|
|
735
743
|
}
|
|
736
744
|
|
|
737
745
|
/// Graceful stop, then wait for the process to exit. Returns an exit-status
|
|
738
746
|
/// Hash (`exit_code`, `success`). Local backend only.
|
|
739
747
|
fn stop_and_wait(&self) -> Result<RHash, Error> {
|
|
740
|
-
let
|
|
748
|
+
let inner = self.inner.get()?;
|
|
749
|
+
let status = block_on(inner.stop_and_wait())?.map_err(error::to_ruby)?;
|
|
741
750
|
Ok(exit_status_to_hash(status))
|
|
742
751
|
}
|
|
743
752
|
|
|
744
753
|
/// Force kill (SIGKILL).
|
|
745
754
|
fn kill(&self) -> Result<(), Error> {
|
|
746
|
-
|
|
755
|
+
let inner = self.inner.get()?;
|
|
756
|
+
block_on(inner.kill())?.map_err(error::to_ruby)
|
|
747
757
|
}
|
|
748
758
|
|
|
749
759
|
/// Trigger a graceful drain (SIGUSR1 on local).
|
|
750
760
|
fn drain(&self) -> Result<(), Error> {
|
|
751
|
-
|
|
761
|
+
let inner = self.inner.get()?;
|
|
762
|
+
block_on(inner.drain())?.map_err(error::to_ruby)
|
|
752
763
|
}
|
|
753
764
|
|
|
754
765
|
/// Wait for the process to exit. Returns an exit-status Hash. Local only.
|
|
755
766
|
fn wait(&self) -> Result<RHash, Error> {
|
|
756
|
-
let
|
|
767
|
+
let inner = self.inner.get()?;
|
|
768
|
+
let status = block_on(inner.wait())?.map_err(error::to_ruby)?;
|
|
757
769
|
Ok(exit_status_to_hash(status))
|
|
758
770
|
}
|
|
759
771
|
|
|
@@ -761,35 +773,42 @@ impl Sandbox {
|
|
|
761
773
|
/// returning a fresh handle. A same-name replacement raises
|
|
762
774
|
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
763
775
|
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
776
|
+
let inner = self.inner.get()?;
|
|
764
777
|
let status = sandbox_status_from_str(&status)?;
|
|
765
|
-
let handle = block_on(
|
|
778
|
+
let handle = block_on(inner.wait_for_status(status))?.map_err(error::to_ruby)?;
|
|
766
779
|
Ok(SbHandle::from_inner(handle))
|
|
767
780
|
}
|
|
768
781
|
|
|
769
782
|
/// Stop and start this exact sandbox, returning the new live sandbox.
|
|
770
783
|
/// `opts` carries `force`/`timeout`/`detached`.
|
|
771
784
|
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
785
|
+
let inner = self.inner.get()?;
|
|
772
786
|
let options = restart_options(opts)?;
|
|
773
|
-
let inner = block_on(
|
|
787
|
+
let inner = block_on(inner.restart_with(options))?.map_err(error::to_ruby)?;
|
|
774
788
|
Ok(Sandbox::from_inner(inner))
|
|
775
789
|
}
|
|
776
790
|
|
|
777
791
|
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
778
792
|
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
793
|
+
let inner = self.inner.get()?;
|
|
779
794
|
let options = destroy_options(opts)?;
|
|
780
|
-
block_on(
|
|
795
|
+
block_on(inner.destroy_with(options))?.map_err(error::to_ruby)
|
|
781
796
|
}
|
|
782
797
|
|
|
783
798
|
/// Live status fetched from the backend (a round-trip per call).
|
|
784
799
|
fn status(&self) -> Result<String, Error> {
|
|
785
|
-
let
|
|
800
|
+
let inner = self.inner.get()?;
|
|
801
|
+
let status = block_on(inner.status())?.map_err(error::to_ruby)?;
|
|
786
802
|
Ok(sandbox_status_str(status).to_string())
|
|
787
803
|
}
|
|
788
804
|
|
|
789
805
|
/// Whether this handle owns the sandbox process lifecycle (a synchronous,
|
|
790
|
-
/// local predicate — no runtime round-trip).
|
|
791
|
-
|
|
792
|
-
|
|
806
|
+
/// local predicate — no runtime round-trip). Process-checked even though
|
|
807
|
+
/// it only reads memory: in a forked child the inherited answer would be
|
|
808
|
+
/// wrong (the parent owns the sandbox), so it raises like the lifecycle
|
|
809
|
+
/// calls it guards.
|
|
810
|
+
fn owns_lifecycle(&self) -> Result<bool, Error> {
|
|
811
|
+
Ok(self.inner.get()?.owns_lifecycle())
|
|
793
812
|
}
|
|
794
813
|
|
|
795
814
|
/// Disarm the SIGTERM safety net so the sandbox keeps running after this
|
|
@@ -797,14 +816,15 @@ impl Sandbox {
|
|
|
797
816
|
/// `Sandbox` is `Clone` (Arc-backed, sharing the same process handle), so
|
|
798
817
|
/// detaching a clone disarms the shared handle just the same.
|
|
799
818
|
fn detach(&self) -> Result<(), Error> {
|
|
800
|
-
let inner = self.inner.clone();
|
|
801
|
-
block_on(inner.detach())
|
|
819
|
+
let inner = self.inner.get()?.clone();
|
|
820
|
+
block_on(inner.detach())?;
|
|
802
821
|
Ok(())
|
|
803
822
|
}
|
|
804
823
|
|
|
805
824
|
/// Latest metrics snapshot as a Hash.
|
|
806
825
|
fn metrics(&self) -> Result<RHash, Error> {
|
|
807
|
-
let
|
|
826
|
+
let inner = self.inner.get()?;
|
|
827
|
+
let m = block_on(inner.metrics())?.map_err(error::to_ruby)?;
|
|
808
828
|
Ok(metrics_to_hash(&m))
|
|
809
829
|
}
|
|
810
830
|
|
|
@@ -816,14 +836,16 @@ impl Sandbox {
|
|
|
816
836
|
/// `{ name, latency_secs }` (round-trip latency as f64 seconds; the Ruby
|
|
817
837
|
/// layer exposes both seconds and milliseconds).
|
|
818
838
|
fn ping(&self) -> Result<RHash, Error> {
|
|
819
|
-
let
|
|
839
|
+
let inner = self.inner.get()?;
|
|
840
|
+
let result = block_on(inner.ping())?.map_err(error::to_ruby)?;
|
|
820
841
|
Ok(ping_result_to_hash(&result))
|
|
821
842
|
}
|
|
822
843
|
|
|
823
844
|
/// Explicitly refresh the idle-activity timer. Returns
|
|
824
845
|
/// `{ name, activity_seq }`.
|
|
825
846
|
fn touch(&self) -> Result<RHash, Error> {
|
|
826
|
-
let
|
|
847
|
+
let inner = self.inner.get()?;
|
|
848
|
+
let result = block_on(inner.touch())?.map_err(error::to_ruby)?;
|
|
827
849
|
Ok(touch_result_to_hash(&result))
|
|
828
850
|
}
|
|
829
851
|
|
|
@@ -833,20 +855,23 @@ impl Sandbox {
|
|
|
833
855
|
/// into a `ModificationPlan`. Mirrors the Node native binding (native returns
|
|
834
856
|
/// JSON, the wrapper shapes it).
|
|
835
857
|
fn modify(&self, opts: RHash) -> Result<String, Error> {
|
|
836
|
-
|
|
858
|
+
let inner = self.inner.get()?;
|
|
859
|
+
run_modify(inner.modify(), opts)
|
|
837
860
|
}
|
|
838
861
|
|
|
839
862
|
/// Read captured logs as an Array of Hashes. `opts`: tail, since_ms,
|
|
840
863
|
/// until_ms, sources (Array of "stdout"/"stderr"/"output"/"system"/"all").
|
|
841
864
|
fn logs(&self, opts: RHash) -> Result<RArray, Error> {
|
|
865
|
+
let inner = self.inner.get()?;
|
|
842
866
|
let log_opts = parse_log_options(opts)?;
|
|
843
|
-
let entries = block_on(
|
|
867
|
+
let entries = block_on(inner.logs(&log_opts))?.map_err(error::to_ruby)?;
|
|
844
868
|
rhash_array(entries.iter().map(log_entry_to_hash))
|
|
845
869
|
}
|
|
846
870
|
|
|
847
871
|
/// Stream metrics snapshots at `interval` seconds. Returns a MetricsStream
|
|
848
872
|
/// to pull snapshots from.
|
|
849
873
|
fn metrics_stream(&self, interval: f64) -> Result<MetricsStream, Error> {
|
|
874
|
+
let inner = self.inner.get()?;
|
|
850
875
|
// `interval <= 0.0` (0 or negative) keeps the prior 1s-default behavior;
|
|
851
876
|
// NaN (for which `<= 0.0` is false) and finite-but-out-of-range values
|
|
852
877
|
// fall through to `secs_to_duration`, which errors cleanly rather than
|
|
@@ -860,15 +885,16 @@ impl Sandbox {
|
|
|
860
885
|
// which panics ("no reactor running") unless constructed inside the
|
|
861
886
|
// runtime context — so build it under `block_on`. (`log_stream` is async
|
|
862
887
|
// and already runs inside `block_on`, so it needs no such wrapper.)
|
|
863
|
-
let stream = block_on(async {
|
|
888
|
+
let stream = block_on(async { inner.metrics_stream(dur) })?;
|
|
864
889
|
Ok(MetricsStream::from_stream(stream))
|
|
865
890
|
}
|
|
866
891
|
|
|
867
892
|
/// Stream captured logs as they appear. `opts`: sources, since_ms,
|
|
868
893
|
/// from_cursor, until_ms, follow. Returns a LogStream.
|
|
869
894
|
fn log_stream(&self, opts: RHash) -> Result<LogStream, Error> {
|
|
895
|
+
let inner = self.inner.get()?;
|
|
870
896
|
let log_opts = parse_log_stream_options(opts)?;
|
|
871
|
-
let stream = block_on(
|
|
897
|
+
let stream = block_on(inner.log_stream(&log_opts))?.map_err(error::to_ruby)?;
|
|
872
898
|
Ok(LogStream::from_stream(stream))
|
|
873
899
|
}
|
|
874
900
|
|
|
@@ -877,87 +903,102 @@ impl Sandbox {
|
|
|
877
903
|
//----------------------------------------------------------------------
|
|
878
904
|
|
|
879
905
|
fn fs_read(&self, path: String) -> Result<RString, Error> {
|
|
880
|
-
let
|
|
881
|
-
let
|
|
906
|
+
let inner = self.inner.get()?;
|
|
907
|
+
let fs = inner.fs();
|
|
908
|
+
let bytes = block_on(fs.read(&path))?.map_err(error::to_ruby)?;
|
|
882
909
|
Ok(ruby().str_from_slice(bytes.as_ref()))
|
|
883
910
|
}
|
|
884
911
|
|
|
885
912
|
fn fs_read_text(&self, path: String) -> Result<String, Error> {
|
|
886
|
-
let
|
|
887
|
-
|
|
913
|
+
let inner = self.inner.get()?;
|
|
914
|
+
let fs = inner.fs();
|
|
915
|
+
block_on(fs.read_to_string(&path))?.map_err(error::to_ruby)
|
|
888
916
|
}
|
|
889
917
|
|
|
890
918
|
fn fs_write(&self, path: String, data: RString) -> Result<(), Error> {
|
|
919
|
+
let inner = self.inner.get()?;
|
|
891
920
|
// Copy out of the Ruby string while we still hold the GVL: the buffer
|
|
892
921
|
// could be moved/freed by GC.compact once block_on releases it.
|
|
893
922
|
let bytes = unsafe { data.as_slice() }.to_vec();
|
|
894
|
-
let fs =
|
|
895
|
-
block_on(fs.write(&path, &bytes))
|
|
923
|
+
let fs = inner.fs();
|
|
924
|
+
block_on(fs.write(&path, &bytes))?.map_err(error::to_ruby)
|
|
896
925
|
}
|
|
897
926
|
|
|
898
927
|
fn fs_list(&self, path: String) -> Result<RArray, Error> {
|
|
899
|
-
let
|
|
900
|
-
let
|
|
928
|
+
let inner = self.inner.get()?;
|
|
929
|
+
let fs = inner.fs();
|
|
930
|
+
let entries = block_on(fs.list(&path))?.map_err(error::to_ruby)?;
|
|
901
931
|
rhash_array(entries.iter().map(fs_entry_to_hash))
|
|
902
932
|
}
|
|
903
933
|
|
|
904
934
|
fn fs_mkdir(&self, path: String) -> Result<(), Error> {
|
|
905
|
-
let
|
|
906
|
-
|
|
935
|
+
let inner = self.inner.get()?;
|
|
936
|
+
let fs = inner.fs();
|
|
937
|
+
block_on(fs.mkdir(&path))?.map_err(error::to_ruby)
|
|
907
938
|
}
|
|
908
939
|
|
|
909
940
|
fn fs_remove(&self, path: String) -> Result<(), Error> {
|
|
910
|
-
let
|
|
911
|
-
|
|
941
|
+
let inner = self.inner.get()?;
|
|
942
|
+
let fs = inner.fs();
|
|
943
|
+
block_on(fs.remove(&path))?.map_err(error::to_ruby)
|
|
912
944
|
}
|
|
913
945
|
|
|
914
946
|
fn fs_remove_dir(&self, path: String) -> Result<(), Error> {
|
|
915
|
-
let
|
|
916
|
-
|
|
947
|
+
let inner = self.inner.get()?;
|
|
948
|
+
let fs = inner.fs();
|
|
949
|
+
block_on(fs.remove_dir(&path))?.map_err(error::to_ruby)
|
|
917
950
|
}
|
|
918
951
|
|
|
919
952
|
fn fs_copy(&self, src: String, dst: String) -> Result<(), Error> {
|
|
920
|
-
let
|
|
921
|
-
|
|
953
|
+
let inner = self.inner.get()?;
|
|
954
|
+
let fs = inner.fs();
|
|
955
|
+
block_on(fs.copy(&src, &dst))?.map_err(error::to_ruby)
|
|
922
956
|
}
|
|
923
957
|
|
|
924
958
|
fn fs_rename(&self, src: String, dst: String) -> Result<(), Error> {
|
|
925
|
-
let
|
|
926
|
-
|
|
959
|
+
let inner = self.inner.get()?;
|
|
960
|
+
let fs = inner.fs();
|
|
961
|
+
block_on(fs.rename(&src, &dst))?.map_err(error::to_ruby)
|
|
927
962
|
}
|
|
928
963
|
|
|
929
964
|
fn fs_exists(&self, path: String) -> Result<bool, Error> {
|
|
930
|
-
let
|
|
931
|
-
|
|
965
|
+
let inner = self.inner.get()?;
|
|
966
|
+
let fs = inner.fs();
|
|
967
|
+
block_on(fs.exists(&path))?.map_err(error::to_ruby)
|
|
932
968
|
}
|
|
933
969
|
|
|
934
970
|
fn fs_stat(&self, path: String) -> Result<RHash, Error> {
|
|
935
|
-
let
|
|
936
|
-
let
|
|
971
|
+
let inner = self.inner.get()?;
|
|
972
|
+
let fs = inner.fs();
|
|
973
|
+
let meta = block_on(fs.stat(&path))?.map_err(error::to_ruby)?;
|
|
937
974
|
Ok(fs_metadata_to_hash(&meta))
|
|
938
975
|
}
|
|
939
976
|
|
|
940
977
|
fn fs_copy_from_host(&self, host_path: String, guest_path: String) -> Result<(), Error> {
|
|
941
|
-
let
|
|
942
|
-
|
|
978
|
+
let inner = self.inner.get()?;
|
|
979
|
+
let fs = inner.fs();
|
|
980
|
+
block_on(fs.copy_from_host(&host_path, &guest_path))?.map_err(error::to_ruby)
|
|
943
981
|
}
|
|
944
982
|
|
|
945
983
|
fn fs_copy_to_host(&self, guest_path: String, host_path: String) -> Result<(), Error> {
|
|
946
|
-
let
|
|
947
|
-
|
|
984
|
+
let inner = self.inner.get()?;
|
|
985
|
+
let fs = inner.fs();
|
|
986
|
+
block_on(fs.copy_to_host(&guest_path, &host_path))?.map_err(error::to_ruby)
|
|
948
987
|
}
|
|
949
988
|
|
|
950
989
|
/// Open a streaming reader over a guest file (for files too large to buffer).
|
|
951
990
|
fn fs_read_stream(&self, path: String) -> Result<crate::fs_stream::FsReadStreamHandle, Error> {
|
|
952
|
-
let
|
|
953
|
-
let
|
|
991
|
+
let inner = self.inner.get()?;
|
|
992
|
+
let fs = inner.fs();
|
|
993
|
+
let stream = block_on(fs.read_stream(&path))?.map_err(error::to_ruby)?;
|
|
954
994
|
Ok(crate::fs_stream::FsReadStreamHandle::new(stream))
|
|
955
995
|
}
|
|
956
996
|
|
|
957
997
|
/// Open a streaming writer to a guest file.
|
|
958
998
|
fn fs_write_stream(&self, path: String) -> Result<crate::fs_stream::FsWriteSinkHandle, Error> {
|
|
959
|
-
let
|
|
960
|
-
let
|
|
999
|
+
let inner = self.inner.get()?;
|
|
1000
|
+
let fs = inner.fs();
|
|
1001
|
+
let sink = block_on(fs.write_stream(&path))?.map_err(error::to_ruby)?;
|
|
961
1002
|
Ok(crate::fs_stream::FsWriteSinkHandle::new(sink))
|
|
962
1003
|
}
|
|
963
1004
|
|
|
@@ -968,13 +1009,14 @@ impl Sandbox {
|
|
|
968
1009
|
/// Open a native in-process SSH client to this sandbox. `opts`: user, term,
|
|
969
1010
|
/// sftp (bool, default true), inactivity_timeout (seconds, f64; 0 disables).
|
|
970
1011
|
fn ssh_open_client(&self, opts: RHash) -> Result<crate::ssh::SshClient, Error> {
|
|
1012
|
+
let inner = self.inner.get()?;
|
|
971
1013
|
let user = conv::opt_string(opts, "user")?;
|
|
972
1014
|
let term = conv::opt_string(opts, "term")?;
|
|
973
1015
|
let sftp = conv::opt::<bool>(opts, "sftp")?.unwrap_or(true);
|
|
974
1016
|
let inactivity_timeout = conv::opt_f64(opts, "inactivity_timeout")?
|
|
975
1017
|
.map(secs_to_duration)
|
|
976
1018
|
.transpose()?;
|
|
977
|
-
let ssh =
|
|
1019
|
+
let ssh = inner.ssh();
|
|
978
1020
|
let client = block_on(ssh.open_client_with(move |mut b| {
|
|
979
1021
|
if let Some(u) = user {
|
|
980
1022
|
b = b.user(u);
|
|
@@ -986,7 +1028,7 @@ impl Sandbox {
|
|
|
986
1028
|
b = b.inactivity_timeout(t);
|
|
987
1029
|
}
|
|
988
1030
|
b.sftp(sftp)
|
|
989
|
-
}))
|
|
1031
|
+
}))?
|
|
990
1032
|
.map_err(error::to_ruby)?;
|
|
991
1033
|
Ok(crate::ssh::SshClient::from_core(client))
|
|
992
1034
|
}
|
|
@@ -995,6 +1037,7 @@ impl Sandbox {
|
|
|
995
1037
|
/// authorized_keys_path, user, sftp (bool, default true),
|
|
996
1038
|
/// inactivity_timeout (seconds, f64; 0 disables).
|
|
997
1039
|
fn ssh_prepare_server(&self, opts: RHash) -> Result<crate::ssh::SshServer, Error> {
|
|
1040
|
+
let inner = self.inner.get()?;
|
|
998
1041
|
let host_key_path = conv::opt_string(opts, "host_key_path")?;
|
|
999
1042
|
let authorized_keys_path = conv::opt_string(opts, "authorized_keys_path")?;
|
|
1000
1043
|
let user = conv::opt_string(opts, "user")?;
|
|
@@ -1002,7 +1045,7 @@ impl Sandbox {
|
|
|
1002
1045
|
let inactivity_timeout = conv::opt_f64(opts, "inactivity_timeout")?
|
|
1003
1046
|
.map(secs_to_duration)
|
|
1004
1047
|
.transpose()?;
|
|
1005
|
-
let ssh =
|
|
1048
|
+
let ssh = inner.ssh();
|
|
1006
1049
|
let server = block_on(ssh.prepare_server_with(move |mut b| {
|
|
1007
1050
|
if let Some(p) = host_key_path {
|
|
1008
1051
|
b = b.host_key_path(p);
|
|
@@ -1017,7 +1060,7 @@ impl Sandbox {
|
|
|
1017
1060
|
b = b.inactivity_timeout(t);
|
|
1018
1061
|
}
|
|
1019
1062
|
b.sftp(sftp)
|
|
1020
|
-
}))
|
|
1063
|
+
}))?
|
|
1021
1064
|
.map_err(error::to_ruby)?;
|
|
1022
1065
|
Ok(crate::ssh::SshServer::from_core(server))
|
|
1023
1066
|
}
|
|
@@ -1031,21 +1074,24 @@ impl Sandbox {
|
|
|
1031
1074
|
/// blocks until the command exits or the detach sequence is typed. `opts`:
|
|
1032
1075
|
/// cwd, user, env, detach_keys, rlimits.
|
|
1033
1076
|
fn attach(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<i32, Error> {
|
|
1077
|
+
let inner = self.inner.get()?;
|
|
1034
1078
|
let parsed = AttachOpts::parse(args, opts)?;
|
|
1035
|
-
block_on(
|
|
1079
|
+
block_on(inner.attach_with(cmd, move |b| parsed.apply(b)))?.map_err(error::to_ruby)
|
|
1036
1080
|
}
|
|
1037
1081
|
|
|
1038
1082
|
/// Attach an interactive terminal running the sandbox's default shell.
|
|
1039
1083
|
fn attach_shell(&self) -> Result<i32, Error> {
|
|
1040
|
-
|
|
1084
|
+
let inner = self.inner.get()?;
|
|
1085
|
+
block_on(inner.attach_shell())?.map_err(error::to_ruby)
|
|
1041
1086
|
}
|
|
1042
1087
|
|
|
1043
1088
|
/// Attach an interactive terminal to the image's resolved OCI ENTRYPOINT
|
|
1044
1089
|
/// and CMD (the default workload, v0.6.9); returns its exit code. `opts`:
|
|
1045
1090
|
/// cwd, user, env, detach_keys, rlimits.
|
|
1046
1091
|
fn attach_default(&self, opts: RHash) -> Result<i32, Error> {
|
|
1092
|
+
let inner = self.inner.get()?;
|
|
1047
1093
|
let parsed = AttachOpts::parse(Vec::new(), opts)?;
|
|
1048
|
-
block_on(
|
|
1094
|
+
block_on(inner.attach_default_with(move |b| parsed.apply(b)))?.map_err(error::to_ruby)
|
|
1049
1095
|
}
|
|
1050
1096
|
}
|
|
1051
1097
|
|
|
@@ -2373,7 +2419,7 @@ fn run_modify(builder: SandboxModificationBuilder, opts: RHash) -> Result<String
|
|
|
2373
2419
|
} else {
|
|
2374
2420
|
builder.apply().await
|
|
2375
2421
|
}
|
|
2376
|
-
})
|
|
2422
|
+
})?
|
|
2377
2423
|
.map_err(error::to_ruby)?;
|
|
2378
2424
|
serde_json::to_string(&plan)
|
|
2379
2425
|
.map_err(|e| error::base_error(format!("failed to serialize modification plan: {e}")))
|
|
@@ -2583,6 +2629,12 @@ fn pull_progress_to_hash(p: &PullProgress) -> RHash {
|
|
|
2583
2629
|
/// handle is consumed once, so both sit behind a `tokio::Mutex`.
|
|
2584
2630
|
#[magnus::wrap(class = "Microsandbox::Native::PullSession", free_immediately, size)]
|
|
2585
2631
|
pub struct PullSession {
|
|
2632
|
+
inner: ForkGuarded<PullParts>,
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
/// The two halves of a pull session; both belong to the creating process's
|
|
2636
|
+
/// runtime (a channel fed by, and the join handle of, a task spawned on it).
|
|
2637
|
+
struct PullParts {
|
|
2586
2638
|
progress: Arc<Mutex<PullProgressHandle>>,
|
|
2587
2639
|
join: Arc<Mutex<Option<JoinHandle<MicrosandboxResult<microsandbox::Sandbox>>>>>,
|
|
2588
2640
|
}
|
|
@@ -2593,25 +2645,41 @@ impl PullSession {
|
|
|
2593
2645
|
join: JoinHandle<MicrosandboxResult<microsandbox::Sandbox>>,
|
|
2594
2646
|
) -> Self {
|
|
2595
2647
|
Self {
|
|
2596
|
-
|
|
2597
|
-
|
|
2648
|
+
inner: ForkGuarded::new(
|
|
2649
|
+
"Microsandbox::PullSession",
|
|
2650
|
+
PullParts {
|
|
2651
|
+
progress: Arc::new(Mutex::new(progress)),
|
|
2652
|
+
join: Arc::new(Mutex::new(Some(join))),
|
|
2653
|
+
},
|
|
2654
|
+
),
|
|
2598
2655
|
}
|
|
2599
2656
|
}
|
|
2600
2657
|
|
|
2601
2658
|
/// Next progress event as a Hash, or nil when the pull is finished.
|
|
2602
2659
|
fn recv(&self) -> Result<Option<RHash>, Error> {
|
|
2603
|
-
let progress = Arc::clone(&self.progress);
|
|
2604
|
-
let event = block_on(async move { progress.lock().await.recv().await })
|
|
2660
|
+
let progress = Arc::clone(&self.inner.get()?.progress);
|
|
2661
|
+
let event = block_on(async move { progress.lock().await.recv().await })?;
|
|
2605
2662
|
Ok(event.map(|p| pull_progress_to_hash(&p)))
|
|
2606
2663
|
}
|
|
2607
2664
|
|
|
2608
2665
|
/// Await the booted sandbox. Call after draining `recv` (it joins the
|
|
2609
2666
|
/// create task). Consumes the join handle, so it is callable only once.
|
|
2610
2667
|
fn result(&self) -> Result<Sandbox, Error> {
|
|
2611
|
-
let join = Arc::clone(&self.join);
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2668
|
+
let join = Arc::clone(&self.inner.get()?.join);
|
|
2669
|
+
// Take the join handle AND await it in ONE `block_on`. `block_on`
|
|
2670
|
+
// returns `Err` when an interrupt is delivered before it starts; as two
|
|
2671
|
+
// calls, an interrupt between them would drop the taken `JoinHandle`
|
|
2672
|
+
// (which detaches the create task rather than cancelling it) and with
|
|
2673
|
+
// it the only way to reach the sandbox that task boots.
|
|
2674
|
+
let joined = block_on(async move {
|
|
2675
|
+
let taken = join.lock().await.take();
|
|
2676
|
+
match taken {
|
|
2677
|
+
Some(handle) => Some(handle.await),
|
|
2678
|
+
None => None,
|
|
2679
|
+
}
|
|
2680
|
+
})?;
|
|
2681
|
+
let inner = joined
|
|
2682
|
+
.ok_or_else(|| error::base_error("pull session result already taken"))?
|
|
2615
2683
|
.map_err(|e| error::base_error(format!("sandbox creation task failed: {e}")))?
|
|
2616
2684
|
.map_err(error::to_ruby)?;
|
|
2617
2685
|
Ok(Sandbox::from_inner(inner))
|
|
@@ -2629,85 +2697,104 @@ impl PullSession {
|
|
|
2629
2697
|
/// a synchronous snapshot read off the handle (no round-trip).
|
|
2630
2698
|
#[magnus::wrap(class = "Microsandbox::Native::SandboxHandle", free_immediately, size)]
|
|
2631
2699
|
pub struct SbHandle {
|
|
2632
|
-
inner: SandboxHandle
|
|
2700
|
+
inner: ForkGuarded<SandboxHandle>,
|
|
2633
2701
|
}
|
|
2634
2702
|
|
|
2635
2703
|
impl SbHandle {
|
|
2636
2704
|
fn from_inner(inner: SandboxHandle) -> Self {
|
|
2637
|
-
Self {
|
|
2705
|
+
Self {
|
|
2706
|
+
inner: ForkGuarded::new("Microsandbox::SandboxHandle", inner),
|
|
2707
|
+
}
|
|
2638
2708
|
}
|
|
2639
2709
|
|
|
2710
|
+
/// Plain data — like every other snapshot accessor here (`id`, `status`,
|
|
2711
|
+
/// the timestamps, `config_json`) it stays readable in a forked child
|
|
2712
|
+
/// (`ForkGuarded::peek`); the lifecycle operations do not.
|
|
2640
2713
|
fn name(&self) -> String {
|
|
2641
|
-
self.inner.name().to_string()
|
|
2714
|
+
self.inner.peek().name().to_string()
|
|
2642
2715
|
}
|
|
2643
2716
|
|
|
2644
2717
|
/// The opaque, backend-assigned identity of the persisted sandbox this
|
|
2645
2718
|
/// handle is bound to (v0.6.16). The lifecycle operations below refuse to
|
|
2646
2719
|
/// act on a same-name replacement by comparing against it.
|
|
2647
2720
|
fn id(&self) -> String {
|
|
2648
|
-
self.inner.id().to_string()
|
|
2721
|
+
self.inner.peek().id().to_string()
|
|
2649
2722
|
}
|
|
2650
2723
|
|
|
2651
2724
|
/// Status snapshot captured when the handle was fetched (synchronous).
|
|
2652
2725
|
fn status(&self) -> String {
|
|
2653
|
-
sandbox_status_str(self.inner.status_snapshot()).to_string()
|
|
2726
|
+
sandbox_status_str(self.inner.peek().status_snapshot()).to_string()
|
|
2654
2727
|
}
|
|
2655
2728
|
|
|
2656
2729
|
fn created_at_ms(&self) -> Option<i64> {
|
|
2657
|
-
self.inner
|
|
2730
|
+
self.inner
|
|
2731
|
+
.peek()
|
|
2732
|
+
.created_at()
|
|
2733
|
+
.map(|dt| dt.timestamp_millis())
|
|
2658
2734
|
}
|
|
2659
2735
|
|
|
2660
2736
|
fn updated_at_ms(&self) -> Option<i64> {
|
|
2661
|
-
self.inner
|
|
2737
|
+
self.inner
|
|
2738
|
+
.peek()
|
|
2739
|
+
.updated_at()
|
|
2740
|
+
.map(|dt| dt.timestamp_millis())
|
|
2662
2741
|
}
|
|
2663
2742
|
|
|
2664
2743
|
/// Graceful stop (SIGTERM→SIGKILL escalation, 10s default) and wait.
|
|
2665
2744
|
fn stop(&self) -> Result<(), Error> {
|
|
2666
|
-
|
|
2745
|
+
let inner = self.inner.get()?;
|
|
2746
|
+
block_on(inner.stop())?.map_err(error::to_ruby)
|
|
2667
2747
|
}
|
|
2668
2748
|
|
|
2669
2749
|
/// Graceful stop with a custom escalation timeout (seconds).
|
|
2670
2750
|
fn stop_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
2671
|
-
|
|
2751
|
+
let inner = self.inner.get()?;
|
|
2752
|
+
block_on(inner.stop_with_timeout(secs_to_duration(secs)?))?.map_err(error::to_ruby)
|
|
2672
2753
|
}
|
|
2673
2754
|
|
|
2674
2755
|
/// Force kill (SIGKILL) and wait.
|
|
2675
2756
|
fn kill(&self) -> Result<(), Error> {
|
|
2676
|
-
|
|
2757
|
+
let inner = self.inner.get()?;
|
|
2758
|
+
block_on(inner.kill())?.map_err(error::to_ruby)
|
|
2677
2759
|
}
|
|
2678
2760
|
|
|
2679
2761
|
/// Force kill, waiting up to `secs` for the process to disappear.
|
|
2680
2762
|
fn kill_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
2681
|
-
|
|
2763
|
+
let inner = self.inner.get()?;
|
|
2764
|
+
block_on(inner.kill_with_timeout(secs_to_duration(secs)?))?.map_err(error::to_ruby)
|
|
2682
2765
|
}
|
|
2683
2766
|
|
|
2684
2767
|
/// Send the graceful-shutdown request and return without waiting.
|
|
2685
2768
|
fn request_stop(&self) -> Result<(), Error> {
|
|
2686
|
-
|
|
2769
|
+
let inner = self.inner.get()?;
|
|
2770
|
+
block_on(inner.request_stop())?.map_err(error::to_ruby)
|
|
2687
2771
|
}
|
|
2688
2772
|
|
|
2689
2773
|
/// Send the force-kill request and return without waiting.
|
|
2690
2774
|
fn request_kill(&self) -> Result<(), Error> {
|
|
2691
|
-
|
|
2775
|
+
let inner = self.inner.get()?;
|
|
2776
|
+
block_on(inner.request_kill())?.map_err(error::to_ruby)
|
|
2692
2777
|
}
|
|
2693
2778
|
|
|
2694
2779
|
/// Send the drain request (SIGUSR1) and return without waiting.
|
|
2695
2780
|
fn request_drain(&self) -> Result<(), Error> {
|
|
2696
|
-
|
|
2781
|
+
let inner = self.inner.get()?;
|
|
2782
|
+
block_on(inner.request_drain())?.map_err(error::to_ruby)
|
|
2697
2783
|
}
|
|
2698
2784
|
|
|
2699
2785
|
/// Connect when this exact sandbox is running, wait while it is starting,
|
|
2700
2786
|
/// or start it when it is created/stopped/crashed. `opts` carries
|
|
2701
2787
|
/// `detached`, which applies only when a start is actually required.
|
|
2702
2788
|
fn connect_or_start(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
2789
|
+
let inner = self.inner.get()?;
|
|
2703
2790
|
let detached = conv::opt_bool(opts, "detached")?;
|
|
2704
2791
|
let inner = block_on(async {
|
|
2705
2792
|
if detached {
|
|
2706
|
-
|
|
2793
|
+
inner.connect_or_start_detached().await
|
|
2707
2794
|
} else {
|
|
2708
|
-
|
|
2795
|
+
inner.connect_or_start().await
|
|
2709
2796
|
}
|
|
2710
|
-
})
|
|
2797
|
+
})?
|
|
2711
2798
|
.map_err(error::to_ruby)?;
|
|
2712
2799
|
Ok(Sandbox::from_inner(inner))
|
|
2713
2800
|
}
|
|
@@ -2716,29 +2803,33 @@ impl SbHandle {
|
|
|
2716
2803
|
/// returning a fresh handle. A same-name replacement raises
|
|
2717
2804
|
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
2718
2805
|
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
2806
|
+
let inner = self.inner.get()?;
|
|
2719
2807
|
let status = sandbox_status_from_str(&status)?;
|
|
2720
|
-
let handle = block_on(
|
|
2808
|
+
let handle = block_on(inner.wait_for_status(status))?.map_err(error::to_ruby)?;
|
|
2721
2809
|
Ok(SbHandle::from_inner(handle))
|
|
2722
2810
|
}
|
|
2723
2811
|
|
|
2724
2812
|
/// Stop and start this exact sandbox, returning the new live sandbox.
|
|
2725
2813
|
/// `opts` carries `force`/`timeout`/`detached`.
|
|
2726
2814
|
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
2815
|
+
let inner = self.inner.get()?;
|
|
2727
2816
|
let options = restart_options(opts)?;
|
|
2728
|
-
let inner = block_on(
|
|
2817
|
+
let inner = block_on(inner.restart_with(options))?.map_err(error::to_ruby)?;
|
|
2729
2818
|
Ok(Sandbox::from_inner(inner))
|
|
2730
2819
|
}
|
|
2731
2820
|
|
|
2732
2821
|
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
2733
2822
|
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
2823
|
+
let inner = self.inner.get()?;
|
|
2734
2824
|
let options = destroy_options(opts)?;
|
|
2735
|
-
block_on(
|
|
2825
|
+
block_on(inner.destroy_with(options))?.map_err(error::to_ruby)
|
|
2736
2826
|
}
|
|
2737
2827
|
|
|
2738
2828
|
/// Block until the sandbox reaches a terminal state; returns a stop-result
|
|
2739
2829
|
/// Hash (name, status, exit_code, signal, observed_at_ms, source).
|
|
2740
2830
|
fn wait_until_stopped(&self) -> Result<RHash, Error> {
|
|
2741
|
-
let
|
|
2831
|
+
let inner = self.inner.get()?;
|
|
2832
|
+
let result = block_on(inner.wait_until_stopped())?.map_err(error::to_ruby)?;
|
|
2742
2833
|
Ok(stop_result_to_hash(&result))
|
|
2743
2834
|
}
|
|
2744
2835
|
|
|
@@ -2746,7 +2837,7 @@ impl SbHandle {
|
|
|
2746
2837
|
/// handle already carries it, no runtime round-trip). The Ruby layer parses
|
|
2747
2838
|
/// it into a Hash for `#config`. Mirrors the Python/Node `config_json`.
|
|
2748
2839
|
fn config_json(&self) -> String {
|
|
2749
|
-
self.inner.config_json().to_string()
|
|
2840
|
+
self.inner.peek().config_json().to_string()
|
|
2750
2841
|
}
|
|
2751
2842
|
|
|
2752
2843
|
/// Snapshot this (stopped) sandbox under a bare name (resolved under the
|
|
@@ -2755,7 +2846,8 @@ impl SbHandle {
|
|
|
2755
2846
|
/// (`snapshot_to` was removed upstream in v0.6.7 — use `Snapshot.create`
|
|
2756
2847
|
/// with `dest_dir:` for explicit placement.)
|
|
2757
2848
|
fn snapshot(&self, name: String) -> Result<RHash, Error> {
|
|
2758
|
-
let
|
|
2849
|
+
let inner = self.inner.get()?;
|
|
2850
|
+
let snap = block_on(inner.snapshot(&name))?.map_err(error::to_ruby)?;
|
|
2759
2851
|
Ok(crate::snapshot::snapshot_to_hash(&snap))
|
|
2760
2852
|
}
|
|
2761
2853
|
|
|
@@ -2763,21 +2855,24 @@ impl SbHandle {
|
|
|
2763
2855
|
/// sandbox raises `SandboxNotRunningError` (the handle does not start it
|
|
2764
2856
|
/// implicitly). Returns `{ name, latency_secs }`.
|
|
2765
2857
|
fn ping(&self) -> Result<RHash, Error> {
|
|
2766
|
-
let
|
|
2858
|
+
let inner = self.inner.get()?;
|
|
2859
|
+
let result = block_on(inner.ping())?.map_err(error::to_ruby)?;
|
|
2767
2860
|
Ok(ping_result_to_hash(&result))
|
|
2768
2861
|
}
|
|
2769
2862
|
|
|
2770
2863
|
/// Explicitly refresh the idle-activity timer. A stopped sandbox raises
|
|
2771
2864
|
/// `SandboxNotRunningError`. Returns `{ name, activity_seq }`.
|
|
2772
2865
|
fn touch(&self) -> Result<RHash, Error> {
|
|
2773
|
-
let
|
|
2866
|
+
let inner = self.inner.get()?;
|
|
2867
|
+
let result = block_on(inner.touch())?.map_err(error::to_ruby)?;
|
|
2774
2868
|
Ok(touch_result_to_hash(&result))
|
|
2775
2869
|
}
|
|
2776
2870
|
|
|
2777
2871
|
/// Plan or apply a live sandbox modification (see `Sandbox::modify`). Returns
|
|
2778
2872
|
/// the `SandboxModificationPlan` as a JSON string.
|
|
2779
2873
|
fn modify(&self, opts: RHash) -> Result<String, Error> {
|
|
2780
|
-
|
|
2874
|
+
let inner = self.inner.get()?;
|
|
2875
|
+
run_modify(inner.modify(), opts)
|
|
2781
2876
|
}
|
|
2782
2877
|
}
|
|
2783
2878
|
|
|
@@ -2866,6 +2961,8 @@ pub(crate) fn log_entry_to_hash(entry: &LogEntry) -> RHash {
|
|
|
2866
2961
|
|
|
2867
2962
|
pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
2868
2963
|
let class = native.define_class("Sandbox", ruby.class_object())?;
|
|
2964
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
2965
|
+
crate::runtime::preload_class::<Sandbox>(ruby);
|
|
2869
2966
|
|
|
2870
2967
|
class.define_singleton_method("create", function!(Sandbox::create, 2))?;
|
|
2871
2968
|
class.define_singleton_method(
|
|
@@ -2939,6 +3036,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2939
3036
|
class.define_method("attach_shell", method!(Sandbox::attach_shell, 0))?;
|
|
2940
3037
|
|
|
2941
3038
|
let handle = native.define_class("SandboxHandle", ruby.class_object())?;
|
|
3039
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
3040
|
+
crate::runtime::preload_class::<SbHandle>(ruby);
|
|
2942
3041
|
handle.define_method("name", method!(SbHandle::name, 0))?;
|
|
2943
3042
|
handle.define_method("id", method!(SbHandle::id, 0))?;
|
|
2944
3043
|
handle.define_method("status", method!(SbHandle::status, 0))?;
|
|
@@ -2966,6 +3065,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2966
3065
|
handle.define_method("modify", method!(SbHandle::modify, 1))?;
|
|
2967
3066
|
|
|
2968
3067
|
let session = native.define_class("PullSession", ruby.class_object())?;
|
|
3068
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
3069
|
+
crate::runtime::preload_class::<PullSession>(ruby);
|
|
2969
3070
|
session.define_method("recv", method!(PullSession::recv, 0))?;
|
|
2970
3071
|
session.define_method("result", method!(PullSession::result, 0))?;
|
|
2971
3072
|
|