lora-ruby 0.8.5 → 0.9.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 +4 -4
- data/lib/lora_ruby/version.rb +1 -1
- data/src/errors.rs +4 -7
- data/src/gvl.rs +42 -8
- data/src/lib.rs +39 -17
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5e8fafa0e3463f9478a2fe5ee4508dcb8952ba61611b6ce49f8d0fc25ae2bb6
|
|
4
|
+
data.tar.gz: 653fd6d952de9430140bf6574ae86995e0750c387e308c9709bc8cf3be726408
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 626786c04ec31baef4f51e5ba58440ba9755bcff854325f65e88f35b689117634bde1f4edfb6cf55661c130f80e2a1075c853ccc9a765a5d12ce0f06a7ddfb37
|
|
7
|
+
data.tar.gz: e95004f92f2a18f0a8ade9d0a98963e2b56ebc1e038850658f007173d007652b7b3eee17d062d6163ff4f544c5a86ef1ec0b3d478d98eab793920aff9a3bf35b
|
data/lib/lora_ruby/version.rb
CHANGED
|
@@ -10,5 +10,5 @@ module LoraRuby
|
|
|
10
10
|
# Guard against redefinition so re-requiring this file (or loading
|
|
11
11
|
# both paths) doesn't emit a "warning: already initialized constant"
|
|
12
12
|
# when the native extension loads second with the identical value.
|
|
13
|
-
VERSION = "0.
|
|
13
|
+
VERSION = "0.9.0" unless const_defined?(:VERSION)
|
|
14
14
|
end
|
data/src/errors.rs
CHANGED
|
@@ -9,18 +9,15 @@
|
|
|
9
9
|
use lora_database::{LoraError, LoraErrorCode};
|
|
10
10
|
use magnus::{prelude::*, Error as MagnusError, ExceptionClass, RModule, Ruby};
|
|
11
11
|
|
|
12
|
-
pub(crate) fn lora_module(ruby: &Ruby) -> RModule {
|
|
13
|
-
ruby.class_object()
|
|
14
|
-
.const_get::<_, RModule>("LoraRuby")
|
|
15
|
-
.expect("LoraRuby module is defined by `init` before any method runs")
|
|
16
|
-
}
|
|
17
|
-
|
|
18
12
|
pub(crate) fn lora_error_class(ruby: &Ruby, name: &str) -> ExceptionClass {
|
|
19
13
|
// `const_get::<_, ExceptionClass>` converts the stored RClass into
|
|
20
14
|
// an ExceptionClass — this is the sound path, because our subclasses
|
|
21
15
|
// of StandardError retain the exception-class trait on the Ruby
|
|
22
16
|
// side even though `define_class` typed them as RClass.
|
|
23
|
-
|
|
17
|
+
let Ok(module) = ruby.class_object().const_get::<_, RModule>("LoraRuby") else {
|
|
18
|
+
return ruby.exception_standard_error();
|
|
19
|
+
};
|
|
20
|
+
module
|
|
24
21
|
.const_get::<_, ExceptionClass>(name)
|
|
25
22
|
.unwrap_or_else(|_| ruby.exception_standard_error())
|
|
26
23
|
}
|
data/src/gvl.rs
CHANGED
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
//! Global VM Lock release primitive.
|
|
2
2
|
|
|
3
3
|
use std::ffi::c_void;
|
|
4
|
+
use std::fmt;
|
|
4
5
|
use std::mem::MaybeUninit;
|
|
6
|
+
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
7
|
+
|
|
8
|
+
pub(crate) struct GvlPanic {
|
|
9
|
+
payload: Box<dyn std::any::Any + Send>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl GvlPanic {
|
|
13
|
+
fn new(payload: Box<dyn std::any::Any + Send>) -> Self {
|
|
14
|
+
Self { payload }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn message(&self) -> &str {
|
|
18
|
+
if let Some(s) = self.payload.downcast_ref::<&'static str>() {
|
|
19
|
+
s
|
|
20
|
+
} else if let Some(s) = self.payload.downcast_ref::<String>() {
|
|
21
|
+
s.as_str()
|
|
22
|
+
} else {
|
|
23
|
+
"non-string panic payload"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl fmt::Display for GvlPanic {
|
|
29
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
30
|
+
write!(
|
|
31
|
+
f,
|
|
32
|
+
"engine panicked while Ruby GVL was released: {}",
|
|
33
|
+
self.message()
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
5
37
|
|
|
6
38
|
/// Run `f` with Ruby's Global VM Lock released.
|
|
7
39
|
///
|
|
@@ -11,7 +43,7 @@ use std::mem::MaybeUninit;
|
|
|
11
43
|
/// keeping all such work on the calling thread. Everything inside
|
|
12
44
|
/// `database_execute`'s closure is pure Rust on pre-extracted data, so
|
|
13
45
|
/// this is sound.
|
|
14
|
-
pub(crate) fn without_gvl<F, R>(f: F) -> R
|
|
46
|
+
pub(crate) fn without_gvl<F, R>(f: F) -> Result<R, GvlPanic>
|
|
15
47
|
where
|
|
16
48
|
F: FnOnce() -> R,
|
|
17
49
|
F: Send,
|
|
@@ -19,7 +51,7 @@ where
|
|
|
19
51
|
{
|
|
20
52
|
struct Data<F, R> {
|
|
21
53
|
func: Option<F>,
|
|
22
|
-
result: MaybeUninit<R
|
|
54
|
+
result: MaybeUninit<std::thread::Result<R>>,
|
|
23
55
|
}
|
|
24
56
|
|
|
25
57
|
unsafe extern "C" fn trampoline<F, R>(data: *mut c_void) -> *mut c_void
|
|
@@ -27,11 +59,13 @@ where
|
|
|
27
59
|
F: FnOnce() -> R,
|
|
28
60
|
{
|
|
29
61
|
let data = &mut *(data as *mut Data<F, R>);
|
|
30
|
-
let
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
let result = match data.func.take() {
|
|
63
|
+
Some(f) => catch_unwind(AssertUnwindSafe(f)),
|
|
64
|
+
None => {
|
|
65
|
+
Err(Box::new("without_gvl: closure already taken") as Box<dyn std::any::Any + Send>)
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
data.result.write(result);
|
|
35
69
|
std::ptr::null_mut()
|
|
36
70
|
}
|
|
37
71
|
|
|
@@ -50,6 +84,6 @@ where
|
|
|
50
84
|
None,
|
|
51
85
|
std::ptr::null_mut(),
|
|
52
86
|
);
|
|
53
|
-
data.result.assume_init()
|
|
87
|
+
data.result.assume_init().map_err(GvlPanic::new)
|
|
54
88
|
}
|
|
55
89
|
}
|
data/src/lib.rs
CHANGED
|
@@ -146,8 +146,7 @@ impl Database {
|
|
|
146
146
|
// cost-equivalent.
|
|
147
147
|
fn database_new(ruby: &Ruby, args: &[Value]) -> Result<Database, MagnusError> {
|
|
148
148
|
let (database_name, options) = database_open_args(ruby, args)?;
|
|
149
|
-
let db =
|
|
150
|
-
.map_err(|e| query_error(ruby, e))?;
|
|
149
|
+
let db = without_gvl_string_result(ruby, move || open_database(database_name, options))?;
|
|
151
150
|
Ok(Database::from_db(db))
|
|
152
151
|
}
|
|
153
152
|
|
|
@@ -180,7 +179,7 @@ fn database_open_wal(ruby: &Ruby, args: &[Value]) -> Result<Database, MagnusErro
|
|
|
180
179
|
));
|
|
181
180
|
}
|
|
182
181
|
options.wal_dir = Some(wal_dir);
|
|
183
|
-
let db =
|
|
182
|
+
let db = without_gvl_string_result(ruby, move || open_wal_database(options))?;
|
|
184
183
|
Ok(Database::from_db(db))
|
|
185
184
|
}
|
|
186
185
|
|
|
@@ -229,8 +228,9 @@ fn database_save_snapshot(
|
|
|
229
228
|
) -> Result<RHash, MagnusError> {
|
|
230
229
|
let (path, options) = snapshot_file_args(ruby, args)?;
|
|
231
230
|
let db = database_inner(ruby, rb_self)?;
|
|
232
|
-
let meta =
|
|
233
|
-
.
|
|
231
|
+
let meta = without_gvl_lora_result(ruby, move || {
|
|
232
|
+
db.save_snapshot_to_with_options(&path, &options)
|
|
233
|
+
})?;
|
|
234
234
|
snapshot_meta_to_rhash(ruby, meta)
|
|
235
235
|
}
|
|
236
236
|
|
|
@@ -241,9 +241,9 @@ fn database_load_snapshot(
|
|
|
241
241
|
) -> Result<RHash, MagnusError> {
|
|
242
242
|
let (path, credentials) = snapshot_load_file_args(ruby, args)?;
|
|
243
243
|
let db = database_inner(ruby, rb_self)?;
|
|
244
|
-
let meta =
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
let meta = without_gvl_lora_result(ruby, move || {
|
|
245
|
+
db.load_snapshot_from_with_credentials(&path, credentials.as_ref())
|
|
246
|
+
})?;
|
|
247
247
|
snapshot_meta_to_rhash(ruby, meta)
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -456,6 +456,31 @@ fn database_inner(
|
|
|
456
456
|
.ok_or_else(|| query_error(ruby, "database is closed"))
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
fn without_gvl_string_result<T>(
|
|
460
|
+
ruby: &Ruby,
|
|
461
|
+
f: impl FnOnce() -> Result<T, String> + Send,
|
|
462
|
+
) -> Result<T, MagnusError>
|
|
463
|
+
where
|
|
464
|
+
T: Send,
|
|
465
|
+
{
|
|
466
|
+
without_gvl(f)
|
|
467
|
+
.map_err(|panic| query_error(ruby, panic.to_string()))?
|
|
468
|
+
.map_err(|e| query_error(ruby, e))
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
fn without_gvl_lora_result<T, E>(
|
|
472
|
+
ruby: &Ruby,
|
|
473
|
+
f: impl FnOnce() -> Result<T, E> + Send,
|
|
474
|
+
) -> Result<T, MagnusError>
|
|
475
|
+
where
|
|
476
|
+
T: Send,
|
|
477
|
+
E: Into<lora_database::LoraError> + Send,
|
|
478
|
+
{
|
|
479
|
+
without_gvl(f)
|
|
480
|
+
.map_err(|panic| query_error(ruby, panic.to_string()))?
|
|
481
|
+
.map_err(|e| query_error_from_anyhow(ruby, e))
|
|
482
|
+
}
|
|
483
|
+
|
|
459
484
|
/// `execute(query, params = nil)` — `-1` arity so `params` is optional and
|
|
460
485
|
/// we can distinguish "not passed" from `nil`/`{}` (both map to empty
|
|
461
486
|
/// params). Everything that touches Ruby values happens under the GVL;
|
|
@@ -494,17 +519,16 @@ fn database_execute(ruby: &Ruby, rb_self: &Database, args: &[Value]) -> Result<R
|
|
|
494
519
|
// is pure Rust — no Ruby values cross the boundary — which keeps this
|
|
495
520
|
// sound.
|
|
496
521
|
let db = database_inner(ruby, rb_self)?;
|
|
497
|
-
let exec_result =
|
|
522
|
+
let exec_result = without_gvl_lora_result(ruby, move || {
|
|
498
523
|
let options = ExecuteOptions {
|
|
499
524
|
format: ResultFormat::RowArrays,
|
|
500
525
|
};
|
|
501
526
|
db.execute_with_params(&query, Some(options), params_map)
|
|
502
|
-
})
|
|
527
|
+
})?;
|
|
503
528
|
|
|
504
529
|
let row_arrays = match exec_result {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
Err(e) => return Err(query_error_from_anyhow(ruby, e)),
|
|
530
|
+
QueryResult::RowArrays(r) => r,
|
|
531
|
+
_ => return Err(query_error(ruby, "expected RowArrays result")),
|
|
508
532
|
};
|
|
509
533
|
|
|
510
534
|
let out = ruby.hash_new();
|
|
@@ -536,8 +560,7 @@ fn database_explain(ruby: &Ruby, rb_self: &Database, args: &[Value]) -> Result<R
|
|
|
536
560
|
None => None,
|
|
537
561
|
};
|
|
538
562
|
let db = database_inner(ruby, rb_self)?;
|
|
539
|
-
let plan =
|
|
540
|
-
.map_err(|e| query_error_from_anyhow(ruby, e))?;
|
|
563
|
+
let plan = without_gvl_lora_result(ruby, move || db.explain(&query, params_map))?;
|
|
541
564
|
query_plan_to_ruby(ruby, &plan)
|
|
542
565
|
}
|
|
543
566
|
|
|
@@ -554,8 +577,7 @@ fn database_profile(ruby: &Ruby, rb_self: &Database, args: &[Value]) -> Result<R
|
|
|
554
577
|
None => None,
|
|
555
578
|
};
|
|
556
579
|
let db = database_inner(ruby, rb_self)?;
|
|
557
|
-
let prof =
|
|
558
|
-
.map_err(|e| query_error_from_anyhow(ruby, e))?;
|
|
580
|
+
let prof = without_gvl_lora_result(ruby, move || db.profile(&query, params_map))?;
|
|
559
581
|
query_profile_to_ruby(ruby, &prof)
|
|
560
582
|
}
|
|
561
583
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lora-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LoraDB, Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|