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 +4 -4
- data/CHANGELOG.md +16 -0
- data/ext/oo7/src/lib.rs +74 -4
- data/lib/oo7/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b297fb73779f9906f68e6c4d095e063ba13d4eda98602a9b11f46b25454138c8
|
|
4
|
+
data.tar.gz: 9d1216cf3d649f149f7c02a1843c8bde9bdad07232a4e2c39ecfc68f1d2dd616
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
18
|
-
//!
|
|
19
|
-
//!
|
|
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
|
-
|
|
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