vibe_zstd 1.1.1 → 1.3.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/CHANGELOG.md +36 -0
- data/README.md +79 -3
- data/ext/vibe_zstd/cctx.c +71 -25
- data/ext/vibe_zstd/dctx.c +260 -32
- data/ext/vibe_zstd/depend +3 -0
- data/ext/vibe_zstd/dict.c +51 -19
- data/ext/vibe_zstd/extconf.rb +2 -0
- data/ext/vibe_zstd/frames.c +13 -2
- data/ext/vibe_zstd/streaming.c +99 -13
- data/ext/vibe_zstd/vibe_zstd.c +30 -0
- data/ext/vibe_zstd/vibe_zstd.h +1 -0
- data/lib/vibe_zstd/version.rb +1 -1
- data/lib/vibe_zstd.rb +48 -23
- metadata +3 -2
data/ext/vibe_zstd/streaming.c
CHANGED
|
@@ -14,6 +14,12 @@ static VALUE vibe_zstd_reader_initialize(int argc, VALUE *argv, VALUE self);
|
|
|
14
14
|
static VALUE vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self);
|
|
15
15
|
static VALUE vibe_zstd_reader_eof(VALUE self);
|
|
16
16
|
|
|
17
|
+
// State struct for rb_ensure-based string lock/unlock in vibe_zstd_writer_write
|
|
18
|
+
typedef struct {
|
|
19
|
+
vibe_zstd_cstream* cstream;
|
|
20
|
+
VALUE data;
|
|
21
|
+
} vibe_zstd_write_state;
|
|
22
|
+
|
|
17
23
|
// TypedData types - defined in vibe_zstd.c
|
|
18
24
|
extern rb_data_type_t vibe_zstd_cstream_type;
|
|
19
25
|
extern rb_data_type_t vibe_zstd_dstream_type;
|
|
@@ -89,6 +95,9 @@ vibe_zstd_writer_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
89
95
|
if (ZSTD_isError(result)) {
|
|
90
96
|
rb_raise(rb_eRuntimeError, "Failed to set dictionary: %s", ZSTD_getErrorName(result));
|
|
91
97
|
}
|
|
98
|
+
// Retain the CDict object so GC won't free it while the stream holds a raw
|
|
99
|
+
// pointer to its internal ZSTD_CDict (ZSTD_CCtx_refCDict stores no Ruby ref)
|
|
100
|
+
rb_ivar_set(self, rb_intern("@dict"), dict);
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
// Allocate reusable output buffer (write barrier for WB_PROTECTED)
|
|
@@ -97,14 +106,16 @@ vibe_zstd_writer_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
97
106
|
return self;
|
|
98
107
|
}
|
|
99
108
|
|
|
109
|
+
// Body of the rb_ensure wrapper: runs the compress loop with data locked
|
|
100
110
|
static VALUE
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
111
|
+
vibe_zstd_writer_write_body(VALUE arg) {
|
|
112
|
+
vibe_zstd_write_state* state = (vibe_zstd_write_state*)arg;
|
|
113
|
+
vibe_zstd_cstream* cstream = state->cstream;
|
|
114
|
+
VALUE data = state->data;
|
|
115
|
+
|
|
116
|
+
// Input buffer: pos advances as ZSTD consumes data.
|
|
117
|
+
// data is locked (rb_str_locktmp) for the duration of this call so that
|
|
118
|
+
// RSTRING_PTR remains valid even when rb_funcall runs arbitrary Ruby code.
|
|
108
119
|
ZSTD_inBuffer input = {
|
|
109
120
|
.src = RSTRING_PTR(data),
|
|
110
121
|
.size = RSTRING_LEN(data),
|
|
@@ -137,10 +148,39 @@ vibe_zstd_writer_write(VALUE self, VALUE data) {
|
|
|
137
148
|
// Write any compressed output that was produced
|
|
138
149
|
if (output.pos > 0) {
|
|
139
150
|
rb_str_set_len(outBuffer, output.pos);
|
|
151
|
+
// rb_funcall may run arbitrary Ruby code, but input.src stays valid
|
|
152
|
+
// because data is locked against mutation/reallocation
|
|
140
153
|
rb_funcall(cstream->io, id_write, 1, outBuffer);
|
|
141
154
|
}
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
return Qnil;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Ensure function: always unlocks data regardless of raise/return
|
|
161
|
+
static VALUE
|
|
162
|
+
vibe_zstd_writer_write_unlock(VALUE arg) {
|
|
163
|
+
rb_str_unlocktmp((VALUE)arg);
|
|
164
|
+
return Qnil;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static VALUE
|
|
168
|
+
vibe_zstd_writer_write(VALUE self, VALUE data) {
|
|
169
|
+
Check_Type(data, T_STRING);
|
|
170
|
+
|
|
171
|
+
vibe_zstd_cstream* cstream;
|
|
172
|
+
TypedData_Get_Struct(self, vibe_zstd_cstream, &vibe_zstd_cstream_type, cstream);
|
|
173
|
+
|
|
174
|
+
// Lock data for the duration of the compress loop so that RSTRING_PTR(data)
|
|
175
|
+
// stays valid even when io.write (called inside the loop) runs Ruby code that
|
|
176
|
+
// could otherwise mutate or resize the string. rb_str_locktmp raises if the
|
|
177
|
+
// string is already locked; the ensure always unlocks it.
|
|
178
|
+
rb_str_locktmp(data);
|
|
179
|
+
|
|
180
|
+
vibe_zstd_write_state state = { cstream, data };
|
|
181
|
+
rb_ensure(vibe_zstd_writer_write_body, (VALUE)&state,
|
|
182
|
+
vibe_zstd_writer_write_unlock, data);
|
|
183
|
+
|
|
144
184
|
return self;
|
|
145
185
|
}
|
|
146
186
|
|
|
@@ -275,6 +315,9 @@ vibe_zstd_reader_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
275
315
|
if (ZSTD_isError(result)) {
|
|
276
316
|
rb_raise(rb_eRuntimeError, "Failed to set dictionary: %s", ZSTD_getErrorName(result));
|
|
277
317
|
}
|
|
318
|
+
// Retain the DDict object so GC won't free it while the stream holds a raw
|
|
319
|
+
// pointer to its internal ZSTD_DDict (ZSTD_DCtx_refDDict stores no Ruby ref)
|
|
320
|
+
rb_ivar_set(self, rb_intern("@dict"), dict);
|
|
278
321
|
}
|
|
279
322
|
|
|
280
323
|
// Initialize input buffer management
|
|
@@ -298,10 +341,18 @@ vibe_zstd_reader_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
298
341
|
// - Maintains internal compressed input buffer that refills from IO as needed
|
|
299
342
|
// - Calls ZSTD_decompressStream incrementally to produce output
|
|
300
343
|
// - Tracks EOF state based on IO exhaustion and frame completion
|
|
344
|
+
// - Input chunks are stored as frozen copies so that IOs which mutate/reuse
|
|
345
|
+
// the returned string cannot invalidate dstream->input.src between calls
|
|
301
346
|
//
|
|
302
347
|
// EOF handling:
|
|
303
348
|
// - Returns nil when no more data available
|
|
304
349
|
// - Sets eof flag when: IO returns nil, frame complete (ret==0), or no progress made
|
|
350
|
+
// - read(0) always returns "" immediately without touching stream state
|
|
351
|
+
//
|
|
352
|
+
// Allocation strategy:
|
|
353
|
+
// - Initial buffer is capped at ZSTD_DStreamOutSize() to avoid gigabyte
|
|
354
|
+
// allocations for large size arguments on small streams
|
|
355
|
+
// - Buffer grows geometrically (doubling) up to requested_size as needed
|
|
305
356
|
//
|
|
306
357
|
// This implements proper streaming semantics for incremental decompression
|
|
307
358
|
// of arbitrarily large files without loading everything into memory.
|
|
@@ -313,6 +364,11 @@ vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self) {
|
|
|
313
364
|
vibe_zstd_dstream* dstream;
|
|
314
365
|
TypedData_Get_Struct(self, vibe_zstd_dstream, &vibe_zstd_dstream_type, dstream);
|
|
315
366
|
|
|
367
|
+
// read(0): per IO semantics, always return "" without touching stream state
|
|
368
|
+
if (!NIL_P(size_arg) && NUM2SIZET(size_arg) == 0) {
|
|
369
|
+
return rb_str_new(NULL, 0);
|
|
370
|
+
}
|
|
371
|
+
|
|
316
372
|
if (dstream->eof) {
|
|
317
373
|
return Qnil;
|
|
318
374
|
}
|
|
@@ -323,8 +379,12 @@ vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self) {
|
|
|
323
379
|
size_t requested_size = NIL_P(size_arg) ? default_chunk_size : NUM2SIZET(size_arg);
|
|
324
380
|
size_t inBufferSize = ZSTD_DStreamInSize();
|
|
325
381
|
|
|
326
|
-
//
|
|
327
|
-
|
|
382
|
+
// Cap the initial allocation to avoid multi-gigabyte pre-allocations when
|
|
383
|
+
// the caller passes a huge size argument for a small stream. The buffer
|
|
384
|
+
// grows geometrically below as output accumulates.
|
|
385
|
+
size_t default_out_size = ZSTD_DStreamOutSize();
|
|
386
|
+
size_t initial_alloc = (requested_size < default_out_size) ? requested_size : default_out_size;
|
|
387
|
+
VALUE result = rb_str_buf_new((long)initial_alloc);
|
|
328
388
|
|
|
329
389
|
size_t total_read = 0;
|
|
330
390
|
int made_progress = 0;
|
|
@@ -341,10 +401,21 @@ vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self) {
|
|
|
341
401
|
break;
|
|
342
402
|
}
|
|
343
403
|
|
|
404
|
+
// The IO is duck-typed: read may return anything. Convert via to_str
|
|
405
|
+
// (raising TypeError otherwise) so RSTRING below never sees a non-String.
|
|
406
|
+
StringValue(chunk);
|
|
407
|
+
|
|
408
|
+
// Store a private frozen copy so that an IO that reuses/mutates its
|
|
409
|
+
// returned buffer string cannot invalidate dstream->input.src between
|
|
410
|
+
// successive read() calls. rb_str_new_frozen is cheap (copy-on-write
|
|
411
|
+
// snapshot) when the string is already frozen, and allocates a
|
|
412
|
+
// separate copy otherwise.
|
|
413
|
+
VALUE frozen_chunk = rb_str_new_frozen(chunk);
|
|
414
|
+
|
|
344
415
|
// Reset input buffer with new data (write barrier for WB_PROTECTED)
|
|
345
|
-
RB_OBJ_WRITE(self, &dstream->input_data,
|
|
346
|
-
dstream->input.src = RSTRING_PTR(
|
|
347
|
-
dstream->input.size = RSTRING_LEN(
|
|
416
|
+
RB_OBJ_WRITE(self, &dstream->input_data, frozen_chunk);
|
|
417
|
+
dstream->input.src = RSTRING_PTR(frozen_chunk);
|
|
418
|
+
dstream->input.size = RSTRING_LEN(frozen_chunk);
|
|
348
419
|
dstream->input.pos = 0;
|
|
349
420
|
}
|
|
350
421
|
|
|
@@ -353,7 +424,22 @@ vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self) {
|
|
|
353
424
|
break;
|
|
354
425
|
}
|
|
355
426
|
|
|
356
|
-
|
|
427
|
+
// Grow the output buffer geometrically when it is full, capped at
|
|
428
|
+
// requested_size. We must recompute RSTRING_PTR after any resize
|
|
429
|
+
// because the backing allocation may move.
|
|
430
|
+
size_t current_capacity = (size_t)rb_str_capacity(result);
|
|
431
|
+
if (total_read >= current_capacity) {
|
|
432
|
+
size_t new_capacity = current_capacity * 2;
|
|
433
|
+
if (new_capacity > requested_size) new_capacity = requested_size;
|
|
434
|
+
rb_str_resize(result, (long)new_capacity);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Cap space_left at (requested_size - total_read) to ensure read(n) never
|
|
438
|
+
// returns more than n bytes: rb_str_capacity may exceed the requested size
|
|
439
|
+
// due to malloc's internal size-class rounding (e.g. request 100, get 135).
|
|
440
|
+
size_t effective_capacity = (size_t)rb_str_capacity(result);
|
|
441
|
+
if (effective_capacity > requested_size) effective_capacity = requested_size;
|
|
442
|
+
size_t space_left = effective_capacity - total_read;
|
|
357
443
|
|
|
358
444
|
ZSTD_outBuffer output = {
|
|
359
445
|
.dst = RSTRING_PTR(result) + total_read,
|
data/ext/vibe_zstd/vibe_zstd.c
CHANGED
|
@@ -210,6 +210,7 @@ vibe_zstd_dctx_alloc(VALUE klass) {
|
|
|
210
210
|
rb_raise(rb_eRuntimeError, "Failed to create ZSTD_DCtx");
|
|
211
211
|
}
|
|
212
212
|
dctx->initial_capacity = 0; // 0 = use class default
|
|
213
|
+
dctx->max_decompressed_size = 0; // 0 = inherit class default
|
|
213
214
|
return TypedData_Wrap_Struct(klass, &vibe_zstd_dctx_type, dctx);
|
|
214
215
|
}
|
|
215
216
|
|
|
@@ -279,6 +280,35 @@ vibe_zstd_default_c_level(VALUE self) {
|
|
|
279
280
|
return INT2NUM(ZSTD_defaultCLevel());
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
// Run func(arg) without the GVL while str is locked against mutation.
|
|
284
|
+
// rb_thread_call_without_gvl can deliver a pending async exception (e.g.
|
|
285
|
+
// Thread#raise, Timeout) after reacquiring the GVL, so the unlock must go
|
|
286
|
+
// through rb_ensure or the string would be left permanently locked.
|
|
287
|
+
typedef struct {
|
|
288
|
+
void* (*func)(void*);
|
|
289
|
+
void* arg;
|
|
290
|
+
} nogvl_locked_call;
|
|
291
|
+
|
|
292
|
+
static VALUE
|
|
293
|
+
nogvl_locked_body(VALUE p) {
|
|
294
|
+
nogvl_locked_call* call = (nogvl_locked_call*)p;
|
|
295
|
+
rb_thread_call_without_gvl(call->func, call->arg, NULL, NULL);
|
|
296
|
+
return Qnil;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static VALUE
|
|
300
|
+
nogvl_locked_unlock(VALUE str) {
|
|
301
|
+
rb_str_unlocktmp(str);
|
|
302
|
+
return Qnil;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
static void
|
|
306
|
+
vibe_zstd_nogvl_with_str_locked(void* (*func)(void*), void* arg, VALUE str) {
|
|
307
|
+
nogvl_locked_call call = { func, arg };
|
|
308
|
+
rb_str_locktmp(str);
|
|
309
|
+
rb_ensure(nogvl_locked_body, (VALUE)&call, nogvl_locked_unlock, str);
|
|
310
|
+
}
|
|
311
|
+
|
|
282
312
|
// Include the split implementation files
|
|
283
313
|
#include "cctx.c"
|
|
284
314
|
#include "dctx.c"
|
data/ext/vibe_zstd/vibe_zstd.h
CHANGED
|
@@ -13,6 +13,7 @@ typedef struct {
|
|
|
13
13
|
typedef struct {
|
|
14
14
|
ZSTD_DCtx* dctx;
|
|
15
15
|
size_t initial_capacity; // Initial capacity for unknown-size decompression (0 = use class default)
|
|
16
|
+
size_t max_decompressed_size; // Output size limit (0 = inherit class default; class default 0 = unlimited)
|
|
16
17
|
} vibe_zstd_dctx;
|
|
17
18
|
|
|
18
19
|
typedef struct {
|
data/lib/vibe_zstd/version.rb
CHANGED
data/lib/vibe_zstd.rb
CHANGED
|
@@ -7,18 +7,33 @@ require_relative "vibe_zstd/constants"
|
|
|
7
7
|
module VibeZstd
|
|
8
8
|
class Error < StandardError; end
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# Keyword options handled per-operation by CCtx#compress / DCtx#decompress.
|
|
11
|
+
# Any other keyword is treated as a context (sticky) parameter and applied via
|
|
12
|
+
# the constructor, so e.g. VibeZstd.compress(data, checksum_flag: true) and
|
|
13
|
+
# VibeZstd.decompress(data, format: 1) work through the convenience methods.
|
|
14
|
+
# An unknown keyword raises NoMethodError from the corresponding setter.
|
|
15
|
+
COMPRESS_CALL_OPTIONS = %i[level dict pledged_size].freeze
|
|
16
|
+
DECOMPRESS_CALL_OPTIONS = %i[dict initial_capacity max_decompressed_size max_size].freeze
|
|
17
|
+
private_constant :COMPRESS_CALL_OPTIONS, :DECOMPRESS_CALL_OPTIONS
|
|
18
|
+
|
|
19
|
+
# Convenience method for one-off compression.
|
|
20
|
+
# Per-call options (level, dict, pledged_size) are passed to #compress; any
|
|
21
|
+
# other keyword is a context parameter (e.g. checksum_flag:, window_log:,
|
|
22
|
+
# workers:, format:) applied to a fresh CCtx.
|
|
12
23
|
def self.compress(data, **options)
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
call_opts = options.slice(*COMPRESS_CALL_OPTIONS)
|
|
25
|
+
ctx_opts = options.except(*COMPRESS_CALL_OPTIONS)
|
|
26
|
+
CCtx.new(**ctx_opts).compress(data, **call_opts)
|
|
15
27
|
end
|
|
16
28
|
|
|
17
|
-
# Convenience method for one-off decompression
|
|
18
|
-
#
|
|
29
|
+
# Convenience method for one-off decompression.
|
|
30
|
+
# Per-call options (dict, initial_capacity, max_decompressed_size/max_size) are
|
|
31
|
+
# passed to #decompress; any other keyword is a context parameter (e.g.
|
|
32
|
+
# format:, window_log_max:) applied to a fresh DCtx.
|
|
19
33
|
def self.decompress(data, **options)
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
call_opts = options.slice(*DECOMPRESS_CALL_OPTIONS)
|
|
35
|
+
ctx_opts = options.except(*DECOMPRESS_CALL_OPTIONS)
|
|
36
|
+
DCtx.new(**ctx_opts).decompress(data, **call_opts)
|
|
22
37
|
end
|
|
23
38
|
|
|
24
39
|
# Get the decompressed content size from a compressed frame
|
|
@@ -88,6 +103,10 @@ module VibeZstd
|
|
|
88
103
|
# Memory footprint: ~128KB per DCtx × unique dictionaries × threads
|
|
89
104
|
# Example: 3 dicts × 5 Puma threads = 1.9MB total
|
|
90
105
|
#
|
|
106
|
+
# Storage: uses Thread#thread_variable_get/set (true thread-local) so that
|
|
107
|
+
# fiber-based servers (Falcon, async) share one pool per OS thread rather
|
|
108
|
+
# than allocating a fresh pool for every fiber.
|
|
109
|
+
#
|
|
91
110
|
# Note: Only supports per-operation parameters (level, dict, pledged_size, initial_capacity)
|
|
92
111
|
# Does NOT support context-level settings (nb_workers, checksum_flag, etc.)
|
|
93
112
|
module ThreadLocal
|
|
@@ -103,9 +122,10 @@ module VibeZstd
|
|
|
103
122
|
# Key by dictionary ID, or :default if no dict
|
|
104
123
|
key = dict ? dict.dict_id : :default
|
|
105
124
|
|
|
106
|
-
# Get or create thread-local context pool
|
|
107
|
-
Thread.current
|
|
108
|
-
cctx =
|
|
125
|
+
# Get or create thread-local context pool (true thread-local, not fiber-local)
|
|
126
|
+
pool = Thread.current.thread_variable_get(:vibe_zstd_cctx_pool) || {}
|
|
127
|
+
cctx = pool[key] ||= VibeZstd::CCtx.new
|
|
128
|
+
Thread.current.thread_variable_set(:vibe_zstd_cctx_pool, pool)
|
|
109
129
|
|
|
110
130
|
# Build options hash
|
|
111
131
|
options = {}
|
|
@@ -122,18 +142,21 @@ module VibeZstd
|
|
|
122
142
|
# @param data [String] Data to decompress
|
|
123
143
|
# @param dict [DDict] Decompression dictionary (optional)
|
|
124
144
|
# @param initial_capacity [Integer] Initial buffer size for unknown-size frames (optional)
|
|
145
|
+
# @param max_decompressed_size [Integer] Output-size limit; raises DecompressedSizeExceeded if exceeded (optional)
|
|
125
146
|
# @return [String] Decompressed data
|
|
126
|
-
def self.decompress(data, dict: nil, initial_capacity: nil)
|
|
147
|
+
def self.decompress(data, dict: nil, initial_capacity: nil, max_decompressed_size: nil)
|
|
127
148
|
key = dict ? dict.dict_id : :default
|
|
128
149
|
|
|
129
|
-
# Get or create thread-local context pool
|
|
130
|
-
Thread.current
|
|
131
|
-
dctx =
|
|
150
|
+
# Get or create thread-local context pool (true thread-local, not fiber-local)
|
|
151
|
+
pool = Thread.current.thread_variable_get(:vibe_zstd_dctx_pool) || {}
|
|
152
|
+
dctx = pool[key] ||= VibeZstd::DCtx.new
|
|
153
|
+
Thread.current.thread_variable_set(:vibe_zstd_dctx_pool, pool)
|
|
132
154
|
|
|
133
155
|
# Build options hash
|
|
134
156
|
options = {}
|
|
135
157
|
options[:dict] = dict if dict
|
|
136
158
|
options[:initial_capacity] = initial_capacity if initial_capacity
|
|
159
|
+
options[:max_decompressed_size] = max_decompressed_size if max_decompressed_size
|
|
137
160
|
|
|
138
161
|
# C code will validate dict matches frame requirements
|
|
139
162
|
dctx.decompress(data, **options)
|
|
@@ -142,19 +165,21 @@ module VibeZstd
|
|
|
142
165
|
# Clear all thread-local context pools for the current thread
|
|
143
166
|
# Useful for testing or explicit memory management
|
|
144
167
|
def self.clear_thread_cache!
|
|
145
|
-
Thread.current
|
|
146
|
-
Thread.current
|
|
168
|
+
Thread.current.thread_variable_set(:vibe_zstd_cctx_pool, {})
|
|
169
|
+
Thread.current.thread_variable_set(:vibe_zstd_dctx_pool, {})
|
|
147
170
|
nil
|
|
148
171
|
end
|
|
149
172
|
|
|
150
173
|
# Get statistics about the current thread's context pools
|
|
151
174
|
# @return [Hash] Pool statistics
|
|
152
175
|
def self.thread_cache_stats
|
|
176
|
+
cctx_pool = Thread.current.thread_variable_get(:vibe_zstd_cctx_pool)
|
|
177
|
+
dctx_pool = Thread.current.thread_variable_get(:vibe_zstd_dctx_pool)
|
|
153
178
|
{
|
|
154
|
-
compression_contexts:
|
|
155
|
-
decompression_contexts:
|
|
156
|
-
compression_keys:
|
|
157
|
-
decompression_keys:
|
|
179
|
+
compression_contexts: cctx_pool&.size || 0,
|
|
180
|
+
decompression_contexts: dctx_pool&.size || 0,
|
|
181
|
+
compression_keys: cctx_pool&.keys || [],
|
|
182
|
+
decompression_keys: dctx_pool&.keys || []
|
|
158
183
|
}
|
|
159
184
|
end
|
|
160
185
|
end
|
|
@@ -229,7 +254,7 @@ module VibeZstd
|
|
|
229
254
|
loop do
|
|
230
255
|
# Check buffer for separator
|
|
231
256
|
if (idx = @line_buffer.index(sep))
|
|
232
|
-
return @line_buffer.slice!(0, idx + sep.
|
|
257
|
+
return @line_buffer.slice!(0, idx + sep.length)
|
|
233
258
|
end
|
|
234
259
|
|
|
235
260
|
# Read more data in larger chunks
|
|
@@ -240,7 +265,7 @@ module VibeZstd
|
|
|
240
265
|
end
|
|
241
266
|
|
|
242
267
|
# Return remaining buffer or nil
|
|
243
|
-
@line_buffer.empty? ? nil : @line_buffer.slice!(0, @line_buffer.
|
|
268
|
+
@line_buffer.empty? ? nil : @line_buffer.slice!(0, @line_buffer.length)
|
|
244
269
|
end
|
|
245
270
|
|
|
246
271
|
# Iterate over lines
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vibe_zstd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kelley Reynolds
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-06-12 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: benchmark-ips
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- benchmark/streaming.rb
|
|
65
65
|
- ext/vibe_zstd/cctx.c
|
|
66
66
|
- ext/vibe_zstd/dctx.c
|
|
67
|
+
- ext/vibe_zstd/depend
|
|
67
68
|
- ext/vibe_zstd/dict.c
|
|
68
69
|
- ext/vibe_zstd/extconf.rb
|
|
69
70
|
- ext/vibe_zstd/frames.c
|