zrip 0.2.0 → 0.2.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 +21 -0
- data/Cargo.lock +29 -28
- data/Cargo.toml +0 -1
- data/README.md +1 -1
- data/ext/zrip/Cargo.toml +4 -4
- data/ext/zrip/src/lib.rs +99 -166
- data/ext/zrip/src/rb.rs +85 -33
- data/lib/zrip/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a16cab574ece332812f7e25c48b75152a11c63418619527273e974353c7279b
|
|
4
|
+
data.tar.gz: 9504303323b72ca6b079ddcb528a3be99d550a56125241c669613221d5aea4e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f80dfc72c92ce4a072d186ba75efa88f88281aab64cc94a19d15e3e15f1548aa52fa7b9cec4d7593db1dc6b4a8ac37cf28e37d14cb38beb059d85f7b6c460288
|
|
7
|
+
data.tar.gz: 910c850499f28d49c02fdfca756770a4d182ac7688891efa7cccc920f1dd2180b30fbcb984cf8b34e143d049374cd0d6da947f5f8f55998a89d9183753a17c9b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.1] - 2026-10-11
|
|
6
|
+
|
|
7
|
+
- Update the `zrip` crate dependency to 0.8.11 and `rb-sys` to 0.9.130.
|
|
8
|
+
- Make `FrameCodec` instances Ractor-shareable as documented, with native
|
|
9
|
+
dictionary copies that leave caller strings mutable.
|
|
10
|
+
- Unlock input strings and free native results when Ruby interrupts a call
|
|
11
|
+
that released the GVL.
|
|
12
|
+
- Protect mutable inputs when mutex contention releases the GVL, including
|
|
13
|
+
calls below the normal size threshold.
|
|
14
|
+
- Catch Rust panics inside native callbacks instead of aborting the process.
|
|
15
|
+
- Preserve Ruby exceptions, `Thread#kill`, and `catch`/`throw` through
|
|
16
|
+
protected native calls.
|
|
17
|
+
- Avoid trainer deadlocks during sample coercion and borrow decompression
|
|
18
|
+
input after size coercion.
|
|
19
|
+
- Enforce `max_output_size:` inside the native decoder across concatenated
|
|
20
|
+
frames, before allocating or decoding past the limit.
|
|
21
|
+
- Validate complete frame headers when reading frame content sizes.
|
|
22
|
+
- Correct the README compression level example to the supported maximum.
|
|
23
|
+
- Update GitHub Actions checkout to v7 and check Rust tests, formatting, and
|
|
24
|
+
Clippy in CI.
|
|
25
|
+
|
|
5
26
|
## [0.2.0] - 2026-08-07
|
|
6
27
|
|
|
7
28
|
- Release the Ruby GVL around large frame/block compression and frame
|
data/Cargo.lock
CHANGED
|
@@ -31,9 +31,9 @@ dependencies = [
|
|
|
31
31
|
|
|
32
32
|
[[package]]
|
|
33
33
|
name = "bitflags"
|
|
34
|
-
version = "2.13.
|
|
34
|
+
version = "2.13.2"
|
|
35
35
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
-
checksum = "
|
|
36
|
+
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
|
37
37
|
|
|
38
38
|
[[package]]
|
|
39
39
|
name = "cexpr"
|
|
@@ -46,9 +46,9 @@ dependencies = [
|
|
|
46
46
|
|
|
47
47
|
[[package]]
|
|
48
48
|
name = "cfg-if"
|
|
49
|
-
version = "1.0.
|
|
49
|
+
version = "1.0.5"
|
|
50
50
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
-
checksum = "
|
|
51
|
+
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
|
52
52
|
|
|
53
53
|
[[package]]
|
|
54
54
|
name = "clang-sys"
|
|
@@ -63,15 +63,15 @@ dependencies = [
|
|
|
63
63
|
|
|
64
64
|
[[package]]
|
|
65
65
|
name = "either"
|
|
66
|
-
version = "1.
|
|
66
|
+
version = "1.19.0"
|
|
67
67
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
-
checksum = "
|
|
68
|
+
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
|
69
69
|
|
|
70
70
|
[[package]]
|
|
71
71
|
name = "fearless_simd"
|
|
72
|
-
version = "
|
|
72
|
+
version = "1.1.0"
|
|
73
73
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
-
checksum = "
|
|
74
|
+
checksum = "3296eb97d869113eaf148a6bdf01884e90b3828ad7ae2340b0f93362fc158611"
|
|
75
75
|
|
|
76
76
|
[[package]]
|
|
77
77
|
name = "glob"
|
|
@@ -90,15 +90,15 @@ dependencies = [
|
|
|
90
90
|
|
|
91
91
|
[[package]]
|
|
92
92
|
name = "lazy_static"
|
|
93
|
-
version = "1.5.
|
|
93
|
+
version = "1.5.1"
|
|
94
94
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
95
|
-
checksum = "
|
|
95
|
+
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
|
96
96
|
|
|
97
97
|
[[package]]
|
|
98
98
|
name = "libc"
|
|
99
|
-
version = "0.2.
|
|
99
|
+
version = "0.2.190"
|
|
100
100
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
101
|
-
checksum = "
|
|
101
|
+
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
|
102
102
|
|
|
103
103
|
[[package]]
|
|
104
104
|
name = "libloading"
|
|
@@ -152,18 +152,18 @@ dependencies = [
|
|
|
152
152
|
|
|
153
153
|
[[package]]
|
|
154
154
|
name = "rb-sys"
|
|
155
|
-
version = "0.9.
|
|
155
|
+
version = "0.9.130"
|
|
156
156
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
157
|
-
checksum = "
|
|
157
|
+
checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
|
|
158
158
|
dependencies = [
|
|
159
159
|
"rb-sys-build",
|
|
160
160
|
]
|
|
161
161
|
|
|
162
162
|
[[package]]
|
|
163
163
|
name = "rb-sys-build"
|
|
164
|
-
version = "0.9.
|
|
164
|
+
version = "0.9.130"
|
|
165
165
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
166
|
-
checksum = "
|
|
166
|
+
checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
|
|
167
167
|
dependencies = [
|
|
168
168
|
"bindgen",
|
|
169
169
|
"lazy_static",
|
|
@@ -234,9 +234,9 @@ dependencies = [
|
|
|
234
234
|
|
|
235
235
|
[[package]]
|
|
236
236
|
name = "unicode-ident"
|
|
237
|
-
version = "1.0.
|
|
237
|
+
version = "1.0.26"
|
|
238
238
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
239
|
-
checksum = "
|
|
239
|
+
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
|
240
240
|
|
|
241
241
|
[[package]]
|
|
242
242
|
name = "windows-link"
|
|
@@ -246,17 +246,17 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
|
246
246
|
|
|
247
247
|
[[package]]
|
|
248
248
|
name = "zrip"
|
|
249
|
-
version = "0.1
|
|
249
|
+
version = "0.2.1"
|
|
250
250
|
dependencies = [
|
|
251
251
|
"rb-sys",
|
|
252
|
-
"zrip 0.8.
|
|
252
|
+
"zrip 0.8.11",
|
|
253
253
|
]
|
|
254
254
|
|
|
255
255
|
[[package]]
|
|
256
256
|
name = "zrip"
|
|
257
|
-
version = "0.8.
|
|
257
|
+
version = "0.8.11"
|
|
258
258
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
259
|
-
checksum = "
|
|
259
|
+
checksum = "9e85bd0b02784d8753fe5cc435dfb65043be505cb621643a3ac4a0c43eb49e4a"
|
|
260
260
|
dependencies = [
|
|
261
261
|
"zrip-core",
|
|
262
262
|
"zrip-decode",
|
|
@@ -265,15 +265,15 @@ dependencies = [
|
|
|
265
265
|
|
|
266
266
|
[[package]]
|
|
267
267
|
name = "zrip-core"
|
|
268
|
-
version = "0.
|
|
268
|
+
version = "0.11.0"
|
|
269
269
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
270
|
-
checksum = "
|
|
270
|
+
checksum = "6dec95be8c75252532ba0ef5e738d9da26a6f51ad52cb45f4ed90c89979727c1"
|
|
271
271
|
|
|
272
272
|
[[package]]
|
|
273
273
|
name = "zrip-decode"
|
|
274
|
-
version = "0.8.
|
|
274
|
+
version = "0.8.10"
|
|
275
275
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
276
|
-
checksum = "
|
|
276
|
+
checksum = "aca029cf83b6a6e282008a9aa1079e13f216b28bac670eb1fb0769c1d256cb0e"
|
|
277
277
|
dependencies = [
|
|
278
278
|
"fearless_simd",
|
|
279
279
|
"zrip-core",
|
|
@@ -281,9 +281,10 @@ dependencies = [
|
|
|
281
281
|
|
|
282
282
|
[[package]]
|
|
283
283
|
name = "zrip-encode"
|
|
284
|
-
version = "0.
|
|
284
|
+
version = "0.9.0"
|
|
285
285
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
286
|
-
checksum = "
|
|
286
|
+
checksum = "b657fa83287fe6799596e53540a7b1f179ca34ffd8589d5c576a5b8a1bcc4260"
|
|
287
287
|
dependencies = [
|
|
288
|
+
"fearless_simd",
|
|
288
289
|
"zrip-core",
|
|
289
290
|
]
|
data/Cargo.toml
CHANGED
data/README.md
CHANGED
|
@@ -58,7 +58,7 @@ original = codec.decompress(compressed)
|
|
|
58
58
|
|
|
59
59
|
```ruby
|
|
60
60
|
fast = Zrip::FrameCodec.new(level: -3) # negative = faster
|
|
61
|
-
strong = Zrip::FrameCodec.new(level:
|
|
61
|
+
strong = Zrip::FrameCodec.new(level: 4) # highest supported level
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
### Bounded decompression
|
data/ext/zrip/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "zrip"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
build = "build.rs"
|
|
6
6
|
|
|
@@ -9,8 +9,8 @@ name = "zrip"
|
|
|
9
9
|
crate-type = ["cdylib", "rlib"]
|
|
10
10
|
|
|
11
11
|
[dependencies]
|
|
12
|
-
zstd = { version = "0.8.
|
|
13
|
-
rb-sys = "0.9"
|
|
12
|
+
zstd = { version = "0.8.11", package = "zrip", features = ["frame", "dict_builder"] }
|
|
13
|
+
rb-sys = "0.9.130"
|
|
14
14
|
|
|
15
15
|
[build-dependencies]
|
|
16
|
-
rb-sys = "0.9"
|
|
16
|
+
rb-sys = "0.9.130"
|
data/ext/zrip/src/lib.rs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
mod rb;
|
|
2
2
|
|
|
3
3
|
use std::ffi::c_void;
|
|
4
|
-
use std::io::Read;
|
|
5
4
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
6
5
|
use std::sync::{Mutex, OnceLock, TryLockError};
|
|
7
6
|
|
|
@@ -18,7 +17,6 @@ static OUTPUT_SIZE_LIMIT_ERROR: OnceLock<GlobalValue> = OnceLock::new();
|
|
|
18
17
|
|
|
19
18
|
const GVL_COMPRESS_THRESHOLD: usize = 64 * 1024;
|
|
20
19
|
const GVL_FRAME_DECOMPRESS_THRESHOLD: usize = 64 * 1024;
|
|
21
|
-
const DECOMPRESS_READ_CHUNK: usize = 16 * 1024;
|
|
22
20
|
|
|
23
21
|
#[derive(Copy, Clone)]
|
|
24
22
|
struct GlobalValue(VALUE);
|
|
@@ -52,27 +50,38 @@ fn should_release_frame_decompress_gvl(output_len: usize) -> bool {
|
|
|
52
50
|
output_len >= GVL_FRAME_DECOMPRESS_THRESHOLD
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
fn with_mutex<T, R, F>(
|
|
53
|
+
fn with_mutex<T, R, F>(
|
|
54
|
+
mutex: &Mutex<T>,
|
|
55
|
+
release_gvl: bool,
|
|
56
|
+
name: &str,
|
|
57
|
+
input: &mut rb::InputBytes,
|
|
58
|
+
func: F,
|
|
59
|
+
) -> RbResult<R>
|
|
56
60
|
where
|
|
57
|
-
F: FnOnce(&mut T) -> RbResult<R>,
|
|
61
|
+
F: FnOnce(&mut T, &[u8]) -> RbResult<R>,
|
|
58
62
|
{
|
|
59
63
|
if release_gvl {
|
|
64
|
+
input.lock_for_without_gvl(true)?;
|
|
60
65
|
return rb::maybe_without_gvl(true, || {
|
|
61
66
|
let mut guard = mutex
|
|
62
67
|
.lock()
|
|
63
68
|
.map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
|
|
64
|
-
func(&mut guard)
|
|
65
|
-
})
|
|
69
|
+
func(&mut guard, input.as_slice())
|
|
70
|
+
})?;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
match mutex.try_lock() {
|
|
69
|
-
Ok(mut guard) => func(&mut guard),
|
|
70
|
-
Err(TryLockError::WouldBlock) =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
Ok(mut guard) => func(&mut guard, input.as_slice()),
|
|
75
|
+
Err(TryLockError::WouldBlock) => {
|
|
76
|
+
// Even a small input needs protection when contention releases GVL.
|
|
77
|
+
input.lock_for_without_gvl(true)?;
|
|
78
|
+
rb::maybe_without_gvl(true, || {
|
|
79
|
+
let mut guard = mutex
|
|
80
|
+
.lock()
|
|
81
|
+
.map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
|
|
82
|
+
func(&mut guard, input.as_slice())
|
|
83
|
+
})?
|
|
84
|
+
}
|
|
76
85
|
Err(TryLockError::Poisoned(_)) => Err(RubyErr::runtime(format!("{name} mutex poisoned"))),
|
|
77
86
|
}
|
|
78
87
|
}
|
|
@@ -89,119 +98,36 @@ enum BoundedError {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
fn parse_frame_content_size(input: &[u8]) -> Result<Option<u64>, BoundedError> {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let fhd = input[4];
|
|
99
|
-
let fcs_flag = (fhd >> 6) & 3;
|
|
100
|
-
let single_segment = (fhd >> 5) & 1;
|
|
101
|
-
let dict_id_flag = fhd & 3;
|
|
102
|
-
|
|
103
|
-
let window_desc_size = if single_segment == 0 { 1usize } else { 0 };
|
|
104
|
-
let dict_id_size = [0usize, 1, 2, 4][dict_id_flag as usize];
|
|
105
|
-
let fcs_field_size = match fcs_flag {
|
|
106
|
-
0 => {
|
|
107
|
-
if single_segment == 1 {
|
|
108
|
-
1usize
|
|
109
|
-
} else {
|
|
110
|
-
return Ok(None);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
1 => 2,
|
|
114
|
-
2 => 4,
|
|
115
|
-
3 => 8,
|
|
116
|
-
_ => unreachable!(),
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
let fcs_offset = 5 + window_desc_size + dict_id_size;
|
|
120
|
-
if input.len() < fcs_offset + fcs_field_size {
|
|
121
|
-
return Err(BoundedError::BadMagic);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
let fcs_bytes = &input[fcs_offset..fcs_offset + fcs_field_size];
|
|
125
|
-
let value = match fcs_field_size {
|
|
126
|
-
1 => fcs_bytes[0] as u64,
|
|
127
|
-
2 => u16::from_le_bytes([fcs_bytes[0], fcs_bytes[1]]) as u64 + 256,
|
|
128
|
-
4 => u32::from_le_bytes([fcs_bytes[0], fcs_bytes[1], fcs_bytes[2], fcs_bytes[3]]) as u64,
|
|
129
|
-
8 => u64::from_le_bytes(fcs_bytes.try_into().unwrap()),
|
|
130
|
-
_ => unreachable!(),
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
Ok(Some(value))
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
fn io_error_is_output_too_small(err: &std::io::Error) -> bool {
|
|
137
|
-
matches!(
|
|
138
|
-
err.get_ref()
|
|
139
|
-
.and_then(|inner| inner.downcast_ref::<zstd::error::DecompressError>()),
|
|
140
|
-
Some(zstd::error::DecompressError::OutputTooSmall)
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
fn read_decoder_bounded<R: Read>(
|
|
145
|
-
decoder: &mut R,
|
|
146
|
-
max_output: usize,
|
|
147
|
-
) -> Result<Vec<u8>, BoundedError> {
|
|
148
|
-
let mut output = Vec::new();
|
|
149
|
-
let mut buf = [0u8; DECOMPRESS_READ_CHUNK];
|
|
150
|
-
|
|
151
|
-
loop {
|
|
152
|
-
let n = decoder.read(&mut buf).map_err(|e| {
|
|
153
|
-
if io_error_is_output_too_small(&e) {
|
|
154
|
-
BoundedError::OutputSizeLimit {
|
|
155
|
-
limit: max_output as u64,
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
BoundedError::DecoderFailed(format!("{e}"))
|
|
159
|
-
}
|
|
160
|
-
})?;
|
|
161
|
-
if n == 0 {
|
|
162
|
-
return Ok(output);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let Some(next_len) = output.len().checked_add(n) else {
|
|
166
|
-
return Err(BoundedError::OutputSizeLimit {
|
|
167
|
-
limit: max_output as u64,
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
if next_len > max_output {
|
|
171
|
-
return Err(BoundedError::OutputSizeLimit {
|
|
172
|
-
limit: max_output as u64,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
output.extend_from_slice(&buf[..n]);
|
|
177
|
-
}
|
|
101
|
+
zstd::frame::header::parse_frame_header(input)
|
|
102
|
+
.map(|header| header.frame_content_size)
|
|
103
|
+
.map_err(|err| match err {
|
|
104
|
+
zstd::DecompressError::BadMagic => BoundedError::BadMagic,
|
|
105
|
+
_ => BoundedError::DecoderFailed(err.to_string()),
|
|
106
|
+
})
|
|
178
107
|
}
|
|
179
108
|
|
|
180
109
|
fn decompress_bounded(
|
|
181
110
|
compressed: &[u8],
|
|
182
111
|
max_output: usize,
|
|
183
112
|
dctx: &mut DecompressContext,
|
|
184
|
-
dict: Option<&Dictionary>,
|
|
185
113
|
) -> Result<Vec<u8>, BoundedError> {
|
|
186
114
|
if compressed.len() < ZSTD_FRAME_MAGIC.len() || compressed[..4] != ZSTD_FRAME_MAGIC {
|
|
187
115
|
return Err(BoundedError::BadMagic);
|
|
188
116
|
}
|
|
189
117
|
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
.map(|out| out.into_owned())
|
|
193
|
-
.map_err(|e| BoundedError::DecoderFailed(format!("{e}")))?
|
|
118
|
+
let limit = if max_output == 0 {
|
|
119
|
+
usize::MAX
|
|
194
120
|
} else {
|
|
195
|
-
|
|
196
|
-
Some(dict) => {
|
|
197
|
-
zstd::FrameDecoder::with_dict_and_limit(compressed, dict.clone(), usize::MAX)
|
|
198
|
-
}
|
|
199
|
-
None => zstd::FrameDecoder::with_limit(compressed, usize::MAX),
|
|
200
|
-
};
|
|
201
|
-
read_decoder_bounded(&mut decoder, max_output)?
|
|
121
|
+
max_output
|
|
202
122
|
};
|
|
203
|
-
|
|
204
|
-
|
|
123
|
+
dctx.decompress_with_limit(compressed, limit)
|
|
124
|
+
.map(|out| out.into_owned())
|
|
125
|
+
.map_err(|err| match err {
|
|
126
|
+
zstd::DecompressError::OutputTooSmall => BoundedError::OutputSizeLimit {
|
|
127
|
+
limit: max_output as u64,
|
|
128
|
+
},
|
|
129
|
+
_ => BoundedError::DecoderFailed(err.to_string()),
|
|
130
|
+
})
|
|
205
131
|
}
|
|
206
132
|
|
|
207
133
|
fn decompress_work_size(compressed: &[u8], max_output: usize) -> usize {
|
|
@@ -277,7 +203,7 @@ fn make_frame_codec_data_type() -> rb_data_type_t {
|
|
|
277
203
|
},
|
|
278
204
|
parent: std::ptr::null(),
|
|
279
205
|
data: std::ptr::null_mut(),
|
|
280
|
-
flags:
|
|
206
|
+
flags: rb::frozen_shareable_flags(),
|
|
281
207
|
}
|
|
282
208
|
}
|
|
283
209
|
|
|
@@ -372,7 +298,6 @@ unsafe fn dict_trainer_ref(value: VALUE) -> RbResult<&'static RbDictTrainer> {
|
|
|
372
298
|
struct FrameCodec {
|
|
373
299
|
dict_len: usize,
|
|
374
300
|
dict_id: Option<u32>,
|
|
375
|
-
dict: Option<Dictionary>,
|
|
376
301
|
level: i32,
|
|
377
302
|
cctx: Mutex<CompressContext>,
|
|
378
303
|
dctx: Mutex<DecompressContext>,
|
|
@@ -382,16 +307,10 @@ unsafe impl Send for FrameCodec {}
|
|
|
382
307
|
unsafe impl Sync for FrameCodec {}
|
|
383
308
|
|
|
384
309
|
fn frame_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -> RbResult<VALUE> {
|
|
385
|
-
let rb_dict =
|
|
386
|
-
None
|
|
387
|
-
} else {
|
|
388
|
-
let rb_dict = rb::string_value(rb_dict)?;
|
|
389
|
-
rb::freeze_value(rb_dict)?;
|
|
390
|
-
Some(rb::value_to_bytes(rb_dict)?)
|
|
391
|
-
};
|
|
310
|
+
let rb_dict = rb::value_to_option_bytes(rb_dict)?;
|
|
392
311
|
let id = rb::value_to_u32(id)?;
|
|
393
312
|
let level = rb::value_to_i32(level)?;
|
|
394
|
-
let (dict_len, dict_id,
|
|
313
|
+
let (dict_len, dict_id, cctx, dctx) = match rb_dict {
|
|
395
314
|
None => {
|
|
396
315
|
let cctx = CompressContext::new(level).map_err(|e| {
|
|
397
316
|
RubyErr::new(
|
|
@@ -399,7 +318,7 @@ fn frame_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -
|
|
|
399
318
|
format!("CompressContext::new failed: {e}"),
|
|
400
319
|
)
|
|
401
320
|
})?;
|
|
402
|
-
(0, None,
|
|
321
|
+
(0, None, cctx, DecompressContext::new())
|
|
403
322
|
}
|
|
404
323
|
Some(bytes) => {
|
|
405
324
|
let dict = load_dict(&bytes)?;
|
|
@@ -411,36 +330,42 @@ fn frame_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -
|
|
|
411
330
|
)
|
|
412
331
|
})?;
|
|
413
332
|
let dctx = DecompressContext::with_dict(dict.clone());
|
|
414
|
-
(dl, Some(id),
|
|
333
|
+
(dl, Some(id), cctx, dctx)
|
|
415
334
|
}
|
|
416
335
|
};
|
|
417
336
|
|
|
418
|
-
unsafe {
|
|
337
|
+
let codec = unsafe {
|
|
419
338
|
rb::wrap_typed_data(
|
|
420
339
|
class,
|
|
421
340
|
Box::new(FrameCodec {
|
|
422
341
|
dict_len,
|
|
423
342
|
dict_id,
|
|
424
|
-
dict,
|
|
425
343
|
level,
|
|
426
344
|
cctx: Mutex::new(cctx),
|
|
427
345
|
dctx: Mutex::new(dctx),
|
|
428
346
|
}),
|
|
429
347
|
frame_codec_data_type(),
|
|
430
|
-
)
|
|
431
|
-
}
|
|
348
|
+
)?
|
|
349
|
+
};
|
|
350
|
+
rb::make_shareable(codec)?;
|
|
351
|
+
Ok(codec)
|
|
432
352
|
}
|
|
433
353
|
|
|
434
354
|
fn frame_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
|
|
435
355
|
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
436
356
|
let mut input = rb::input_bytes(rb_input)?;
|
|
437
357
|
let release_gvl = should_release_compress_gvl(input.len());
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
358
|
+
let out = with_mutex(
|
|
359
|
+
&rb_self.cctx,
|
|
360
|
+
release_gvl,
|
|
361
|
+
"FrameCodec CCtx",
|
|
362
|
+
&mut input,
|
|
363
|
+
|cctx, input| {
|
|
364
|
+
cctx.compress(input)
|
|
365
|
+
.map(|out| out.into_owned())
|
|
366
|
+
.map_err(|e| RubyErr::new(compress_error(), format!("zstd compress failed: {e}")))
|
|
367
|
+
},
|
|
368
|
+
)?;
|
|
444
369
|
rb::new_binary_string(&out)
|
|
445
370
|
}
|
|
446
371
|
|
|
@@ -450,18 +375,22 @@ fn frame_codec_decompress_impl(
|
|
|
450
375
|
max_output: VALUE,
|
|
451
376
|
) -> RbResult<VALUE> {
|
|
452
377
|
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
453
|
-
let mut compressed = rb::input_bytes(rb_input)?;
|
|
454
378
|
let max_output = rb::value_to_usize(max_output)?;
|
|
379
|
+
let mut compressed = rb::input_bytes(rb_input)?;
|
|
455
380
|
let release_gvl = should_release_frame_decompress_gvl(decompress_work_size(
|
|
456
381
|
compressed.as_slice(),
|
|
457
382
|
max_output,
|
|
458
383
|
));
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
384
|
+
let out = with_mutex(
|
|
385
|
+
&rb_self.dctx,
|
|
386
|
+
release_gvl,
|
|
387
|
+
"FrameCodec DCtx",
|
|
388
|
+
&mut compressed,
|
|
389
|
+
|dctx, compressed| {
|
|
390
|
+
decompress_bounded(compressed, max_output, dctx)
|
|
391
|
+
.map_err(|e| bounded_err(e, "zstd frame decode failed"))
|
|
392
|
+
},
|
|
393
|
+
)?;
|
|
465
394
|
rb::new_binary_string(&out)
|
|
466
395
|
}
|
|
467
396
|
|
|
@@ -543,7 +472,6 @@ unsafe extern "C" fn frame_codec_get_frame_content_size(_class: VALUE, rb_input:
|
|
|
543
472
|
struct BlockCodec {
|
|
544
473
|
dict_len: usize,
|
|
545
474
|
dict_id: Option<u32>,
|
|
546
|
-
dict: Option<Dictionary>,
|
|
547
475
|
level: i32,
|
|
548
476
|
cctx: Mutex<CompressContext>,
|
|
549
477
|
dctx: Mutex<DecompressContext>,
|
|
@@ -553,7 +481,7 @@ fn block_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -
|
|
|
553
481
|
let rb_dict = rb::value_to_option_bytes(rb_dict)?;
|
|
554
482
|
let id = rb::value_to_u32(id)?;
|
|
555
483
|
let level = rb::value_to_i32(level)?;
|
|
556
|
-
let (dict_len, dict_id,
|
|
484
|
+
let (dict_len, dict_id, cctx, dctx) = match rb_dict {
|
|
557
485
|
None => {
|
|
558
486
|
let cctx = CompressContext::new(level).map_err(|e| {
|
|
559
487
|
RubyErr::new(
|
|
@@ -561,7 +489,7 @@ fn block_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -
|
|
|
561
489
|
format!("CompressContext::new failed: {e}"),
|
|
562
490
|
)
|
|
563
491
|
})?;
|
|
564
|
-
(0, None,
|
|
492
|
+
(0, None, cctx, DecompressContext::new())
|
|
565
493
|
}
|
|
566
494
|
Some(bytes) => {
|
|
567
495
|
let dict = load_dict(&bytes)?;
|
|
@@ -573,7 +501,7 @@ fn block_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -
|
|
|
573
501
|
)
|
|
574
502
|
})?;
|
|
575
503
|
let dctx = DecompressContext::with_dict(dict.clone());
|
|
576
|
-
(dl, Some(id),
|
|
504
|
+
(dl, Some(id), cctx, dctx)
|
|
577
505
|
}
|
|
578
506
|
};
|
|
579
507
|
|
|
@@ -583,7 +511,6 @@ fn block_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE, level: VALUE) -
|
|
|
583
511
|
Box::new(BlockCodec {
|
|
584
512
|
dict_len,
|
|
585
513
|
dict_id,
|
|
586
|
-
dict,
|
|
587
514
|
level,
|
|
588
515
|
cctx: Mutex::new(cctx),
|
|
589
516
|
dctx: Mutex::new(dctx),
|
|
@@ -597,12 +524,17 @@ fn block_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE>
|
|
|
597
524
|
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
598
525
|
let mut input = rb::input_bytes(rb_input)?;
|
|
599
526
|
let release_gvl = should_release_compress_gvl(input.len());
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
527
|
+
let out = with_mutex(
|
|
528
|
+
&rb_self.cctx,
|
|
529
|
+
release_gvl,
|
|
530
|
+
"BlockCodec CCtx",
|
|
531
|
+
&mut input,
|
|
532
|
+
|cctx, input| {
|
|
533
|
+
cctx.compress(input)
|
|
534
|
+
.map(|out| out.into_owned())
|
|
535
|
+
.map_err(|e| RubyErr::new(compress_error(), format!("zstd compress failed: {e}")))
|
|
536
|
+
},
|
|
537
|
+
)?;
|
|
606
538
|
rb::new_binary_string(&out)
|
|
607
539
|
}
|
|
608
540
|
|
|
@@ -612,17 +544,18 @@ fn block_codec_decompress_impl(
|
|
|
612
544
|
max_output: VALUE,
|
|
613
545
|
) -> RbResult<VALUE> {
|
|
614
546
|
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
615
|
-
let compressed = rb::input_bytes(rb_input)?;
|
|
616
547
|
let max_output = rb::value_to_usize(max_output)?;
|
|
617
|
-
let
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
548
|
+
let mut compressed = rb::input_bytes(rb_input)?;
|
|
549
|
+
let out = with_mutex(
|
|
550
|
+
&rb_self.dctx,
|
|
551
|
+
false,
|
|
552
|
+
"BlockCodec DCtx",
|
|
553
|
+
&mut compressed,
|
|
554
|
+
|dctx, compressed| {
|
|
555
|
+
decompress_bounded(compressed, max_output, dctx)
|
|
556
|
+
.map_err(|e| bounded_err(e, "zstd block decode failed"))
|
|
557
|
+
},
|
|
558
|
+
)?;
|
|
626
559
|
rb::new_binary_string(&out)
|
|
627
560
|
}
|
|
628
561
|
|
|
@@ -704,6 +637,7 @@ fn dict_trainer_new_impl(class: VALUE, max_dict_size: VALUE) -> RbResult<VALUE>
|
|
|
704
637
|
}
|
|
705
638
|
|
|
706
639
|
fn dict_trainer_add_sample_impl(rb_self: VALUE, rb_data: VALUE) -> RbResult<VALUE> {
|
|
640
|
+
let data = rb::value_to_bytes(rb_data)?;
|
|
707
641
|
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
708
642
|
let mut borrow = rb_self
|
|
709
643
|
.inner
|
|
@@ -712,7 +646,6 @@ fn dict_trainer_add_sample_impl(rb_self: VALUE, rb_data: VALUE) -> RbResult<VALU
|
|
|
712
646
|
let state = borrow
|
|
713
647
|
.as_mut()
|
|
714
648
|
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
715
|
-
let data = rb::value_to_bytes(rb_data)?;
|
|
716
649
|
if data.len() < 4 {
|
|
717
650
|
return Ok(rb::qnil());
|
|
718
651
|
}
|
|
@@ -960,11 +893,11 @@ mod tests {
|
|
|
960
893
|
compressed.extend_from_slice(&zstd::compress(&[b'b'; 100], 1).unwrap());
|
|
961
894
|
|
|
962
895
|
assert!(matches!(
|
|
963
|
-
decompress_bounded(&compressed, 199, &mut dctx
|
|
896
|
+
decompress_bounded(&compressed, 199, &mut dctx),
|
|
964
897
|
Err(BoundedError::OutputSizeLimit { .. })
|
|
965
898
|
));
|
|
966
899
|
assert_eq!(
|
|
967
|
-
decompress_bounded(&compressed, 200, &mut dctx
|
|
900
|
+
decompress_bounded(&compressed, 200, &mut dctx)
|
|
968
901
|
.unwrap()
|
|
969
902
|
.len(),
|
|
970
903
|
200
|
data/ext/zrip/src/rb.rs
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
use std::ffi::{c_char, c_long, c_void, CStr, CString};
|
|
2
|
-
#[cfg(ruby_engine = "mri")]
|
|
3
|
-
use std::mem::MaybeUninit;
|
|
4
2
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
5
3
|
use std::ptr;
|
|
6
4
|
|
|
@@ -32,7 +30,7 @@ unsafe extern "C" {
|
|
|
32
30
|
|
|
33
31
|
#[derive(Debug)]
|
|
34
32
|
pub enum RubyErr {
|
|
35
|
-
|
|
33
|
+
Jump(i32),
|
|
36
34
|
Error { class: VALUE, message: String },
|
|
37
35
|
}
|
|
38
36
|
|
|
@@ -55,15 +53,6 @@ impl RubyErr {
|
|
|
55
53
|
pub fn type_error(message: impl Into<String>) -> Self {
|
|
56
54
|
Self::new(unsafe { rb_sys::rb_eTypeError }, message)
|
|
57
55
|
}
|
|
58
|
-
|
|
59
|
-
fn current_exception() -> Self {
|
|
60
|
-
let err = unsafe { rb_sys::rb_errinfo() };
|
|
61
|
-
if err == qnil() {
|
|
62
|
-
Self::runtime("Ruby exception")
|
|
63
|
-
} else {
|
|
64
|
-
Self::Exception(err)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
56
|
}
|
|
68
57
|
|
|
69
58
|
pub fn wrap<F>(f: F) -> VALUE
|
|
@@ -89,12 +78,15 @@ where
|
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
pub fn raise(err: RubyErr) -> ! {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
match err {
|
|
82
|
+
// Preserve Ruby's tag and pending error info for Thread#kill and
|
|
83
|
+
// catch/throw, as well as ordinary exceptions.
|
|
84
|
+
RubyErr::Jump(state) => unsafe { rb_sys::rb_jump_tag(state) },
|
|
85
|
+
RubyErr::Error { class, message } => {
|
|
86
|
+
let exc = error_exception(class, message);
|
|
87
|
+
unsafe { rb_sys::rb_exc_raise(exc) }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
98
90
|
}
|
|
99
91
|
|
|
100
92
|
fn error_exception(class: VALUE, message: String) -> VALUE {
|
|
@@ -145,7 +137,7 @@ where
|
|
|
145
137
|
};
|
|
146
138
|
|
|
147
139
|
if state != 0 {
|
|
148
|
-
Err(RubyErr::
|
|
140
|
+
Err(RubyErr::Jump(state))
|
|
149
141
|
} else if data.panicked {
|
|
150
142
|
Err(RubyErr::runtime("native Rust panic"))
|
|
151
143
|
} else {
|
|
@@ -167,7 +159,7 @@ where
|
|
|
167
159
|
#[cfg(ruby_engine = "mri")]
|
|
168
160
|
struct WithoutGvlData<F, R> {
|
|
169
161
|
func: Option<F>,
|
|
170
|
-
output:
|
|
162
|
+
output: Option<std::thread::Result<R>>,
|
|
171
163
|
}
|
|
172
164
|
|
|
173
165
|
#[cfg(ruby_engine = "mri")]
|
|
@@ -176,49 +168,55 @@ where
|
|
|
176
168
|
F: FnOnce() -> R,
|
|
177
169
|
{
|
|
178
170
|
let data = unsafe { &mut *(data.cast::<WithoutGvlData<F, R>>()) };
|
|
179
|
-
|
|
180
|
-
data.output
|
|
171
|
+
// Rust panics must be caught before returning through the C callback.
|
|
172
|
+
data.output = Some(catch_unwind(AssertUnwindSafe(|| {
|
|
173
|
+
data.func.take().expect("missing without-GVL function")()
|
|
174
|
+
})));
|
|
181
175
|
ptr::null_mut()
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
#[cfg(ruby_engine = "mri")]
|
|
185
|
-
fn without_gvl<F, R>(func: F) -> R
|
|
179
|
+
fn without_gvl<F, R>(func: F) -> RbResult<R>
|
|
186
180
|
where
|
|
187
181
|
F: FnOnce() -> R,
|
|
188
182
|
{
|
|
189
183
|
let mut data = WithoutGvlData {
|
|
190
184
|
func: Some(func),
|
|
191
|
-
output:
|
|
185
|
+
output: None,
|
|
192
186
|
};
|
|
193
|
-
|
|
187
|
+
// Ruby checks interrupts before and after the callback. Protect the call
|
|
188
|
+
// so an exception still drops native output and unlocks borrowed strings.
|
|
189
|
+
protect_unit(|| unsafe {
|
|
194
190
|
rb_thread_call_without_gvl(
|
|
195
191
|
Some(without_gvl_trampoline::<F, R>),
|
|
196
192
|
(&mut data as *mut WithoutGvlData<F, R>).cast::<c_void>(),
|
|
197
193
|
None,
|
|
198
194
|
ptr::null_mut(),
|
|
199
195
|
);
|
|
200
|
-
|
|
201
|
-
|
|
196
|
+
})?;
|
|
197
|
+
data.output
|
|
198
|
+
.ok_or_else(|| RubyErr::runtime("without-GVL callback did not run"))?
|
|
199
|
+
.map_err(|_| RubyErr::runtime("native Rust panic"))
|
|
202
200
|
}
|
|
203
201
|
|
|
204
202
|
#[cfg(ruby_engine = "mri")]
|
|
205
|
-
pub fn maybe_without_gvl<F, R>(release_gvl: bool, func: F) -> R
|
|
203
|
+
pub fn maybe_without_gvl<F, R>(release_gvl: bool, func: F) -> RbResult<R>
|
|
206
204
|
where
|
|
207
205
|
F: FnOnce() -> R,
|
|
208
206
|
{
|
|
209
207
|
if release_gvl {
|
|
210
208
|
without_gvl(func)
|
|
211
209
|
} else {
|
|
212
|
-
func()
|
|
210
|
+
Ok(func())
|
|
213
211
|
}
|
|
214
212
|
}
|
|
215
213
|
|
|
216
214
|
#[cfg(not(ruby_engine = "mri"))]
|
|
217
|
-
pub fn maybe_without_gvl<F, R>(_release_gvl: bool, func: F) -> R
|
|
215
|
+
pub fn maybe_without_gvl<F, R>(_release_gvl: bool, func: F) -> RbResult<R>
|
|
218
216
|
where
|
|
219
217
|
F: FnOnce() -> R,
|
|
220
218
|
{
|
|
221
|
-
func()
|
|
219
|
+
Ok(func())
|
|
222
220
|
}
|
|
223
221
|
|
|
224
222
|
pub const fn qnil() -> VALUE {
|
|
@@ -255,8 +253,23 @@ pub fn string_value(value: VALUE) -> RbResult<VALUE> {
|
|
|
255
253
|
protect_value(|| unsafe { rb_sys::rb_str_to_str(value) })
|
|
256
254
|
}
|
|
257
255
|
|
|
258
|
-
pub fn
|
|
259
|
-
|
|
256
|
+
pub fn frozen_shareable_flags() -> VALUE {
|
|
257
|
+
#[cfg(ruby_engine = "mri")]
|
|
258
|
+
{
|
|
259
|
+
// RUBY_TYPED_FROZEN_SHAREABLE equals RUBY_FL_SHAREABLE in Ruby headers.
|
|
260
|
+
1 | rb_sys::ruby_fl_type::RUBY_FL_SHAREABLE as VALUE
|
|
261
|
+
}
|
|
262
|
+
#[cfg(not(ruby_engine = "mri"))]
|
|
263
|
+
{
|
|
264
|
+
1
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
pub fn make_shareable(value: VALUE) -> RbResult<()> {
|
|
269
|
+
protect_unit(|| unsafe { rb_sys::RB_OBJ_FREEZE(value) })?;
|
|
270
|
+
#[cfg(ruby_engine = "mri")]
|
|
271
|
+
protect_value(|| unsafe { rb_sys::rb_ractor_make_shareable(value) })?;
|
|
272
|
+
Ok(())
|
|
260
273
|
}
|
|
261
274
|
|
|
262
275
|
pub fn value_to_bytes(value: VALUE) -> RbResult<Vec<u8>> {
|
|
@@ -613,3 +626,42 @@ unsafe fn transmute_3(
|
|
|
613
626
|
fn c_long_len(len: usize) -> RbResult<c_long> {
|
|
614
627
|
c_long::try_from(len).map_err(|_| RubyErr::arg("length too large"))
|
|
615
628
|
}
|
|
629
|
+
|
|
630
|
+
#[cfg(all(test, ruby_engine = "mri"))]
|
|
631
|
+
mod tests {
|
|
632
|
+
use super::*;
|
|
633
|
+
|
|
634
|
+
fn run_callback<F, R>(func: F) -> std::thread::Result<R>
|
|
635
|
+
where
|
|
636
|
+
F: FnOnce() -> R,
|
|
637
|
+
{
|
|
638
|
+
let mut data = WithoutGvlData {
|
|
639
|
+
func: Some(func),
|
|
640
|
+
output: None,
|
|
641
|
+
};
|
|
642
|
+
unsafe {
|
|
643
|
+
without_gvl_trampoline::<F, R>(
|
|
644
|
+
(&mut data as *mut WithoutGvlData<F, R>).cast::<c_void>(),
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
data.output.unwrap()
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
#[test]
|
|
651
|
+
fn callback_catches_panics_before_returning_to_c() {
|
|
652
|
+
let dropped = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
|
653
|
+
struct OnDrop(std::sync::Arc<std::sync::atomic::AtomicBool>);
|
|
654
|
+
impl Drop for OnDrop {
|
|
655
|
+
fn drop(&mut self) {
|
|
656
|
+
self.0.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
let captured = OnDrop(dropped.clone());
|
|
660
|
+
let result = run_callback(move || {
|
|
661
|
+
let _captured = captured;
|
|
662
|
+
panic!("callback panic");
|
|
663
|
+
});
|
|
664
|
+
assert!(result.is_err());
|
|
665
|
+
assert!(dropped.load(std::sync::atomic::Ordering::SeqCst));
|
|
666
|
+
}
|
|
667
|
+
}
|
data/lib/zrip/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zrip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -16,6 +16,9 @@ dependencies:
|
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '0.9'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.9.130
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -23,6 +26,9 @@ dependencies:
|
|
|
23
26
|
- - "~>"
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '0.9'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.9.130
|
|
26
32
|
description: Ruby bindings (via Rust/rb-sys) for zrip, a pure-Rust Zstandard implementation.
|
|
27
33
|
Frame-format and block-format compress/decompress with optional dictionary support,
|
|
28
34
|
configurable compression levels, and FastCOVER-based dictionary training. Ractor-safe.
|
|
@@ -54,7 +60,7 @@ licenses:
|
|
|
54
60
|
- MIT
|
|
55
61
|
metadata:
|
|
56
62
|
homepage_uri: https://github.com/paddor/zrip-rb
|
|
57
|
-
source_code_uri: https://github.com/paddor/zrip-rb
|
|
63
|
+
source_code_uri: https://github.com/paddor/zrip-rb/tree/main
|
|
58
64
|
documentation_uri: https://rubydoc.info/gems/zrip
|
|
59
65
|
changelog_uri: https://github.com/paddor/zrip-rb/blob/main/CHANGELOG.md
|
|
60
66
|
rubygems_mfa_required: 'true'
|
|
@@ -72,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
78
|
- !ruby/object:Gem::Version
|
|
73
79
|
version: '0'
|
|
74
80
|
requirements: []
|
|
75
|
-
rubygems_version: 4.0.
|
|
81
|
+
rubygems_version: 4.0.20
|
|
76
82
|
specification_version: 4
|
|
77
83
|
summary: Ractor-safe Zstandard bindings for Ruby (pure-Rust zrip backend)
|
|
78
84
|
test_files: []
|