lz4rip 0.1.1 → 0.2.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.
@@ -1,117 +1,325 @@
1
- use magnus::{
2
- exception::ExceptionClass, function, method, prelude::*, r_string::RString, value::Opaque,
3
- Error, Ruby,
4
- };
5
- use std::cell::RefCell;
1
+ mod rb;
2
+
3
+ use std::ffi::c_void;
6
4
  use std::io::{Cursor, Read, Write};
7
- use std::sync::OnceLock;
5
+ use std::panic::{catch_unwind, AssertUnwindSafe};
6
+ use std::sync::{Mutex, OnceLock, TryLockError};
7
+
8
+ use lz4::block::{self, Decompressor, DictCompressor, DictTrainer};
9
+ use lz4::frame::{BlockMode, FrameDecoder, FrameDecoderOptions, FrameEncoder, FrameInfo};
10
+ use rb_sys::{rb_data_type_struct__bindgen_ty_1, rb_data_type_t, size_t, VALUE};
8
11
 
9
- use lz4::block::{self, Compressor, Decompressor, DictTrainer};
10
- use lz4::frame::{BlockMode, FrameDecoder, FrameEncoder, FrameInfo};
12
+ use crate::rb::{RbResult, RubyErr};
11
13
 
12
14
  const COMPRESSOR_HEAP_SIZE: usize = 8192;
13
15
 
14
16
  const LZ4_FRAME_MAGIC: [u8; 4] = [0x04, 0x22, 0x4d, 0x18];
17
+ const GVL_COMPRESS_THRESHOLD: usize = 256 * 1024;
18
+ const GVL_FRAME_DECOMPRESS_THRESHOLD: usize = 256 * 1024;
19
+
20
+ static DECOMPRESS_ERROR: OnceLock<GlobalValue> = OnceLock::new();
21
+
22
+ #[derive(Copy, Clone)]
23
+ struct GlobalValue(VALUE);
24
+
25
+ unsafe impl Send for GlobalValue {}
26
+ unsafe impl Sync for GlobalValue {}
27
+
28
+ fn decompress_error() -> VALUE {
29
+ DECOMPRESS_ERROR
30
+ .get()
31
+ .expect("DecompressError not initialized")
32
+ .0
33
+ }
34
+
35
+ fn should_release_compress_gvl(input_len: usize) -> bool {
36
+ input_len >= GVL_COMPRESS_THRESHOLD
37
+ }
38
+
39
+ fn should_release_frame_decompress_gvl(input_len: usize) -> bool {
40
+ input_len >= GVL_FRAME_DECOMPRESS_THRESHOLD
41
+ }
42
+
43
+ fn with_mutex<T, R, F>(mutex: &Mutex<T>, release_gvl: bool, name: &str, func: F) -> RbResult<R>
44
+ where
45
+ F: FnOnce(&mut T) -> RbResult<R>,
46
+ {
47
+ if release_gvl {
48
+ return rb::maybe_without_gvl(true, || {
49
+ let mut guard = mutex
50
+ .lock()
51
+ .map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
52
+ func(&mut guard)
53
+ });
54
+ }
55
+
56
+ match mutex.try_lock() {
57
+ Ok(mut guard) => func(&mut guard),
58
+ Err(TryLockError::WouldBlock) => rb::maybe_without_gvl(true, || {
59
+ let mut guard = mutex
60
+ .lock()
61
+ .map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
62
+ func(&mut guard)
63
+ }),
64
+ Err(TryLockError::Poisoned(_)) => Err(RubyErr::runtime(format!("{name} mutex poisoned"))),
65
+ }
66
+ }
67
+
68
+ // ---------- typed data ----------
69
+
70
+ struct NativeDataType(rb_data_type_t);
71
+
72
+ unsafe impl Send for NativeDataType {}
73
+ unsafe impl Sync for NativeDataType {}
74
+
75
+ static BLOCK_CODEC_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
76
+ static FRAME_CODEC_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
77
+ static DICT_TRAINER_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
78
+
79
+ fn block_codec_data_type() -> *const rb_data_type_t {
80
+ &BLOCK_CODEC_DATA_TYPE
81
+ .get_or_init(|| NativeDataType(make_block_codec_data_type()))
82
+ .0
83
+ }
84
+
85
+ fn frame_codec_data_type() -> *const rb_data_type_t {
86
+ &FRAME_CODEC_DATA_TYPE
87
+ .get_or_init(|| NativeDataType(make_frame_codec_data_type()))
88
+ .0
89
+ }
90
+
91
+ fn dict_trainer_data_type() -> *const rb_data_type_t {
92
+ &DICT_TRAINER_DATA_TYPE
93
+ .get_or_init(|| NativeDataType(make_dict_trainer_data_type()))
94
+ .0
95
+ }
96
+
97
+ fn make_block_codec_data_type() -> rb_data_type_t {
98
+ rb_data_type_t {
99
+ wrap_struct_name: c"lz4rip_block_codec".as_ptr(),
100
+ function: rb_data_type_struct__bindgen_ty_1 {
101
+ dmark: None,
102
+ dfree: Some(block_codec_free),
103
+ dsize: Some(block_codec_native_size),
104
+ dcompact: None,
105
+ reserved: [std::ptr::null_mut(); 1],
106
+ },
107
+ parent: std::ptr::null(),
108
+ data: std::ptr::null_mut(),
109
+ flags: 1,
110
+ }
111
+ }
112
+
113
+ fn make_frame_codec_data_type() -> rb_data_type_t {
114
+ rb_data_type_t {
115
+ wrap_struct_name: c"lz4rip_frame_codec".as_ptr(),
116
+ function: rb_data_type_struct__bindgen_ty_1 {
117
+ dmark: None,
118
+ dfree: Some(frame_codec_free),
119
+ dsize: Some(frame_codec_native_size),
120
+ dcompact: None,
121
+ reserved: [std::ptr::null_mut(); 1],
122
+ },
123
+ parent: std::ptr::null(),
124
+ data: std::ptr::null_mut(),
125
+ flags: 1,
126
+ }
127
+ }
128
+
129
+ fn make_dict_trainer_data_type() -> rb_data_type_t {
130
+ rb_data_type_t {
131
+ wrap_struct_name: c"lz4rip_dict_trainer".as_ptr(),
132
+ function: rb_data_type_struct__bindgen_ty_1 {
133
+ dmark: None,
134
+ dfree: Some(dict_trainer_free),
135
+ dsize: Some(dict_trainer_native_size),
136
+ dcompact: None,
137
+ reserved: [std::ptr::null_mut(); 1],
138
+ },
139
+ parent: std::ptr::null(),
140
+ data: std::ptr::null_mut(),
141
+ flags: 1,
142
+ }
143
+ }
144
+
145
+ unsafe extern "C" fn block_codec_free(ptr: *mut c_void) {
146
+ if ptr.is_null() {
147
+ return;
148
+ }
149
+
150
+ let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
151
+ drop(Box::from_raw(ptr as *mut BlockCodec));
152
+ }));
153
+ }
154
+
155
+ unsafe extern "C" fn frame_codec_free(ptr: *mut c_void) {
156
+ if ptr.is_null() {
157
+ return;
158
+ }
159
+
160
+ let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
161
+ drop(Box::from_raw(ptr as *mut FrameCodec));
162
+ }));
163
+ }
164
+
165
+ unsafe extern "C" fn dict_trainer_free(ptr: *mut c_void) {
166
+ if ptr.is_null() {
167
+ return;
168
+ }
169
+
170
+ let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
171
+ drop(Box::from_raw(ptr as *mut RbDictTrainer));
172
+ }));
173
+ }
174
+
175
+ unsafe extern "C" fn block_codec_native_size(_ptr: *const c_void) -> size_t {
176
+ std::mem::size_of::<BlockCodec>() as size_t
177
+ }
178
+
179
+ unsafe extern "C" fn frame_codec_native_size(_ptr: *const c_void) -> size_t {
180
+ std::mem::size_of::<FrameCodec>() as size_t
181
+ }
182
+
183
+ unsafe extern "C" fn dict_trainer_native_size(_ptr: *const c_void) -> size_t {
184
+ std::mem::size_of::<RbDictTrainer>() as size_t
185
+ }
15
186
 
16
- static DECOMPRESS_ERROR: OnceLock<Opaque<ExceptionClass>> = OnceLock::new();
187
+ unsafe fn block_codec_ref(value: VALUE) -> RbResult<&'static BlockCodec> {
188
+ unsafe { rb::typed_data_ref(value, block_codec_data_type(), "Lz4rip::BlockCodec") }
189
+ }
17
190
 
18
- fn decompress_error(ruby: &Ruby) -> ExceptionClass {
19
- ruby.get_inner(
20
- *DECOMPRESS_ERROR
21
- .get()
22
- .expect("DecompressError not initialized"),
23
- )
191
+ unsafe fn frame_codec_ref(value: VALUE) -> RbResult<&'static FrameCodec> {
192
+ unsafe { rb::typed_data_ref(value, frame_codec_data_type(), "Lz4rip::FrameCodec") }
193
+ }
194
+
195
+ unsafe fn dict_trainer_ref(value: VALUE) -> RbResult<&'static RbDictTrainer> {
196
+ unsafe { rb::typed_data_ref(value, dict_trainer_data_type(), "Lz4rip::DictTrainer") }
24
197
  }
25
198
 
26
199
  // ---------- module functions ----------
27
200
 
28
- fn lz4rip_compress_bound(_ruby: &Ruby, size: usize) -> usize {
29
- block::get_maximum_output_size(size)
201
+ fn lz4rip_compress_bound_impl(size: VALUE) -> RbResult<VALUE> {
202
+ rb::usize_value(block::get_maximum_output_size(rb::value_to_usize(size)?))
30
203
  }
31
204
 
32
- fn lz4rip_block_stream_size(_ruby: &Ruby) -> usize {
33
- COMPRESSOR_HEAP_SIZE
205
+ fn lz4rip_block_stream_size_impl() -> RbResult<VALUE> {
206
+ rb::usize_value(COMPRESSOR_HEAP_SIZE)
207
+ }
208
+
209
+ unsafe extern "C" fn lz4rip_compress_bound(_module: VALUE, size: VALUE) -> VALUE {
210
+ rb::wrap(|| lz4rip_compress_bound_impl(size))
211
+ }
212
+
213
+ unsafe extern "C" fn lz4rip_block_stream_size(_module: VALUE) -> VALUE {
214
+ rb::wrap(lz4rip_block_stream_size_impl)
34
215
  }
35
216
 
36
217
  // ---------- BlockCodec ----------
37
218
 
38
- #[magnus::wrap(class = "Lz4rip::BlockCodec", free_immediately, size)]
39
219
  struct BlockCodec {
40
- compressor: Option<RefCell<Compressor>>,
41
- decompressor: Option<Decompressor>,
220
+ compressor: Option<Mutex<DictCompressor>>,
221
+ decompressor: Option<Mutex<Decompressor>>,
42
222
  dict_len: usize,
43
223
  }
44
224
 
45
- fn block_codec_new(_ruby: &Ruby, rb_dict: Option<RString>) -> Result<BlockCodec, Error> {
46
- match rb_dict {
47
- None => Ok(BlockCodec {
225
+ fn block_codec_new_impl(class: VALUE, rb_dict: VALUE) -> RbResult<VALUE> {
226
+ let codec = match rb::value_to_option_bytes(rb_dict)? {
227
+ None => BlockCodec {
48
228
  compressor: None,
49
229
  decompressor: None,
50
230
  dict_len: 0,
51
- }),
52
- Some(rb_dict) => {
53
- let bytes: Vec<u8> = unsafe { rb_dict.as_slice().to_vec() };
54
- Ok(BlockCodec {
55
- compressor: Some(RefCell::new(Compressor::with_dict(&bytes))),
56
- decompressor: Some(Decompressor::with_dict(&bytes)),
57
- dict_len: bytes.len(),
58
- })
59
- }
60
- }
231
+ },
232
+ Some(bytes) => BlockCodec {
233
+ compressor: Some(Mutex::new(DictCompressor::new(&bytes))),
234
+ decompressor: Some(Mutex::new(Decompressor::with_dict(&bytes))),
235
+ dict_len: bytes.len(),
236
+ },
237
+ };
238
+
239
+ unsafe { rb::wrap_typed_data(class, Box::new(codec), block_codec_data_type()) }
61
240
  }
62
241
 
63
- fn block_codec_size(rb_self: &BlockCodec) -> usize {
242
+ fn block_codec_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
243
+ let rb_self = unsafe { block_codec_ref(rb_self)? };
64
244
  if rb_self.compressor.is_some() {
65
- COMPRESSOR_HEAP_SIZE + rb_self.dict_len
245
+ rb::usize_value(COMPRESSOR_HEAP_SIZE + rb_self.dict_len)
66
246
  } else {
67
- 0
247
+ rb::usize_value(0)
68
248
  }
69
249
  }
70
250
 
71
- fn block_codec_has_dict(rb_self: &BlockCodec) -> bool {
72
- rb_self.compressor.is_some()
251
+ fn block_codec_has_dict_impl(rb_self: VALUE) -> RbResult<VALUE> {
252
+ let rb_self = unsafe { block_codec_ref(rb_self)? };
253
+ Ok(rb::bool_value(rb_self.compressor.is_some()))
73
254
  }
74
255
 
75
- fn block_codec_compress(
76
- ruby: &Ruby,
77
- rb_self: &BlockCodec,
78
- rb_input: RString,
79
- ) -> Result<RString, Error> {
80
- let input: &[u8] = unsafe { rb_input.as_slice() };
256
+ fn block_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
257
+ let rb_self = unsafe { block_codec_ref(rb_self)? };
258
+ let mut input = rb::input_bytes(rb_input)?;
259
+ let release_gvl = should_release_compress_gvl(input.len());
260
+ input.lock_for_without_gvl(release_gvl)?;
81
261
 
82
262
  let out = match &rb_self.compressor {
83
- None => block::compress(input),
84
- Some(comp) => comp.borrow_mut().compress(input),
263
+ None => rb::maybe_without_gvl(release_gvl, || Ok(block::compress(input.as_slice())))?,
264
+ Some(comp) => with_mutex(comp, release_gvl, "BlockCodec compressor", |comp| {
265
+ Ok(comp.compress(input.as_slice()))
266
+ })?,
85
267
  };
86
268
 
87
- Ok(ruby.str_from_slice(&out))
269
+ rb::new_binary_string(&out)
88
270
  }
89
271
 
90
- fn block_codec_decompress(
91
- ruby: &Ruby,
92
- rb_self: &BlockCodec,
93
- rb_input: RString,
94
- decompressed_size: usize,
95
- ) -> Result<RString, Error> {
96
- let compressed: &[u8] = unsafe { rb_input.as_slice() };
272
+ fn block_codec_decompress_impl(
273
+ rb_self: VALUE,
274
+ rb_input: VALUE,
275
+ decompressed_size: VALUE,
276
+ ) -> RbResult<VALUE> {
277
+ let rb_self = unsafe { block_codec_ref(rb_self)? };
278
+ let compressed = rb::input_bytes(rb_input)?;
279
+ let decompressed_size = rb::value_to_usize(decompressed_size)?;
97
280
 
98
281
  let result = match &rb_self.decompressor {
99
- None => block::decompress(compressed, decompressed_size),
100
- Some(decomp) => decomp.decompress(compressed, decompressed_size),
282
+ None => block::decompress(compressed.as_slice(), decompressed_size),
283
+ Some(decomp) => with_mutex(decomp, false, "BlockCodec decompressor", |decomp| {
284
+ Ok(decomp.decompress(compressed.as_slice(), decompressed_size))
285
+ })?,
101
286
  };
102
287
 
103
288
  match result {
104
- Ok(data) => Ok(ruby.str_from_slice(&data)),
105
- Err(e) => Err(Error::new(
106
- decompress_error(ruby),
289
+ Ok(data) => rb::new_binary_string(&data),
290
+ Err(e) => Err(RubyErr::new(
291
+ decompress_error(),
107
292
  format!("lz4 block decode failed: {e}"),
108
293
  )),
109
294
  }
110
295
  }
111
296
 
297
+ unsafe extern "C" fn block_codec_new(class: VALUE, rb_dict: VALUE) -> VALUE {
298
+ rb::wrap(|| block_codec_new_impl(class, rb_dict))
299
+ }
300
+
301
+ unsafe extern "C" fn block_codec_size(rb_self: VALUE) -> VALUE {
302
+ rb::wrap(|| block_codec_size_impl(rb_self))
303
+ }
304
+
305
+ unsafe extern "C" fn block_codec_has_dict(rb_self: VALUE) -> VALUE {
306
+ rb::wrap(|| block_codec_has_dict_impl(rb_self))
307
+ }
308
+
309
+ unsafe extern "C" fn block_codec_compress(rb_self: VALUE, rb_input: VALUE) -> VALUE {
310
+ rb::wrap(|| block_codec_compress_impl(rb_self, rb_input))
311
+ }
312
+
313
+ unsafe extern "C" fn block_codec_decompress(
314
+ rb_self: VALUE,
315
+ rb_input: VALUE,
316
+ decompressed_size: VALUE,
317
+ ) -> VALUE {
318
+ rb::wrap(|| block_codec_decompress_impl(rb_self, rb_input, decompressed_size))
319
+ }
320
+
112
321
  // ---------- FrameCodec ----------
113
322
 
114
- #[magnus::wrap(class = "Lz4rip::FrameCodec", free_immediately, size)]
115
323
  struct FrameCodec {
116
324
  dict: Option<DictBound>,
117
325
  }
@@ -121,214 +329,336 @@ struct DictBound {
121
329
  id: u32,
122
330
  }
123
331
 
124
- fn frame_codec_initialize(
125
- _ruby: &Ruby,
126
- rb_dict: Option<RString>,
127
- id: u32,
128
- ) -> Result<FrameCodec, Error> {
129
- let dict = rb_dict.map(|s| {
130
- let bytes: Vec<u8> = unsafe { s.as_slice().to_vec() };
131
- s.freeze();
132
- DictBound { bytes, id }
133
- });
134
- Ok(FrameCodec { dict })
332
+ fn frame_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE) -> RbResult<VALUE> {
333
+ let id = rb::value_to_u32(id)?;
334
+ let dict = if rb_dict == rb::qnil() {
335
+ None
336
+ } else {
337
+ let rb_dict = rb::string_value(rb_dict)?;
338
+ rb::freeze_value(rb_dict)?;
339
+ Some(DictBound {
340
+ bytes: rb::value_to_bytes(rb_dict)?,
341
+ id,
342
+ })
343
+ };
344
+ unsafe {
345
+ rb::wrap_typed_data(
346
+ class,
347
+ Box::new(FrameCodec { dict }),
348
+ frame_codec_data_type(),
349
+ )
350
+ }
135
351
  }
136
352
 
137
- fn frame_codec_compress(
138
- ruby: &Ruby,
139
- rb_self: &FrameCodec,
140
- rb_input: RString,
141
- ) -> Result<RString, Error> {
142
- let input: &[u8] = unsafe { rb_input.as_slice() };
353
+ fn frame_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
354
+ let rb_self = unsafe { frame_codec_ref(rb_self)? };
355
+ let mut input = rb::input_bytes(rb_input)?;
356
+ let release_gvl = should_release_compress_gvl(input.len());
357
+ input.lock_for_without_gvl(release_gvl)?;
358
+
359
+ let out = rb::maybe_without_gvl(release_gvl, || compress_frame(rb_self, input.as_slice()))
360
+ .map_err(RubyErr::runtime)?;
361
+
362
+ rb::new_binary_string(&out)
363
+ }
143
364
 
365
+ fn compress_frame(rb_self: &FrameCodec, input: &[u8]) -> Result<Vec<u8>, String> {
144
366
  let buf = Vec::new();
145
367
  let mut enc = match &rb_self.dict {
146
368
  None => {
147
369
  let info = FrameInfo::new().block_mode(BlockMode::Linked);
148
370
  FrameEncoder::with_frame_info(info, buf)
149
371
  }
150
- Some(d) => FrameEncoder::with_dictionary(buf, &d.bytes, d.id),
372
+ Some(d) => {
373
+ let info = FrameInfo::new().block_mode(BlockMode::Linked);
374
+ FrameEncoder::with_dictionary(buf, &d.bytes, d.id, Some(info))
375
+ .map_err(|e| format!("lz4 frame compress failed: {e}"))?
376
+ }
151
377
  };
152
378
 
153
- enc.write_all(input).map_err(|e| {
154
- Error::new(
155
- ruby.exception_runtime_error(),
156
- format!("lz4 frame compress failed: {e}"),
157
- )
158
- })?;
159
-
160
- let out = enc.finish().map_err(|e| {
161
- Error::new(
162
- ruby.exception_runtime_error(),
163
- format!("lz4 frame compress failed: {e}"),
164
- )
165
- })?;
379
+ enc.write_all(input)
380
+ .map_err(|e| format!("lz4 frame compress failed: {e}"))?;
166
381
 
167
- Ok(ruby.str_from_slice(&out))
382
+ enc.finish()
383
+ .map_err(|e| format!("lz4 frame compress failed: {e}"))
168
384
  }
169
385
 
170
- fn frame_codec_decompress(
171
- ruby: &Ruby,
172
- rb_self: &FrameCodec,
173
- rb_input: RString,
174
- ) -> Result<RString, Error> {
175
- let input: &[u8] = unsafe { rb_input.as_slice() };
176
-
177
- if input.len() < 4 || input[..4] != LZ4_FRAME_MAGIC {
178
- return Err(Error::new(
179
- decompress_error(ruby),
386
+ fn frame_codec_decompress_impl(
387
+ rb_self: VALUE,
388
+ rb_input: VALUE,
389
+ max_decompressed_size: VALUE,
390
+ ) -> RbResult<VALUE> {
391
+ let rb_self = unsafe { frame_codec_ref(rb_self)? };
392
+ let mut input = rb::input_bytes(rb_input)?;
393
+ let max_decompressed_size = rb::value_to_option_usize(max_decompressed_size)?;
394
+ let release_gvl = should_release_frame_decompress_gvl(input.len());
395
+ input.lock_for_without_gvl(release_gvl)?;
396
+
397
+ if input.len() < 4 || input.as_slice()[..4] != LZ4_FRAME_MAGIC {
398
+ return Err(RubyErr::new(
399
+ decompress_error(),
180
400
  "lz4 frame decode failed: bad magic (input is not an LZ4 frame)",
181
401
  ));
182
402
  }
183
403
 
184
- let mut dec = match &rb_self.dict {
185
- None => FrameDecoder::new(Cursor::new(input)),
186
- Some(d) => FrameDecoder::with_dictionary(Cursor::new(input), &d.bytes, d.id),
187
- };
404
+ let out = rb::maybe_without_gvl(release_gvl, || {
405
+ decompress_frame(rb_self, input.as_slice(), max_decompressed_size)
406
+ })
407
+ .map_err(|e| RubyErr::new(decompress_error(), e))?;
408
+
409
+ rb::new_binary_string(&out)
410
+ }
411
+
412
+ fn decompress_frame(
413
+ rb_self: &FrameCodec,
414
+ input: &[u8],
415
+ max_decompressed_size: Option<usize>,
416
+ ) -> Result<Vec<u8>, String> {
417
+ let mut dec = FrameDecoder::with_options(
418
+ Cursor::new(input),
419
+ FrameDecoderOptions {
420
+ dictionary: rb_self.dict.as_ref().map(|d| (d.bytes.as_slice(), d.id)),
421
+ max_output: max_decompressed_size,
422
+ },
423
+ );
188
424
 
189
425
  let mut out = Vec::new();
190
- dec.read_to_end(&mut out).map_err(|e| {
191
- Error::new(
192
- decompress_error(ruby),
193
- format!("lz4 frame decode failed: {e}"),
194
- )
195
- })?;
426
+ dec.read_to_end(&mut out)
427
+ .map_err(|e| format!("lz4 frame decode failed: {e}"))?;
428
+ Ok(out)
429
+ }
196
430
 
197
- Ok(ruby.str_from_slice(&out))
431
+ fn frame_codec_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
432
+ let rb_self = unsafe { frame_codec_ref(rb_self)? };
433
+ rb::usize_value(rb_self.dict.as_ref().map_or(0, |d| d.bytes.len()))
198
434
  }
199
435
 
200
- fn frame_codec_size(rb_self: &FrameCodec) -> usize {
201
- rb_self.dict.as_ref().map_or(0, |d| d.bytes.len())
436
+ fn frame_codec_has_dict_impl(rb_self: VALUE) -> RbResult<VALUE> {
437
+ let rb_self = unsafe { frame_codec_ref(rb_self)? };
438
+ Ok(rb::bool_value(rb_self.dict.is_some()))
202
439
  }
203
440
 
204
- fn frame_codec_has_dict(rb_self: &FrameCodec) -> bool {
205
- rb_self.dict.is_some()
441
+ fn frame_codec_id_impl(rb_self: VALUE) -> RbResult<VALUE> {
442
+ let rb_self = unsafe { frame_codec_ref(rb_self)? };
443
+ rb::u32_option_value(rb_self.dict.as_ref().map(|d| d.id))
206
444
  }
207
445
 
208
- fn frame_codec_id(rb_self: &FrameCodec) -> Option<u32> {
209
- rb_self.dict.as_ref().map(|d| d.id)
446
+ unsafe extern "C" fn frame_codec_new(class: VALUE, rb_dict: VALUE, id: VALUE) -> VALUE {
447
+ rb::wrap(|| frame_codec_new_impl(class, rb_dict, id))
448
+ }
449
+
450
+ unsafe extern "C" fn frame_codec_compress(rb_self: VALUE, rb_input: VALUE) -> VALUE {
451
+ rb::wrap(|| frame_codec_compress_impl(rb_self, rb_input))
452
+ }
453
+
454
+ unsafe extern "C" fn frame_codec_decompress(
455
+ rb_self: VALUE,
456
+ rb_input: VALUE,
457
+ max_decompressed_size: VALUE,
458
+ ) -> VALUE {
459
+ rb::wrap(|| frame_codec_decompress_impl(rb_self, rb_input, max_decompressed_size))
460
+ }
461
+
462
+ unsafe extern "C" fn frame_codec_size(rb_self: VALUE) -> VALUE {
463
+ rb::wrap(|| frame_codec_size_impl(rb_self))
464
+ }
465
+
466
+ unsafe extern "C" fn frame_codec_has_dict(rb_self: VALUE) -> VALUE {
467
+ rb::wrap(|| frame_codec_has_dict_impl(rb_self))
468
+ }
469
+
470
+ unsafe extern "C" fn frame_codec_id(rb_self: VALUE) -> VALUE {
471
+ rb::wrap(|| frame_codec_id_impl(rb_self))
210
472
  }
211
473
 
212
474
  // ---------- DictTrainer ----------
213
475
 
214
476
  const LZ4_MAX_DISTANCE: usize = 65535;
215
477
 
216
- #[magnus::wrap(class = "Lz4rip::DictTrainer", free_immediately, size)]
217
478
  struct RbDictTrainer {
218
- inner: RefCell<Option<DictTrainer>>,
479
+ inner: Mutex<Option<DictTrainer>>,
219
480
  max_dict_size: usize,
220
481
  }
221
482
 
222
- fn dict_trainer_new(_ruby: &Ruby, max_dict_size: usize) -> RbDictTrainer {
483
+ fn dict_trainer_new_impl(class: VALUE, max_dict_size: VALUE) -> RbResult<VALUE> {
484
+ let max_dict_size = rb::value_to_usize(max_dict_size)?;
223
485
  let capped = max_dict_size.min(LZ4_MAX_DISTANCE);
224
- RbDictTrainer {
225
- max_dict_size: capped,
226
- inner: RefCell::new(Some(DictTrainer::new(max_dict_size))),
486
+ unsafe {
487
+ rb::wrap_typed_data(
488
+ class,
489
+ Box::new(RbDictTrainer {
490
+ max_dict_size: capped,
491
+ inner: Mutex::new(Some(DictTrainer::new(max_dict_size))),
492
+ }),
493
+ dict_trainer_data_type(),
494
+ )
227
495
  }
228
496
  }
229
497
 
230
- fn dict_trainer_add_sample(
231
- ruby: &Ruby,
232
- rb_self: &RbDictTrainer,
233
- rb_data: RString,
234
- ) -> Result<(), Error> {
235
- let mut borrow = rb_self.inner.borrow_mut();
236
- let trainer = borrow.as_mut().ok_or_else(|| {
237
- Error::new(
238
- ruby.exception_runtime_error(),
239
- "DictTrainer already consumed by #train",
240
- )
241
- })?;
242
- let data: &[u8] = unsafe { rb_data.as_slice() };
498
+ fn dict_trainer_add_sample_impl(rb_self: VALUE, rb_data: VALUE) -> RbResult<VALUE> {
499
+ let rb_self = unsafe { dict_trainer_ref(rb_self)? };
500
+ let mut borrow = rb_self
501
+ .inner
502
+ .lock()
503
+ .map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
504
+ let trainer = borrow
505
+ .as_mut()
506
+ .ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
507
+ let data = rb::input_bytes(rb_data)?;
243
508
  let sample = if data.len() > rb_self.max_dict_size {
244
- &data[..rb_self.max_dict_size]
509
+ &data.as_slice()[..rb_self.max_dict_size]
245
510
  } else {
246
- data
511
+ data.as_slice()
247
512
  };
248
513
  trainer.add_sample(sample);
249
- Ok(())
514
+ Ok(rb::qnil())
250
515
  }
251
516
 
252
- fn dict_trainer_sample_count(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<usize, Error> {
253
- let borrow = rb_self.inner.borrow();
254
- borrow.as_ref().map(|t| t.sample_count()).ok_or_else(|| {
255
- Error::new(
256
- ruby.exception_runtime_error(),
257
- "DictTrainer already consumed by #train",
258
- )
259
- })
517
+ fn dict_trainer_sample_count_impl(rb_self: VALUE) -> RbResult<VALUE> {
518
+ let rb_self = unsafe { dict_trainer_ref(rb_self)? };
519
+ let borrow = rb_self
520
+ .inner
521
+ .lock()
522
+ .map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
523
+ let value = borrow
524
+ .as_ref()
525
+ .map(|t| t.sample_count())
526
+ .ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
527
+ rb::usize_value(value)
260
528
  }
261
529
 
262
- fn dict_trainer_total_bytes(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<usize, Error> {
263
- let borrow = rb_self.inner.borrow();
264
- borrow.as_ref().map(|t| t.total_bytes()).ok_or_else(|| {
265
- Error::new(
266
- ruby.exception_runtime_error(),
267
- "DictTrainer already consumed by #train",
268
- )
269
- })
530
+ fn dict_trainer_total_bytes_impl(rb_self: VALUE) -> RbResult<VALUE> {
531
+ let rb_self = unsafe { dict_trainer_ref(rb_self)? };
532
+ let borrow = rb_self
533
+ .inner
534
+ .lock()
535
+ .map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
536
+ let value = borrow
537
+ .as_ref()
538
+ .map(|t| t.total_bytes())
539
+ .ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
540
+ rb::usize_value(value)
270
541
  }
271
542
 
272
- fn dict_trainer_train(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<RString, Error> {
273
- let trainer = rb_self.inner.borrow_mut().take().ok_or_else(|| {
274
- Error::new(
275
- ruby.exception_runtime_error(),
276
- "DictTrainer already consumed by #train",
277
- )
278
- })?;
543
+ fn dict_trainer_train_impl(rb_self: VALUE) -> RbResult<VALUE> {
544
+ let rb_self = unsafe { dict_trainer_ref(rb_self)? };
545
+ let trainer = rb_self
546
+ .inner
547
+ .lock()
548
+ .map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?
549
+ .take()
550
+ .ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
279
551
  let dict = trainer.train();
280
- Ok(ruby.str_from_slice(&dict))
552
+ rb::new_binary_string(&dict)
281
553
  }
282
554
 
283
- fn dict_trainer_max_dict_size(rb_self: &RbDictTrainer) -> usize {
284
- rb_self.max_dict_size
555
+ fn dict_trainer_max_dict_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
556
+ let rb_self = unsafe { dict_trainer_ref(rb_self)? };
557
+ rb::usize_value(rb_self.max_dict_size)
285
558
  }
286
559
 
287
- fn dict_trainer_trained(rb_self: &RbDictTrainer) -> bool {
288
- rb_self.inner.borrow().is_none()
560
+ fn dict_trainer_trained_impl(rb_self: VALUE) -> RbResult<VALUE> {
561
+ let rb_self = unsafe { dict_trainer_ref(rb_self)? };
562
+ let borrow = rb_self
563
+ .inner
564
+ .lock()
565
+ .map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
566
+ Ok(rb::bool_value(borrow.is_none()))
567
+ }
568
+
569
+ unsafe extern "C" fn dict_trainer_new(class: VALUE, max_dict_size: VALUE) -> VALUE {
570
+ rb::wrap(|| dict_trainer_new_impl(class, max_dict_size))
571
+ }
572
+
573
+ unsafe extern "C" fn dict_trainer_add_sample(rb_self: VALUE, rb_data: VALUE) -> VALUE {
574
+ rb::wrap(|| dict_trainer_add_sample_impl(rb_self, rb_data))
575
+ }
576
+
577
+ unsafe extern "C" fn dict_trainer_sample_count(rb_self: VALUE) -> VALUE {
578
+ rb::wrap(|| dict_trainer_sample_count_impl(rb_self))
579
+ }
580
+
581
+ unsafe extern "C" fn dict_trainer_total_bytes(rb_self: VALUE) -> VALUE {
582
+ rb::wrap(|| dict_trainer_total_bytes_impl(rb_self))
583
+ }
584
+
585
+ unsafe extern "C" fn dict_trainer_train(rb_self: VALUE) -> VALUE {
586
+ rb::wrap(|| dict_trainer_train_impl(rb_self))
587
+ }
588
+
589
+ unsafe extern "C" fn dict_trainer_max_dict_size(rb_self: VALUE) -> VALUE {
590
+ rb::wrap(|| dict_trainer_max_dict_size_impl(rb_self))
591
+ }
592
+
593
+ unsafe extern "C" fn dict_trainer_trained(rb_self: VALUE) -> VALUE {
594
+ rb::wrap(|| dict_trainer_trained_impl(rb_self))
289
595
  }
290
596
 
291
597
  // ---------- module init ----------
292
598
 
293
- #[magnus::init]
294
- fn init(ruby: &Ruby) -> Result<(), Error> {
295
- unsafe { rb_sys::rb_ext_ractor_safe(true) };
599
+ /// # Safety
600
+ ///
601
+ /// Ruby calls this function while loading the native extension. The Ruby VM
602
+ /// must be initialized, and the symbol must only be entered by Ruby's extension
603
+ /// loader.
604
+ #[no_mangle]
605
+ pub unsafe extern "C" fn Init_lz4rip() {
606
+ rb::wrap_init(init);
607
+ }
296
608
 
297
- let module = ruby.define_module("Lz4rip")?;
609
+ fn init() -> RbResult<()> {
610
+ #[cfg(ruby_engine = "mri")]
611
+ unsafe {
612
+ rb_sys::rb_ext_ractor_safe(true);
613
+ }
614
+
615
+ let module = unsafe { rb::define_module(c"Lz4rip")? };
298
616
 
299
617
  let decompress_error_class =
300
- module.define_error("DecompressError", ruby.exception_standard_error())?;
618
+ unsafe { rb::define_error_under(module, c"DecompressError", rb_sys::rb_eStandardError)? };
301
619
  DECOMPRESS_ERROR
302
- .set(Opaque::from(decompress_error_class))
620
+ .set(GlobalValue(decompress_error_class))
303
621
  .unwrap_or_else(|_| panic!("init called more than once"));
304
622
 
305
- module.define_module_function("compress_bound", function!(lz4rip_compress_bound, 1))?;
306
- module.define_module_function("block_stream_size", function!(lz4rip_block_stream_size, 0))?;
307
-
308
- let codec_class = module.define_class("BlockCodec", ruby.class_object())?;
309
- codec_class.define_singleton_method("_native_new", function!(block_codec_new, 1))?;
310
- codec_class.define_method("size", method!(block_codec_size, 0))?;
311
- codec_class.define_method("has_dict?", method!(block_codec_has_dict, 0))?;
312
- codec_class.define_method("compress", method!(block_codec_compress, 1))?;
313
- codec_class.define_method("_decompress", method!(block_codec_decompress, 2))?;
314
-
315
- let trainer_class = module.define_class("DictTrainer", ruby.class_object())?;
316
- trainer_class.define_singleton_method("_native_new", function!(dict_trainer_new, 1))?;
317
- trainer_class.define_method("add_sample", method!(dict_trainer_add_sample, 1))?;
318
- trainer_class.define_method("sample_count", method!(dict_trainer_sample_count, 0))?;
319
- trainer_class.define_method("total_bytes", method!(dict_trainer_total_bytes, 0))?;
320
- trainer_class.define_method("train", method!(dict_trainer_train, 0))?;
321
- trainer_class.define_method("max_dict_size", method!(dict_trainer_max_dict_size, 0))?;
322
- trainer_class.define_method("trained?", method!(dict_trainer_trained, 0))?;
323
-
324
- let frame_codec_class = module.define_class("FrameCodec", ruby.class_object())?;
325
- frame_codec_class
326
- .define_singleton_method("_native_new", function!(frame_codec_initialize, 2))?;
327
- frame_codec_class.define_method("compress", method!(frame_codec_compress, 1))?;
328
- frame_codec_class.define_method("decompress", method!(frame_codec_decompress, 1))?;
329
- frame_codec_class.define_method("size", method!(frame_codec_size, 0))?;
330
- frame_codec_class.define_method("has_dict?", method!(frame_codec_has_dict, 0))?;
331
- frame_codec_class.define_method("id", method!(frame_codec_id, 0))?;
623
+ unsafe {
624
+ rb::define_module_function_1(module, c"compress_bound", lz4rip_compress_bound)?;
625
+ rb::define_module_function_0(module, c"block_stream_size", lz4rip_block_stream_size)?;
626
+ }
627
+
628
+ let codec_class = unsafe { rb::define_class_under(module, c"BlockCodec", rb_sys::rb_cObject)? };
629
+ unsafe {
630
+ rb::undef_alloc_func(codec_class)?;
631
+ rb::define_singleton_method_1(codec_class, c"_native_new", block_codec_new)?;
632
+ rb::define_method_0(codec_class, c"size", block_codec_size)?;
633
+ rb::define_method_0(codec_class, c"has_dict?", block_codec_has_dict)?;
634
+ rb::define_method_1(codec_class, c"compress", block_codec_compress)?;
635
+ rb::define_method_2(codec_class, c"_decompress", block_codec_decompress)?;
636
+ }
637
+
638
+ let trainer_class =
639
+ unsafe { rb::define_class_under(module, c"DictTrainer", rb_sys::rb_cObject)? };
640
+ unsafe {
641
+ rb::undef_alloc_func(trainer_class)?;
642
+ rb::define_singleton_method_1(trainer_class, c"_native_new", dict_trainer_new)?;
643
+ rb::define_method_1(trainer_class, c"add_sample", dict_trainer_add_sample)?;
644
+ rb::define_method_0(trainer_class, c"sample_count", dict_trainer_sample_count)?;
645
+ rb::define_method_0(trainer_class, c"total_bytes", dict_trainer_total_bytes)?;
646
+ rb::define_method_0(trainer_class, c"train", dict_trainer_train)?;
647
+ rb::define_method_0(trainer_class, c"max_dict_size", dict_trainer_max_dict_size)?;
648
+ rb::define_method_0(trainer_class, c"trained?", dict_trainer_trained)?;
649
+ }
650
+
651
+ let frame_codec_class =
652
+ unsafe { rb::define_class_under(module, c"FrameCodec", rb_sys::rb_cObject)? };
653
+ unsafe {
654
+ rb::undef_alloc_func(frame_codec_class)?;
655
+ rb::define_singleton_method_2(frame_codec_class, c"_native_new", frame_codec_new)?;
656
+ rb::define_method_1(frame_codec_class, c"compress", frame_codec_compress)?;
657
+ rb::define_method_2(frame_codec_class, c"_decompress", frame_codec_decompress)?;
658
+ rb::define_method_0(frame_codec_class, c"size", frame_codec_size)?;
659
+ rb::define_method_0(frame_codec_class, c"has_dict?", frame_codec_has_dict)?;
660
+ rb::define_method_0(frame_codec_class, c"id", frame_codec_id)?;
661
+ }
332
662
 
333
663
  Ok(())
334
664
  }
@@ -350,7 +680,7 @@ mod tests {
350
680
  let dict = b"common log prefix: ".to_vec();
351
681
  let msg = b"common log prefix: event=login user=alice".to_vec();
352
682
 
353
- let mut comp = Compressor::with_dict(&dict);
683
+ let mut comp = DictCompressor::new(&dict);
354
684
  let ct_dict = comp.compress(&msg);
355
685
  let decomp = Decompressor::with_dict(&dict);
356
686
  let pt = decomp.decompress(&ct_dict, msg.len()).unwrap();