oo7-rb 1.0.0 → 1.0.1

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: ef2fd80ab2a8f10c5d088bbbec07818d02de3f10052481948aaa696ea06edeb7
4
- data.tar.gz: cec2a14c4480f65cc60c153bc13288c37f18cb2f465276979acbd1ca26ef86dd
3
+ metadata.gz: b297fb73779f9906f68e6c4d095e063ba13d4eda98602a9b11f46b25454138c8
4
+ data.tar.gz: 9d1216cf3d649f149f7c02a1843c8bde9bdad07232a4e2c39ecfc68f1d2dd616
5
5
  SHA512:
6
- metadata.gz: 333db89d32d32c9bce18e3ef5d44bfa84473849005c7b972b63f3220de0af49688aaf433d2c9f2762b61e5db5da696ce55f60472b57e2c559a3237a80027a92a
7
- data.tar.gz: 8c4a678e0c38fe38e056aa456855b1066a2225c276cdb2d9e5165edffb197078be52dc112e67fb94e35842d2bb523758f098907a63c5e817fcdb798f20349270
6
+ metadata.gz: 12300842da80f184bf6546dd13d1431522942eb0e306a9da4ec9a4ed3feb4389a93e033a2db848965b18027e2b722d88fcca0c43ae9b60f00da85fffdd754578
7
+ data.tar.gz: 72555652dd852e1dacfa27b873d8275629b0fa239134a7ea89dc16ec9d586c5daa417c632cc6ae48fd403bb749ea88abc91af2364d4291730c3c25632b96f677
data/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.1] - 2026-11-02
11
+
12
+ ### Fixed
13
+
14
+ - The GVL is released while a call waits, via `rb_thread_call_without_gvl`.
15
+ Every method previously held it for the whole D-Bus round trip, so a worker
16
+ thread blocked the main thread too -- the GVL is global -- and a GTK
17
+ application could not keep its main loop answering by moving the call off it.
18
+ It froze instead, and took the desktop with it, which is the thing this gem
19
+ exists to avoid. magnus 0.9 does not bind that function, so the crate declares
20
+ it.
21
+ - `test/oo7/concurrency.rb` checks it by behaviour rather than by inspection: a
22
+ second Ruby thread has to make progress while a keyring call is in flight.
23
+ With the GVL held it records zero ticks, so the test fails if the release is
24
+ ever lost again.
25
+
10
26
  ## [1.0.0] - 2026-11-02
11
27
 
12
28
  ### Added
data/ext/oo7/src/lib.rs CHANGED
@@ -14,9 +14,13 @@
14
14
  //!
15
15
  //! Every method on the crate is async. Each binding drives it to completion on a
16
16
  //! Tokio runtime owned by this extension and returns the result, so the Ruby API
17
- //! is ordinary and synchronous. The calling thread is blocked while it waits and
18
- //! the GVL is held, so a keyring call should not be made from a thread that
19
- //! something else is waiting on -- on a GTK main loop, put it on a Ruby thread.
17
+ //! is ordinary and synchronous.
18
+ //!
19
+ //! The calling thread waits, but it waits with the GVL released, so every other
20
+ //! Ruby thread keeps running. That is what lets a GTK application put a keyring
21
+ //! call on a worker thread and keep its main loop answering: without it the
22
+ //! worker would hold the GVL throughout and freeze the main thread anyway, which
23
+ //! is a frozen desktop rather than a slow one.
20
24
  //!
21
25
  //! # Provenance
22
26
  //!
@@ -28,6 +32,7 @@
28
32
  //! - <https://gitlab.gnome.org/World/fractal> (GPL-3.0-or-later; read only)
29
33
 
30
34
  use std::collections::HashMap;
35
+ use std::ffi::c_void;
31
36
  use std::future::Future;
32
37
  use std::sync::OnceLock;
33
38
 
@@ -59,8 +64,73 @@ fn runtime() -> Result<&'static Runtime, Error> {
59
64
  .map_err(rt_err)
60
65
  }
61
66
 
67
+ // Ruby's public C API, which magnus 0.9 does not bind (its list carries
68
+ // rb_thread_call_without_gvl commented out). Declared here rather than reached
69
+ // for through rb-sys' generated bindings, which depend on what bindgen picked up
70
+ // from this Ruby's headers.
71
+ unsafe extern "C" {
72
+ fn rb_thread_call_without_gvl(
73
+ func: extern "C" fn(*mut c_void) -> *mut c_void,
74
+ data1: *mut c_void,
75
+ ubf: Option<extern "C" fn(*mut c_void)>,
76
+ data2: *mut c_void,
77
+ ) -> *mut c_void;
78
+ }
79
+
80
+ /// Run `work` with the GVL released.
81
+ ///
82
+ /// This is the whole reason a Ruby thread can be used to keep a keyring call off
83
+ /// a GTK main loop. The GVL is global: a worker thread that blocks inside an
84
+ /// extension still holding it stops every other Ruby thread, the main one
85
+ /// included, so offloading the call would otherwise buy nothing at all.
86
+ ///
87
+ /// `work` must not touch Ruby -- no values, no exceptions, no allocation through
88
+ /// the VM -- because this thread does not hold the GVL while it runs. Driving a
89
+ /// future over D-Bus touches only Rust, which is what makes it eligible.
90
+ ///
91
+ /// The unblocking function is None, so the call is not interruptible: a D-Bus
92
+ /// round trip that never answers cannot be killed by Thread#kill. That is the
93
+ /// trade for not blocking the VM, and it is the right way round for a UI, where
94
+ /// the alternative is a frozen desktop.
95
+ fn without_gvl<F, T>(work: F) -> T
96
+ where
97
+ F: FnOnce() -> T,
98
+ {
99
+ struct Payload<F, T> {
100
+ work: Option<F>,
101
+ out: Option<T>,
102
+ }
103
+
104
+ extern "C" fn call<F, T>(data: *mut c_void) -> *mut c_void
105
+ where
106
+ F: FnOnce() -> T,
107
+ {
108
+ // Safe: the pointer is the Payload below, alive for the whole call, and
109
+ // reached from this one thread only.
110
+ let payload = unsafe { &mut *data.cast::<Payload<F, T>>() };
111
+ let work = payload.work.take().expect("the work runs once");
112
+ payload.out = Some(work());
113
+ std::ptr::null_mut()
114
+ }
115
+
116
+ let mut payload = Payload { work: Some(work), out: None };
117
+
118
+ unsafe {
119
+ rb_thread_call_without_gvl(
120
+ call::<F, T>,
121
+ (&raw mut payload).cast::<c_void>(),
122
+ None,
123
+ std::ptr::null_mut(),
124
+ );
125
+ }
126
+
127
+ payload.out.expect("the work ran")
128
+ }
129
+
62
130
  fn block<F: Future>(future: F) -> Result<F::Output, Error> {
63
- Ok(runtime()?.block_on(future))
131
+ let runtime = runtime()?;
132
+
133
+ Ok(without_gvl(|| runtime.block_on(future)))
64
134
  }
65
135
 
66
136
  /// A Ruby hash of strings, which is what the Secret Service calls attributes.
data/lib/oo7/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2026, by General Intelligence Systems.
5
5
 
6
6
  module Oo7
7
- VERSION = "1.0.0"
7
+ VERSION = "1.0.1"
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oo7-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Kidd