microsandbox-rb 0.5.11 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2d0013cf55f89aa21492847859554937ff838d5d38d4f5f34d0e8da473c9554
4
- data.tar.gz: 95bd9d994905567facd356597a8fee6e7f25715a485edbbc48bf98bb09f90eb3
3
+ metadata.gz: abfe8d0fedb192bc5efa95a741ae971475a2a5cc441879c092108bc8bdf9eed5
4
+ data.tar.gz: ffeb9bda17817cc1c58bfe5cc9c7d58543cde155eead9d2e5eb3844c8a48b4dd
5
5
  SHA512:
6
- metadata.gz: 337de8ed90dfea5b44d959579a16dafdd736e33fbfff7e10ed8ad4678184f84bdf417f33d85a7a8f11ab69a4785a91c381ea719600769b804d9a71c1c048adb1
7
- data.tar.gz: 672d7c7945e3a5561ad9934bc682b5f9764d1873d4235105e33ca1494a0079d11e574d520ed0e1f90bb66049264cdc783180448d2f456c78185024d9f18b186d
6
+ metadata.gz: 73379d82644123c717cd7db9391cab9bf72c16b5cf398ea104fb51d2b608353ed9cd6783db55e1490d941ae67728f1c69fe0bd2dcec37c7ad541c56996afea9f
7
+ data.tar.gz: b20914b13315697e89ec8a72e7620592a9c00d1e8fa908117ef24d64aea9755b431a2298b85d58ed00ca664065f0b626510c7f11fbb919860d3f839c93268a28
data/CHANGELOG.md CHANGED
@@ -1,11 +1,48 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to this gem are documented here. The format is based on
4
- [Keep a Changelog](https://keepachangelog.com/), and the version tracks the
5
- upstream microsandbox runtime.
4
+ [Keep a Changelog](https://keepachangelog.com/). The gem follows its own
5
+ [semantic version](https://semver.org/), **independent of** the upstream
6
+ microsandbox runtime it embeds; each release notes the upstream runtime tag it
7
+ wraps, and the README's Versioning section keeps the full gem→runtime map.
6
8
 
7
9
  ## [Unreleased]
8
10
 
11
+ ## [0.6.0] - 2026-06-23
12
+
13
+ This release puts the gem on its **own semantic version**, decoupled from the
14
+ upstream microsandbox runtime tag it embeds (which stays at `v0.5.8`). The
15
+ `0.5.x` lineage had stopped tracking upstream 1:1 — gem-only revisions and a
16
+ bundled breaking change (the `0.5.9 → 0.5.10` lifecycle split) had already
17
+ diverged the two numbers. `0.6.0` makes the split explicit; the gem version no
18
+ longer mirrors the upstream tag. See the README's **Versioning** section for the
19
+ gem→runtime map and the go-forward policy. No runtime change and no breaking API
20
+ change in this release.
21
+
22
+ ### Added
23
+
24
+ - **`Microsandbox.runtime_version`** and the `Microsandbox::RUNTIME_VERSION`
25
+ constant — report the upstream microsandbox runtime tag this gem build embeds
26
+ (e.g. `"v0.5.8"`). The gem now versions itself independently of that tag, so
27
+ this is the supported way to learn which runtime is wrapped.
28
+ `spec/unit/version_spec.rb` pins the constant to the Cargo git tag so it can't
29
+ drift.
30
+
31
+ ## [0.5.12] - 2026-06-23
32
+
33
+ ### Fixed
34
+
35
+ - **fork-safe tokio runtime.** The process-wide multi-threaded runtime is now
36
+ tagged with the pid it was built under and rebuilt automatically after a
37
+ `fork(2)`. A forking host (Solid Queue / Resque job servers, clustered Puma)
38
+ used to inherit a runtime whose worker + I/O-driver threads do not survive the
39
+ fork — `block_on` could still drive the calling thread, but background I/O (e.g.
40
+ the agent-relay connection that streams `exec_stream` output) never ran, so
41
+ long-lived operations stalled or the connection dropped mid-stream in the child.
42
+ `runtime()` now detects the pid change and builds a fresh runtime for the child
43
+ (the stale one is leaked, never dropped — dropping a runtime whose threads
44
+ vanished across fork can hang on the shutdown join). No API change.
45
+
9
46
  ## [0.5.11] - 2026-06-23
10
47
 
11
48
  ### Added
@@ -254,6 +291,7 @@ microsandbox runtime, aligned with the official Python/Node/Go SDKs.
254
291
  core crate has Apple-native deps). Until precompiled gems are published,
255
292
  installing from source requires a Rust toolchain (stable >= 1.91).
256
293
 
257
- [Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.8...HEAD
294
+ [Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.6.0...HEAD
295
+ [0.6.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.12...v0.6.0
258
296
  [0.5.8]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.7...v0.5.8
259
297
  [0.5.7]: https://github.com/superradcompany/microsandbox/releases/tag/v0.5.7
data/Cargo.lock CHANGED
@@ -3249,7 +3249,7 @@ dependencies = [
3249
3249
 
3250
3250
  [[package]]
3251
3251
  name = "microsandbox_rb"
3252
- version = "0.5.11"
3252
+ version = "0.6.0"
3253
3253
  dependencies = [
3254
3254
  "chrono",
3255
3255
  "futures",
data/README.md CHANGED
@@ -374,6 +374,43 @@ Resolution order when no backend is set programmatically: `MSB_BACKEND`
374
374
  operations (create/start/stop/remove/get/list, one-shot exec, follow log
375
375
  streaming); unsupported operations raise `Microsandbox::UnsupportedError`.
376
376
 
377
+ ## Versioning
378
+
379
+ The gem follows its **own** [semantic version](https://semver.org/), **independent
380
+ of** the upstream `microsandbox` runtime it embeds. Early releases (`0.5.7`–`0.5.9`)
381
+ happened to share the upstream tag, but gem-only revisions and a bundled breaking
382
+ change diverged the two numbers — the gem version is **not** a reliable indicator
383
+ of the embedded runtime version. To learn which runtime a build wraps, ask it:
384
+
385
+ ```ruby
386
+ Microsandbox::VERSION # => "0.6.0" (the gem's own version)
387
+ Microsandbox.runtime_version # => "v0.5.8" (the embedded upstream runtime tag)
388
+ ```
389
+
390
+ | Gem version | Upstream runtime | Notes |
391
+ |-------------|------------------|-------|
392
+ | `0.5.7` | `v0.5.7` | initial release |
393
+ | `0.5.8` | `v0.5.7` | gem-only revision |
394
+ | `0.5.9` | `v0.5.7` | gem-only revision |
395
+ | `0.5.10` | `v0.5.8` | adopts upstream `v0.5.8`; **breaking** lifecycle split |
396
+ | `0.5.11` | `v0.5.8` | gem-only revision |
397
+ | `0.5.12` | `v0.5.8` | gem-only revision |
398
+ | `0.6.0` | `v0.5.8` | gem version decoupled from the upstream tag; adds `runtime_version` |
399
+
400
+ **Going forward** — the gem version moves on its own semver track and no longer
401
+ mirrors the upstream tag:
402
+
403
+ - A **gem-only** change (new bindings, fixes, refactors over the *same* runtime)
404
+ bumps the gem version by itself.
405
+ - **Adopting a new upstream runtime** bumps the gem version too and updates the
406
+ pinned git tag, `Microsandbox::RUNTIME_VERSION`, and the table above together.
407
+ - While the gem is `0.x`, a breaking API change bumps the **minor** (`0.5 → 0.6`),
408
+ not the patch — the `0.5.9 → 0.5.10` lifecycle split predates this policy and is
409
+ the reason for it.
410
+
411
+ Every release records its embedded runtime in `CHANGELOG.md`, and
412
+ `Microsandbox.runtime_version` reports it at runtime.
413
+
377
414
  ## Development
378
415
 
379
416
  ```bash
@@ -406,9 +443,13 @@ or credential setup is needed.
406
443
 
407
444
  **Each release:**
408
445
 
409
- 1. Bump `Microsandbox::VERSION` (and the `tag = "vX.Y.Z"` on the core-crate
410
- dependency in `ext/microsandbox/Cargo.toml`) to match the upstream runtime,
411
- update `CHANGELOG.md`.
446
+ 1. Bump the gem's **own** version — `Microsandbox::VERSION` and the matching
447
+ `[package] version` in `ext/microsandbox/Cargo.toml` (they must stay equal)
448
+ on its independent semver track (see [Versioning](#versioning)); **don't** pick
449
+ the number to mirror the upstream tag. If the release also adopts a new upstream
450
+ runtime, bump the `tag = "vX.Y.Z"` on **both** the `microsandbox` and
451
+ `microsandbox-network` git deps, update `Microsandbox::RUNTIME_VERSION` to match,
452
+ and add a row to the Versioning table. Update `CHANGELOG.md`.
412
453
  2. Push a `vX.Y.Z` tag. CI builds the **source gem** and pushes it to RubyGems
413
454
  via `rubygems/configure-rubygems-credentials` (OIDC, `id-token: write`) — no
414
455
  `RUBYGEMS_API_KEY` secret required.
@@ -7,7 +7,7 @@ description = "Ruby SDK native extension for microsandbox — secure, fast micro
7
7
  # Must equal Microsandbox::VERSION (lib/microsandbox/version.rb) — Native.version
8
8
  # returns this via env!("CARGO_PKG_VERSION") and version_spec.rb asserts equality.
9
9
  # The core-crate dependency below stays pinned at its own tag (v0.5.8).
10
- version = "0.5.11"
10
+ version = "0.6.0"
11
11
  authors = ["Super Rad Company <development@superrad.company>"]
12
12
  repository = "https://github.com/superradcompany/microsandbox"
13
13
  license = "Apache-2.0"
@@ -9,7 +9,8 @@
9
9
  use std::ffi::c_void;
10
10
  use std::future::Future;
11
11
  use std::panic::{catch_unwind, AssertUnwindSafe};
12
- use std::sync::OnceLock;
12
+ use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
13
+ use std::sync::Mutex;
13
14
 
14
15
  use magnus::Ruby;
15
16
  use tokio::runtime::Runtime;
@@ -21,17 +22,60 @@ pub fn ruby() -> Ruby {
21
22
  Ruby::get().expect("microsandbox: not on a Ruby thread")
22
23
  }
23
24
 
24
- static RUNTIME: OnceLock<Runtime> = OnceLock::new();
25
+ // The process-wide tokio runtime, guarded by the pid it was built under. A
26
+ // multi-threaded runtime owns worker + I/O-driver threads, and `fork(2)` copies
27
+ // ONLY the calling thread — so a child that inherits this runtime has a runtime
28
+ // whose threads are gone: `block_on` can still drive the current thread, but the
29
+ // background I/O that keeps e.g. the agent-relay connection alive never runs, so
30
+ // connections stall/drop mid-use. This is hit in practice by forking job servers
31
+ // (Solid Queue, Resque) and clustered web servers (Puma workers).
32
+ //
33
+ // Fix: store the runtime behind a leaked raw pointer tagged with the building
34
+ // pid. If `runtime()` is called in a process whose pid differs (we forked), build
35
+ // a FRESH runtime for this process and swap the pointer. The stale runtime is
36
+ // LEAKED, never dropped — dropping a tokio runtime whose worker threads vanished
37
+ // across fork can hang on the shutdown thread-join. One runtime is leaked per
38
+ // process that uses the SDK (bounded; equivalent to the old set-once behavior in
39
+ // the common no-fork case).
40
+ static RUNTIME_PTR: AtomicPtr<Runtime> = AtomicPtr::new(std::ptr::null_mut());
41
+ static RUNTIME_PID: AtomicU32 = AtomicU32::new(0);
42
+ static RUNTIME_LOCK: Mutex<()> = Mutex::new(());
25
43
 
26
- /// The process-wide multi-threaded tokio runtime, built on first use.
44
+ fn build_runtime() -> Runtime {
45
+ tokio::runtime::Builder::new_multi_thread()
46
+ .enable_all()
47
+ .thread_name("microsandbox-rb")
48
+ .build()
49
+ .expect("microsandbox: failed to build tokio runtime")
50
+ }
51
+
52
+ /// The multi-threaded tokio runtime for THIS process, built on first use and
53
+ /// rebuilt after a `fork(2)` (fork-safe — see the note above).
27
54
  pub fn runtime() -> &'static Runtime {
28
- RUNTIME.get_or_init(|| {
29
- tokio::runtime::Builder::new_multi_thread()
30
- .enable_all()
31
- .thread_name("microsandbox-rb")
32
- .build()
33
- .expect("microsandbox: failed to build tokio runtime")
34
- })
55
+ let cur = std::process::id();
56
+ let ptr = RUNTIME_PTR.load(Ordering::Acquire);
57
+ if !ptr.is_null() && RUNTIME_PID.load(Ordering::Acquire) == cur {
58
+ // SAFETY: ptr was produced by Box::leak (valid for 'static) and matches
59
+ // this process's pid, so its runtime threads are live in this process.
60
+ return unsafe { &*ptr };
61
+ }
62
+
63
+ let _guard = RUNTIME_LOCK
64
+ .lock()
65
+ .expect("microsandbox: runtime lock poisoned");
66
+ // Re-check under the lock (another thread may have just built it).
67
+ let ptr = RUNTIME_PTR.load(Ordering::Acquire);
68
+ if !ptr.is_null() && RUNTIME_PID.load(Ordering::Acquire) == cur {
69
+ return unsafe { &*ptr };
70
+ }
71
+
72
+ // Build a fresh runtime for this process and publish it. The previous pointer
73
+ // (if any) belonged to a parent process and is intentionally leaked, not
74
+ // dropped (its threads are gone post-fork; Drop would block on join).
75
+ let rt: &'static Runtime = Box::leak(Box::new(build_runtime()));
76
+ RUNTIME_PTR.store(rt as *const Runtime as *mut Runtime, Ordering::Release);
77
+ RUNTIME_PID.store(cur, Ordering::Release);
78
+ rt
35
79
  }
36
80
 
37
81
  /// Run `f` with the Ruby GVL released.
@@ -1,9 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Microsandbox
4
- # Gem version. Tracks the upstream microsandbox runtime (currently `v0.5.8`,
5
- # the pinned core-crate tag); the patch segment advances for gem-only revisions
6
- # that add bindings atop the same core. Must equal the native ext's Cargo crate
7
- # version (`Native.version`), enforced by spec/unit/version_spec.rb.
8
- VERSION = "0.5.11"
4
+ # Gem version. Versioned independently of the upstream microsandbox runtime it
5
+ # embeds: the gem follows its own semver (while 0.x, breaking changes bump the
6
+ # minor and fixes bump the patch), so the number does NOT track the upstream tag
7
+ # one-to-one. Consult {RUNTIME_VERSION} for the wrapped runtime, and the
8
+ # Versioning section of the README for the full gem-to-runtime map. Must equal
9
+ # the native ext's Cargo crate version (`Native.version`), enforced by
10
+ # spec/unit/version_spec.rb.
11
+ VERSION = "0.6.0"
12
+
13
+ # The upstream microsandbox runtime release this gem build embeds — the `tag`
14
+ # pinned on the `microsandbox`/`microsandbox-network` git deps in
15
+ # ext/microsandbox/Cargo.toml. Exposed at runtime as
16
+ # {Microsandbox.runtime_version}. spec/unit/version_spec.rb asserts it stays in
17
+ # sync with the Cargo tag so it can't silently drift out of date.
18
+ RUNTIME_VERSION = "v0.5.8"
9
19
  end
data/lib/microsandbox.rb CHANGED
@@ -45,6 +45,15 @@ module Microsandbox
45
45
  VERSION
46
46
  end
47
47
 
48
+ # The upstream microsandbox runtime release this gem build embeds (the git
49
+ # `tag` pinned in ext/microsandbox/Cargo.toml). The gem's own {version} is
50
+ # versioned independently of this, so consult this to learn which runtime is
51
+ # wrapped. See the Versioning section of the README for the full map.
52
+ # @return [String] e.g. "v0.5.8"
53
+ def runtime_version
54
+ RUNTIME_VERSION
55
+ end
56
+
48
57
  # Download and install the `msb` runtime + `libkrunfw` into
49
58
  # `~/.microsandbox` (idempotent).
50
59
  #
data/sig/microsandbox.rbs CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Microsandbox
4
4
  VERSION: String
5
+ RUNTIME_VERSION: String
5
6
 
6
7
  def self.version: () -> String
8
+ def self.runtime_version: () -> String
7
9
  def self.install: () -> nil
8
10
  def self.installed?: () -> bool
9
11
  def self.ensure_runtime!: () -> nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: microsandbox-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.11
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ya-luotao