solid-resp-ractor 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e95465d9cdecff50ce715e8150335b5f90da49121a1a047edb433210e308015c
4
- data.tar.gz: 1dd82d1a8d80ea412d1ce2257f51b081dc5231718f9aaed71386d739a6bc8ac3
3
+ metadata.gz: 6f2f11114c1db059699754b9f59cc7f477973e8afd851a988774704491cce1d0
4
+ data.tar.gz: b34e06efbf004e73d426a6007666305c394b20c0b5ca98749719422ae6f2bd99
5
5
  SHA512:
6
- metadata.gz: bd9661ccd7e481088936f7b3425da4bacf8a58754dab3122397bbd003d03c7704fe1e6b813be11575dab60b790548c776ca1e452998c80891b8a967c4ccfa7d2
7
- data.tar.gz: 901b266a995302514ebe6f0fb4cdfd0e9641e415b908fb109b249b8ed14e0b0f2fe2572c2e902158d6af9918f50cc1ed725f1a48f1c70c825484578dbbc99a4b
6
+ metadata.gz: da143efb9f9261281d77b9638e4f2b21affaf1e26f2a11517934adfd8ad4c7326ad7442f6f3df8ce448ddfa7bcea0b588277bbe6eccc41111c9b5b268d92456e
7
+ data.tar.gz: 4970b3297c71ba673e49ea9d41effc4038b20bc69c56f4f5f71d16d33ceaab9df976f7d91946a66902cb18a3dcdd5fe614771a8a320c7ae585b942c19cfe4d74
data/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.5] - 2026-10-04
8
+
9
+ ### Changed
10
+
11
+ - Parse RESP integers and lengths in place from the read buffer, without
12
+ allocating a temporary line String. Non-decimal forms still fall back to
13
+ `Kernel#Integer` semantics.
14
+ - Read nested values through an internal depth-tracked path instead of the
15
+ public `#read`, removing per-element `ensure` bookkeeping and subclass
16
+ wrappers from aggregate parsing.
17
+ - Skip handler dispatch for scalars and plain aggregates when the built-in
18
+ `Compatible` or `Typed` handlers are used.
19
+ - Clear the consumed read buffer once per reply instead of after every token.
20
+ - Encode flat commands in a single loop with inline String and small Integer
21
+ fast paths for the default argument encoder.
22
+ - Cache non-blocking IO capabilities and take only one clock reading per
23
+ readiness wait.
24
+ - Ruby 4.0.1 microbenchmarks: flat command encoding +72% to +85%, bulk reply
25
+ parsing +51%, 10-element arrays +68% with 33 → 11 allocations, and
26
+ 50-reply pipelines +46% with 125 → 75 allocations.
27
+
28
+ ### Fixed
29
+
30
+ - Locate CRLF terminators by byte offset (`String#byteindex`, Ruby 3.2+) so
31
+ custom sources returning multibyte UTF-8 chunks are parsed correctly.
32
+
5
33
  ## [0.1.4] - 2026-10-01
6
34
 
7
35
  ### Changed
@@ -27,61 +27,77 @@ module SolidRespRactor
27
27
  Array.new(129) { |length| "*#{length}#{CRLF}".freeze },
28
28
  )
29
29
 
30
+ INTEGER_STRINGS = Ractor.make_shareable(
31
+ Array.new(1_024) { |integer| integer.to_s.freeze },
32
+ )
33
+ EMPTY_COMMAND = "RESP command cannot be empty"
34
+
30
35
  attr_reader :argument_encoder
31
36
 
32
37
  def initialize(argument_encoder: ArgumentEncoders::Default, expand_arrays: true)
33
38
  @argument_encoder = argument_encoder
34
39
  @expand_arrays = expand_arrays
40
+ @default_arguments = argument_encoder.equal?(ArgumentEncoders::Default)
35
41
  freeze
36
42
  end
37
43
 
38
44
  def encode(command)
39
- unless @expand_arrays && command.any? { |argument| argument.is_a?(Array) }
40
- return encode_flat(command)
41
- end
42
-
43
- length = command.sum { |argument| expanded?(argument) ? argument.length : 1 }
44
- raise ArgumentError, "RESP command cannot be empty" if length.zero?
45
-
46
- command.each_with_object(+"*#{length}#{CRLF}") do |argument, buffer|
47
- if expanded?(argument)
48
- argument.each { |value| append_argument(buffer, value) }
45
+ length = command.length
46
+ raise ArgumentError, EMPTY_COMMAND if length.zero?
47
+
48
+ buffer = +(ARRAY_HEADERS[length] || "*#{length}#{CRLF}")
49
+ index = 0
50
+ while index < length
51
+ argument = command[index]
52
+ if @default_arguments && argument.is_a?(String)
53
+ value = argument
54
+ elsif @expand_arrays && argument.is_a?(Array)
55
+ return encode_expanded(command)
49
56
  else
50
- append_argument(buffer, argument)
57
+ value = encode_argument(argument)
51
58
  end
59
+ bytesize = value.bytesize
60
+ buffer << (BULK_HEADERS[bytesize] || "$#{bytesize}#{CRLF}") << value << CRLF
61
+ index += 1
52
62
  end
63
+ buffer
53
64
  end
54
65
 
55
66
  private
56
67
 
57
- def encode_flat(command)
58
- length = command.length
59
- raise ArgumentError, "RESP command cannot be empty" if length.zero?
68
+ def encode_expanded(command)
69
+ length = command.sum { |argument| argument.is_a?(Array) ? argument.length : 1 }
70
+ raise ArgumentError, EMPTY_COMMAND if length.zero?
60
71
 
61
- command.each_with_object(+array_header(length)) do |argument, buffer|
62
- append_argument(buffer, argument)
72
+ command.each_with_object(+"*#{length}#{CRLF}") do |argument, buffer|
73
+ if argument.is_a?(Array)
74
+ argument.each { |value| append_argument(buffer, value) }
75
+ else
76
+ append_argument(buffer, argument)
77
+ end
63
78
  end
64
79
  end
65
80
 
66
- def array_header(length)
67
- ARRAY_HEADERS[length] || "*#{length}#{CRLF}"
81
+ def append_argument(buffer, argument)
82
+ value = encode_argument(argument)
83
+ bytesize = value.bytesize
84
+ buffer << (BULK_HEADERS[bytesize] || "$#{bytesize}#{CRLF}") << value << CRLF
68
85
  end
69
86
 
70
- def expanded?(argument)
71
- @expand_arrays && argument.is_a?(Array)
72
- end
87
+ def encode_argument(argument)
88
+ if @default_arguments
89
+ return argument if argument.is_a?(String)
90
+ if argument.is_a?(Integer) && argument >= 0 && argument < 1_024
91
+ return INTEGER_STRINGS[argument]
92
+ end
93
+ end
73
94
 
74
- def append_argument(buffer, argument)
75
95
  value = @argument_encoder.call(argument)
76
96
  unless value.is_a?(String)
77
97
  raise TypeError, "argument encoder must return a String, got #{value.class}"
78
98
  end
79
99
 
80
- buffer << bulk_header(value.bytesize) << value << CRLF
81
- end
82
-
83
- def bulk_header(length)
84
- BULK_HEADERS[length] || "$#{length}#{CRLF}"
100
+ value
85
101
  end
86
102
  end
87
103
 
@@ -4,6 +4,7 @@ module SolidRespRactor
4
4
  class Reader
5
5
  CRLF = "\r\n"
6
6
  COMPACT_THRESHOLD = 16 * 1024
7
+ PASSTHROUGH_HANDLERS = Ractor.make_shareable([Handlers::Compatible, Handlers::Typed])
7
8
 
8
9
  attr_reader :source
9
10
 
@@ -31,11 +32,15 @@ module SolidRespRactor
31
32
  )
32
33
  @read_timeout = read_timeout
33
34
  @handler = handler
35
+ # Built-in handlers return scalars and plain aggregates unchanged, so
36
+ # the hot path can skip the dispatch for those types.
37
+ @passthrough = PASSTHROUGH_HANDLERS.include?(handler)
34
38
  @error_mapper = error_mapper
35
39
  @limits = limits
40
+ @max_line_size = limits.max_line_size
41
+ @max_nesting_depth = limits.max_nesting_depth
36
42
  @buffer = +""
37
43
  @offset = 0
38
- @read_depth = 0
39
44
  @captured_error = nil
40
45
  end
41
46
 
@@ -55,116 +60,139 @@ module SolidRespRactor
55
60
  end
56
61
 
57
62
  def read(exception: true)
58
- top_level = @read_depth.zero?
59
- @captured_error = nil if top_level
60
- @read_depth += 1
61
- if @read_depth > @limits.max_nesting_depth
62
- raise ProtocolError, "RESP nesting exceeds #{@limits.max_nesting_depth}"
63
- end
64
- value = read_type(read_byte)
65
- raise @captured_error if top_level && exception && @captured_error
63
+ @captured_error = nil
64
+ value = read_value(1)
65
+ clear_consumed_buffer
66
+ raise @captured_error if exception && @captured_error
66
67
 
67
68
  value
68
69
  rescue EOFError
69
70
  raise ConnectionError, "RESP stream closed"
70
- ensure
71
- @read_depth -= 1
72
71
  end
73
72
 
74
73
  private
75
74
 
76
- def read_type(type)
75
+ def read_value(depth)
76
+ if depth > @max_nesting_depth
77
+ raise ProtocolError, "RESP nesting exceeds #{@max_nesting_depth}"
78
+ end
79
+
80
+ fill_buffer if @offset >= @buffer.bytesize
81
+ type = @buffer.getbyte(@offset)
82
+ @offset += 1
83
+ read_type(type, depth)
84
+ end
85
+
86
+ def read_type(type, depth)
77
87
  case type
78
- when 43 then handle(:simple_string, read_line)
79
- when 45 then error_response(read_line, blob: false)
80
- when 58 then handle(:integer, parse_integer(read_line))
81
88
  when 36 then read_blob
82
- when 42 then read_array
89
+ when 43
90
+ value = read_line
91
+ @passthrough ? value : handle(:simple_string, value)
92
+ when 58
93
+ value = read_integer
94
+ @passthrough ? value : handle(:integer, value)
95
+ when 42 then read_array(depth)
96
+ when 45 then error_response(read_line, blob: false)
83
97
  when 95 then read_null
84
98
  when 35 then read_boolean
85
99
  when 44 then handle(:double, parse_float(read_line))
86
- when 40 then handle(:big_number, parse_integer(read_line))
87
- when 37 then read_map
88
- when 126 then read_collection(:set)
89
- when 62 then read_collection(:push)
100
+ when 40 then handle(:big_number, read_integer)
101
+ when 37 then read_map(depth)
102
+ when 126 then read_collection(:set, depth)
103
+ when 62 then read_collection(:push, depth)
90
104
  when 61 then read_verbatim
91
- when 33 then error_response(read_sized_string(streamed: true), blob: true)
92
- when 124 then read_attribute
105
+ when 33 then error_response(read_sized_string(true), blob: true)
106
+ when 124 then read_attribute(depth)
93
107
  else raise ProtocolError, "Unknown RESP type byte: #{type.chr.inspect}"
94
108
  end
95
109
  end
96
110
 
97
111
  def read_blob
98
- length = read_length(nullable: true, streamed: true)
99
- return handle(:blob_string, nil) unless length
100
-
101
- validate_blob_length(length) unless length == :streamed
102
- value = length == :streamed ? read_chunked_string : read_sized_value(length)
103
- handle(:blob_string, value)
112
+ length = read_length(true, true)
113
+ if length.nil?
114
+ value = nil
115
+ elsif length == :streamed
116
+ value = read_chunked_string
117
+ else
118
+ validate_blob_length(length)
119
+ value = read_sized_value(length)
120
+ end
121
+ @passthrough ? value : handle(:blob_string, value)
104
122
  end
105
123
 
106
- def read_array
107
- length = read_length(nullable: true, streamed: true)
108
- return handle(:array, nil) unless length
109
-
110
- validate_collection_length(length) unless length == :streamed
111
- value = if length == :streamed
112
- read_streamed_collection
124
+ def read_array(depth)
125
+ length = read_length(true, true)
126
+ if length.nil?
127
+ value = nil
128
+ elsif length == :streamed
129
+ value = read_streamed_collection(depth)
113
130
  else
114
- Array.new(length) { read(exception: false) }
131
+ validate_collection_length(length)
132
+ value = read_elements(length, depth + 1)
133
+ end
134
+ @passthrough ? value : handle(:array, value)
135
+ end
136
+
137
+ def read_elements(length, depth)
138
+ values = Array.new(length)
139
+ index = 0
140
+ while index < length
141
+ values[index] = read_value(depth)
142
+ index += 1
115
143
  end
116
- handle(:array, value)
144
+ values
117
145
  end
118
146
 
119
- def read_collection(type)
120
- length = read_length(streamed: true)
121
- validate_collection_length(length) unless length == :streamed
147
+ def read_collection(type, depth)
148
+ length = read_length(false, true)
122
149
  value = if length == :streamed
123
- read_streamed_collection
150
+ read_streamed_collection(depth)
124
151
  else
125
- Array.new(length) { read(exception: false) }
152
+ validate_collection_length(length)
153
+ read_elements(length, depth + 1)
126
154
  end
127
155
  handle(type, value)
128
156
  end
129
157
 
130
- def read_map
131
- length = read_length(streamed: true)
132
- validate_collection_length(length) unless length == :streamed
158
+ def read_map(depth)
159
+ length = read_length(false, true)
133
160
  value = if length == :streamed
134
- read_streamed_map
161
+ read_streamed_map(depth)
135
162
  else
136
- {}.tap do |result|
137
- length.times { result[read(exception: false)] = read(exception: false) }
138
- end
163
+ validate_collection_length(length)
164
+ result = {}
165
+ length.times { result[read_value(depth + 1)] = read_value(depth + 1) }
166
+ result
139
167
  end
140
168
  handle(:map, value)
141
169
  end
142
170
 
143
- def read_streamed_collection
171
+ def read_streamed_collection(depth)
144
172
  values = []
145
173
  loop do
146
174
  type = read_byte
147
175
  break if aggregate_end?(type)
148
176
 
149
- values << read_type(type)
177
+ values << read_type(type, depth)
150
178
  validate_collection_length(values.length)
151
179
  end
152
180
  values
153
181
  end
154
182
 
155
- def read_streamed_map
156
- {}.tap do |result|
157
- count = 0
158
- loop do
159
- type = read_byte
160
- break if aggregate_end?(type)
183
+ def read_streamed_map(depth)
184
+ result = {}
185
+ count = 0
186
+ loop do
187
+ type = read_byte
188
+ break if aggregate_end?(type)
161
189
 
162
- key = read_type(type)
163
- result[key] = read(exception: false)
164
- count += 1
165
- validate_collection_length(count)
166
- end
190
+ key = read_type(type, depth)
191
+ result[key] = read_value(depth + 1)
192
+ count += 1
193
+ validate_collection_length(count)
167
194
  end
195
+ result
168
196
  end
169
197
 
170
198
  def aggregate_end?(type)
@@ -176,9 +204,9 @@ module SolidRespRactor
176
204
  true
177
205
  end
178
206
 
179
- def read_attribute
180
- attributes = read_map
181
- value = read(exception: false)
207
+ def read_attribute(depth)
208
+ attributes = read_map(depth)
209
+ value = read_value(depth + 1)
182
210
  handle(:attribute, [attributes, value])
183
211
  end
184
212
 
@@ -186,7 +214,7 @@ module SolidRespRactor
186
214
  value = read_line
187
215
  raise ProtocolError, "RESP null must not contain data" unless value.empty?
188
216
 
189
- handle(:null, nil)
217
+ @passthrough ? nil : handle(:null, nil)
190
218
  end
191
219
 
192
220
  def read_boolean
@@ -198,7 +226,7 @@ module SolidRespRactor
198
226
  end
199
227
 
200
228
  def read_verbatim
201
- value = read_sized_string(streamed: true)
229
+ value = read_sized_string(true)
202
230
  unless value.bytesize >= 4 && value.byteslice(3, 1) == ":"
203
231
  raise ProtocolError, "Invalid RESP verbatim string"
204
232
  end
@@ -206,10 +234,12 @@ module SolidRespRactor
206
234
  handle(:verbatim, [value.byteslice(0, 3).freeze, value.byteslice(4..)])
207
235
  end
208
236
 
209
- def read_sized_string(streamed: false)
210
- length = read_length(streamed: streamed)
211
- validate_blob_length(length) unless length == :streamed
212
- length == :streamed ? read_chunked_string : read_sized_value(length)
237
+ def read_sized_string(streamed = false)
238
+ length = read_length(false, streamed)
239
+ return read_chunked_string if length == :streamed
240
+
241
+ validate_blob_length(length)
242
+ read_sized_value(length)
213
243
  end
214
244
 
215
245
  def read_chunked_string
@@ -230,22 +260,65 @@ module SolidRespRactor
230
260
  end
231
261
 
232
262
  def read_sized_value(length)
263
+ offset = @offset
264
+ buffer = @buffer
265
+ if buffer.bytesize - offset >= length + 2 &&
266
+ buffer.getbyte(offset + length) == 13 &&
267
+ buffer.getbyte(offset + length + 1) == 10
268
+ @offset = offset + length + 2
269
+ return buffer.byteslice(offset, length)
270
+ end
271
+
233
272
  value = read_bytes(length)
234
273
  read_crlf
235
274
  value
236
275
  end
237
276
 
238
- def read_length(nullable: false, streamed: false)
239
- value = read_line
240
- return :streamed if streamed && value == "?"
277
+ def read_length(nullable = false, streamed = false)
278
+ length = scan_integer
279
+ unless length
280
+ value = read_line
281
+ return :streamed if streamed && value == "?"
241
282
 
242
- length = parse_integer(value)
283
+ length = parse_integer(value)
284
+ end
243
285
  return if nullable && length == -1
244
286
  raise ProtocolError, "Invalid RESP length: #{length}" if length.negative?
245
287
 
246
288
  length
247
289
  end
248
290
 
291
+ def read_integer
292
+ scan_integer || parse_integer(read_line)
293
+ end
294
+
295
+ # Parses a plain decimal line in place, without allocating the line.
296
+ # Returns nil whenever the line is incomplete or not a plain decimal so
297
+ # the caller falls back to the exact Kernel#Integer semantics.
298
+ def scan_integer
299
+ buffer = @buffer
300
+ offset = @offset
301
+ index = find_crlf(buffer, offset)
302
+ return unless index && index - offset <= @max_line_size
303
+
304
+ position = offset
305
+ negative = buffer.getbyte(position) == 45
306
+ position += 1 if negative
307
+ return if position >= index
308
+ return if buffer.getbyte(position) == 48 && position + 1 < index
309
+
310
+ value = 0
311
+ while position < index
312
+ byte = buffer.getbyte(position)
313
+ return unless byte >= 48 && byte <= 57
314
+
315
+ value = (value * 10) + (byte - 48)
316
+ position += 1
317
+ end
318
+ @offset = index + 2
319
+ negative ? -value : value
320
+ end
321
+
249
322
  def parse_integer(value)
250
323
  Integer(value)
251
324
  rescue ArgumentError
@@ -264,36 +337,43 @@ module SolidRespRactor
264
337
 
265
338
  def read_line
266
339
  loop do
267
- if (index = @buffer.index(CRLF, @offset))
268
- line_length = index - @offset
269
- if line_length > @limits.max_line_size
270
- raise ProtocolError, "RESP line exceeds #{@limits.max_line_size} bytes"
340
+ offset = @offset
341
+ if (index = find_crlf(@buffer, offset))
342
+ line_length = index - offset
343
+ if line_length > @max_line_size
344
+ raise ProtocolError, "RESP line exceeds #{@max_line_size} bytes"
271
345
  end
272
- value = @buffer.byteslice(@offset, line_length)
273
346
  @offset = index + 2
274
- clear_consumed_buffer
275
- return value
347
+ return @buffer.byteslice(offset, line_length)
276
348
  end
277
- if available_bytes > @limits.max_line_size
278
- raise ProtocolError, "RESP line exceeds #{@limits.max_line_size} bytes"
349
+ if available_bytes > @max_line_size
350
+ raise ProtocolError, "RESP line exceeds #{@max_line_size} bytes"
279
351
  end
280
352
  fill_buffer
281
353
  end
282
354
  end
283
355
 
356
+ if "".respond_to?(:byteindex)
357
+ def find_crlf(buffer, offset)
358
+ buffer.byteindex(CRLF, offset)
359
+ end
360
+ else
361
+ def find_crlf(buffer, offset)
362
+ buffer.index(CRLF, offset)
363
+ end
364
+ end
365
+
284
366
  def read_bytes(length)
285
367
  fill_buffer while available_bytes < length
286
368
  value = @buffer.byteslice(@offset, length)
287
369
  @offset += length
288
- clear_consumed_buffer
289
370
  value
290
371
  end
291
372
 
292
373
  def read_byte
293
- fill_buffer while available_bytes < 1
374
+ fill_buffer if @offset >= @buffer.bytesize
294
375
  value = @buffer.getbyte(@offset)
295
376
  @offset += 1
296
- clear_consumed_buffer
297
377
  value
298
378
  end
299
379
 
@@ -306,11 +386,14 @@ module SolidRespRactor
306
386
  end
307
387
 
308
388
  @offset += 2
309
- clear_consumed_buffer
310
389
  end
311
390
 
312
391
  def fill_buffer
313
- compact_buffer
392
+ if @offset == @buffer.bytesize
393
+ clear_consumed_buffer
394
+ else
395
+ compact_buffer
396
+ end
314
397
  chunk = @source.read(timeout: @read_timeout)
315
398
  raise EOFError if chunk.nil?
316
399
  unless chunk.is_a?(String)
@@ -48,28 +48,18 @@ module SolidRespRactor
48
48
  @selector = selector
49
49
  @clock = clock
50
50
  @read_buffer = +""
51
+ @nonblocking = io.respond_to?(:read_nonblock)
52
+ @selector_waits = selector.respond_to?(:wait)
53
+ @selectable = nil
51
54
  end
52
55
 
53
56
  def read(timeout:)
54
- return blocking_read unless @io.respond_to?(:read_nonblock)
57
+ return blocking_read unless @nonblocking
55
58
 
56
- deadline = nil
57
- loop do
58
- chunk = @io.read_nonblock(
59
- @chunk_size,
60
- @read_buffer,
61
- exception: false,
62
- )
63
- return chunk unless chunk == :wait_readable || chunk == :wait_writable
64
-
65
- deadline ||= @clock.now + timeout if timeout
66
- remaining = deadline && deadline - @clock.now
67
- raise_timeout(timeout) if remaining && remaining <= 0
59
+ chunk = @io.read_nonblock(@chunk_size, @read_buffer, exception: false)
60
+ return chunk unless chunk == :wait_readable || chunk == :wait_writable
68
61
 
69
- selectable = @io.respond_to?(:to_io) ? @io.to_io : @io
70
- event = chunk == :wait_readable ? :read : :write
71
- raise_timeout(timeout) unless wait(selectable, event, remaining)
72
- end
62
+ wait_for_chunk(chunk, timeout)
73
63
  rescue IOError, SystemCallError => error
74
64
  raise ConnectionError, error.message, cause: error
75
65
  end
@@ -84,12 +74,29 @@ module SolidRespRactor
84
74
 
85
75
  private
86
76
 
77
+ def wait_for_chunk(chunk, timeout)
78
+ selectable = (@selectable ||= @io.respond_to?(:to_io) ? @io.to_io : @io)
79
+ deadline = timeout && @clock.now + timeout
80
+ remaining = timeout
81
+ loop do
82
+ raise_timeout(timeout) if remaining && remaining <= 0
83
+
84
+ event = chunk == :wait_readable ? :read : :write
85
+ raise_timeout(timeout) unless wait(selectable, event, remaining)
86
+
87
+ chunk = @io.read_nonblock(@chunk_size, @read_buffer, exception: false)
88
+ return chunk unless chunk == :wait_readable || chunk == :wait_writable
89
+
90
+ remaining = deadline && deadline - @clock.now
91
+ end
92
+ end
93
+
87
94
  def blocking_read
88
95
  @io.read(@chunk_size)
89
96
  end
90
97
 
91
98
  def wait(io, event, timeout)
92
- return @selector.wait(io, event, timeout) if @selector.respond_to?(:wait)
99
+ return @selector.wait(io, event, timeout) if @selector_waits
93
100
 
94
101
  readers = event == :read ? [io] : nil
95
102
  writers = event == :write ? [io] : nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidRespRactor
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid-resp-ractor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Vandenbogaerde