io-event 1.14.2 → 1.14.4

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: 0deb2b3991062957f5767ac97e9abf7fe72b20e2264d08fd81293c407f92024f
4
- data.tar.gz: 93b9e08ea55f39b5b983b8627b91720047154a2f778d86fea620da54d5e3173c
3
+ metadata.gz: e9427c8ecb6a9bd50366bb9e83324a7777421b00e7e286d58579850d19c8cb70
4
+ data.tar.gz: a22a8091b388a3f737501f7048d743d898cd4e271c0cfef79e7d4003ebff38bb
5
5
  SHA512:
6
- metadata.gz: 2e58ec44e475bad561ddd53797a1552037fc92a4602dd89f6af92148130d4486d7dca47b6ae09bf11373be7e393a1baea835ae29e3ea388da4d4189b1d87be47
7
- data.tar.gz: 9040993cc15180b65f142a28529d6672dceb88de0ae887ca9f1815bf0e11d4fe7929a3295ec50110b6c7faab83728cf36bf5c51d564b377eca4af40b756e9140
6
+ metadata.gz: a929a416fd1ae9f790102275d571e637543cf3dd5f1520cb92032d2b7c0974774d557890a13631eac2f40d8257a5b75f5da0fd4bcce0ac96197ad824669ef941
7
+ data.tar.gz: 4d9ff031fb26b596a3e654b7525c9f6bf302006cb424c904dcf2045f4861bcd7158cebb3096b2739a76d5dd4ec2a4466dbe0b2f395aa675ef8c165aad3896f8a
checksums.yaml.gz.sig CHANGED
Binary file
@@ -22,8 +22,8 @@ $ bundle add io-event
22
22
  This example shows how to perform a blocking operation
23
23
 
24
24
  ```ruby
25
- require 'fiber'
26
- require 'io/event'
25
+ require "fiber"
26
+ require "io/event"
27
27
 
28
28
  selector = IO::Event::Selector.new(Fiber.current)
29
29
  input, output = IO.pipe
data/ext/extconf.rb CHANGED
@@ -5,6 +5,7 @@
5
5
  # Copyright, 2021-2025, by Samuel Williams.
6
6
  # Copyright, 2023, by Math Ieu.
7
7
  # Copyright, 2025, by Stanislav (Stas) Katkov.
8
+ # Copyright, 2026, by Stan Hu.
8
9
 
9
10
  return if RUBY_DESCRIPTION =~ /jruby/
10
11
 
@@ -54,7 +55,7 @@ have_func("rb_io_descriptor")
54
55
  have_func("&rb_process_status_wait")
55
56
  have_func("rb_fiber_current")
56
57
  have_func("&rb_fiber_raise")
57
- have_func("epoll_pwait2")
58
+ have_func("epoll_pwait2") if enable_config("epoll_pwait2", true)
58
59
 
59
60
  have_header("ruby/io/buffer.h")
60
61
 
@@ -728,7 +728,7 @@ VALUE io_write_loop(VALUE _arguments) {
728
728
  break;
729
729
  } else if (length > 0 && IO_Event_try_again(errno)) {
730
730
  if (DEBUG_IO_WRITE) fprintf(stderr, "IO_Event_Selector_KQueue_io_wait(fd=%d, length=%zu)\n", arguments->descriptor, length);
731
- IO_Event_Selector_KQueue_io_wait(arguments->self, arguments->fiber, arguments->io, RB_INT2NUM(IO_EVENT_READABLE));
731
+ IO_Event_Selector_KQueue_io_wait(arguments->self, arguments->fiber, arguments->io, RB_INT2NUM(IO_EVENT_WRITABLE));
732
732
  } else {
733
733
  if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno);
734
734
  return rb_fiber_scheduler_io_result(-1, errno);
@@ -737,7 +737,7 @@ VALUE io_write_loop(VALUE _arguments) {
737
737
  maximum_size = size - offset;
738
738
  }
739
739
 
740
- if (DEBUG_IO_READ) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset);
740
+ if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset);
741
741
  return rb_fiber_scheduler_io_result(total, 0);
742
742
  };
743
743
 
@@ -29,7 +29,7 @@ VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags)
29
29
  int IO_Event_Selector_nonblock_set(int file_descriptor)
30
30
  {
31
31
  #ifdef _WIN32
32
- rb_w32_set_nonblock(file_descriptor);
32
+ return rb_w32_set_nonblock(file_descriptor);
33
33
  #else
34
34
  // Get the current mode:
35
35
  int flags = fcntl(file_descriptor, F_GETFL, 0);
@@ -988,6 +988,10 @@ VALUE IO_Event_Selector_URing_io_close(VALUE self, VALUE io) {
988
988
  io_uring_prep_close(sqe, descriptor);
989
989
  io_uring_sqe_set_data(sqe, NULL);
990
990
  io_uring_submit_now(selector);
991
+
992
+ // It would be nice to explore not flushing immediately, but instead deferring to the next select cycle.
993
+ // The problem with this approach is that if the user expects the file descriptor to be closed immediately, (e.g. before fork), it may not be closed in time.
994
+ // io_uring_submit_pending(selector);
991
995
  } else {
992
996
  close(descriptor);
993
997
  }
@@ -1101,13 +1105,19 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1101
1105
  }
1102
1106
 
1103
1107
  io_uring_cq_advance(ring, 1);
1104
- // This marks the waiting operation as "complete":
1105
- IO_Event_Selector_URing_Completion_release(selector, completion);
1106
1108
 
1109
+ VALUE fiber = 0;
1107
1110
  if (waiting && waiting->fiber) {
1108
1111
  assert(waiting->result != -ECANCELED);
1109
1112
 
1110
- IO_Event_Selector_loop_resume(&selector->backend, waiting->fiber, 0, NULL);
1113
+ fiber = waiting->fiber;
1114
+ }
1115
+
1116
+ // This marks the waiting operation as "complete":
1117
+ IO_Event_Selector_URing_Completion_release(selector, completion);
1118
+
1119
+ if (fiber) {
1120
+ IO_Event_Selector_loop_resume(&selector->backend, fiber, 0, NULL);
1111
1121
  }
1112
1122
  }
1113
1123
 
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2025, by Samuel Williams.
5
-
6
- require_relative "../support"
4
+ # Copyright, 2021-2026, by Samuel Williams.
7
5
 
8
6
  module IO::Event
9
7
  # @namespace
@@ -128,6 +126,12 @@ module IO::Event
128
126
  @selector.ready?
129
127
  end
130
128
 
129
+ # Run the given blocking operation and wait for its completion.
130
+ def blocking_operation_wait(operation)
131
+ log("Waiting for blocking operation #{operation.inspect}")
132
+ @selector.blocking_operation_wait(operation)
133
+ end
134
+
131
135
  # Wait for the given process, forwarded to the underlying selector.
132
136
  def process_wait(*arguments)
133
137
  log("Waiting for process with #{arguments.inspect}")
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2021, by Wander Hillen.
5
- # Copyright, 2021-2025, by Samuel Williams.
5
+ # Copyright, 2021-2026, by Samuel Williams.
6
6
 
7
7
  class IO
8
8
  module Event
@@ -152,7 +152,7 @@ class IO
152
152
  original_size = @contents.size
153
153
 
154
154
  # Filter out elements that match the condition - O(n)
155
- @contents.reject! {|element| yield(element)}
155
+ @contents.reject!{|element| yield(element)}
156
156
 
157
157
  # If we removed elements, rebuild the heap - O(n)
158
158
  if @contents.size < original_size
@@ -166,7 +166,7 @@ class IO
166
166
  # Validate the heap invariant. Every element except the root must not be smaller than its parent element. Note that it MAY be equal.
167
167
  def valid?
168
168
  # Notice we skip index 0 on purpose, because it has no parent:
169
- (1..(@contents.size - 1)).all? {|index| @contents[index] >= @contents[(index - 1) / 2]}
169
+ (1..(@contents.size - 1)).all?{|index| @contents[index] >= @contents[(index - 1) / 2]}
170
170
  end
171
171
 
172
172
  private
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2025, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
  # Copyright, 2023, by Math Ieu.
6
6
 
7
7
  require_relative "../interrupt"
8
- require_relative "../support"
9
8
 
10
9
  module IO::Event
11
10
  module Selector
@@ -184,127 +183,64 @@ module IO::Event
184
183
  errno == EAGAIN or errno == EWOULDBLOCK
185
184
  end
186
185
 
187
- if Support.fiber_scheduler_v3?
188
- # Ruby 3.3+, full IO::Buffer support.
186
+ # Read from the given IO to the buffer.
187
+ #
188
+ # @parameter length [Integer] The minimum number of bytes to read.
189
+ # @parameter offset [Integer] The offset into the buffer to read to.
190
+ def io_read(fiber, io, buffer, length, offset = 0)
191
+ total = 0
189
192
 
190
- # Read from the given IO to the buffer.
191
- #
192
- # @parameter length [Integer] The minimum number of bytes to read.
193
- # @parameter offset [Integer] The offset into the buffer to read to.
194
- def io_read(fiber, io, buffer, length, offset = 0)
195
- total = 0
196
-
197
- Selector.nonblock(io) do
198
- while true
199
- result = Fiber.blocking{buffer.read(io, 0, offset)}
200
-
201
- if result < 0
202
- if length > 0 and again?(result)
203
- self.io_wait(fiber, io, IO::READABLE)
204
- else
205
- return result
206
- end
207
- elsif result == 0
208
- break
193
+ Selector.nonblock(io) do
194
+ while true
195
+ result = Fiber.blocking{buffer.read(io, 0, offset)}
196
+
197
+ if result < 0
198
+ if length > 0 and again?(result)
199
+ self.io_wait(fiber, io, IO::READABLE)
209
200
  else
210
- total += result
211
- break if total >= length
212
- offset += result
201
+ return result
213
202
  end
203
+ elsif result == 0
204
+ break
205
+ else
206
+ total += result
207
+ break if total >= length
208
+ offset += result
214
209
  end
215
210
  end
216
-
217
- return total
218
211
  end
219
212
 
220
- # Write to the given IO from the buffer.
221
- #
222
- # @parameter length [Integer] The minimum number of bytes to write.
223
- # @parameter offset [Integer] The offset into the buffer to write from.
224
- def io_write(fiber, io, buffer, length, offset = 0)
225
- total = 0
226
-
227
- Selector.nonblock(io) do
228
- while true
229
- result = Fiber.blocking{buffer.write(io, 0, offset)}
230
-
231
- if result < 0
232
- if length > 0 and again?(result)
233
- self.io_wait(fiber, io, IO::READABLE)
234
- else
235
- return result
236
- end
237
- elsif result == 0
238
- break result
213
+ return total
214
+ end
215
+
216
+ # Write to the given IO from the buffer.
217
+ #
218
+ # @parameter length [Integer] The minimum number of bytes to write.
219
+ # @parameter offset [Integer] The offset into the buffer to write from.
220
+ def io_write(fiber, io, buffer, length, offset = 0)
221
+ total = 0
222
+
223
+ Selector.nonblock(io) do
224
+ while true
225
+ result = Fiber.blocking{buffer.write(io, 0, offset)}
226
+
227
+ if result < 0
228
+ if length > 0 and again?(result)
229
+ self.io_wait(fiber, io, IO::WRITABLE)
239
230
  else
240
- total += result
241
- break if total >= length
242
- offset += result
243
- end
244
- end
245
- end
246
-
247
- return total
248
- end
249
- elsif Support.fiber_scheduler_v2?
250
- # Ruby 3.2, most IO::Buffer support, but slightly clunky read/write methods.
251
- def io_read(fiber, io, buffer, length, offset = 0)
252
- total = 0
253
-
254
- Selector.nonblock(io) do
255
- maximum_size = buffer.size - offset
256
- while maximum_size > 0
257
- result = Fiber.blocking{buffer.read(io, maximum_size, offset)}
258
-
259
- if again?(result)
260
- if length > 0
261
- self.io_wait(fiber, io, IO::READABLE)
262
- else
263
- return result
264
- end
265
- elsif result < 0
266
231
  return result
267
- else
268
- total += result
269
- offset += result
270
- break if total >= length
271
232
  end
272
-
273
- maximum_size = buffer.size - offset
233
+ elsif result == 0
234
+ break result
235
+ else
236
+ total += result
237
+ break if total >= length
238
+ offset += result
274
239
  end
275
240
  end
276
-
277
- return total
278
241
  end
279
242
 
280
- def io_write(fiber, io, buffer, length, offset = 0)
281
- total = 0
282
-
283
- Selector.nonblock(io) do
284
- maximum_size = buffer.size - offset
285
- while maximum_size > 0
286
- result = Fiber.blocking{buffer.write(io, maximum_size, offset)}
287
-
288
- if again?(result)
289
- if length > 0
290
- self.io_wait(fiber, io, IO::READABLE)
291
- else
292
- return result
293
- end
294
- elsif result < 0
295
- return result
296
- else
297
- total += result
298
- offset += result
299
- break if total >= length
300
- end
301
-
302
- maximum_size = buffer.size - offset
303
- end
304
- end
305
-
306
- return total
307
- end
243
+ return total
308
244
  end
309
245
 
310
246
  # Wait for a process to change state.
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "selector/select"
7
7
  require_relative "debug/selector"
8
- require_relative "support"
9
8
 
10
9
  module IO::Event
11
10
  # @namespace
@@ -1,50 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2025, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  class IO
7
7
  module Event
8
- # Helper methods for detecting support for various features.
8
+ # @namespace
9
9
  module Support
10
- # Some features are only availble if the IO::Buffer class is available.
11
- #
12
- # @returns [Boolean] Whether the IO::Buffer class is available.
10
+ # Check if the `IO::Buffer` class is available.
13
11
  def self.buffer?
14
12
  IO.const_defined?(:Buffer)
15
13
  end
16
-
17
- # More advanced read/write methods and blocking controls were introduced in Ruby 3.2.
18
- #
19
- # To be removed on 31 Mar 2026.
20
- def self.fiber_scheduler_v2?
21
- if RUBY_VERSION >= "3.2"
22
- return true if RUBY_VERSION >= "3.2.6"
23
-
24
- # Some interface changes were back-ported incorrectly and released in 3.2.5 <https://github.com/ruby/ruby/pull/10778> - Specifically "Improvements to IO::Buffer read/write/pread/pwrite." is missing correct size calculation.
25
- return false if RUBY_VERSION >= "3.2.5"
26
-
27
- # Feature detection:
28
- IO.const_defined?(:Buffer) and Fiber.respond_to?(:blocking) and IO::Buffer.instance_method(:read).arity == -1
29
- end
30
- end
31
-
32
- # Updated inferfaces for read/write and IO::Buffer were introduced in Ruby 3.3, including pread/pwrite.
33
- #
34
- # To become the default 31 Mar 2026.
35
- def self.fiber_scheduler_v3?
36
- return true if RUBY_VERSION >= "3.3"
37
-
38
- if fiber_scheduler_v2?
39
- # Feature detection if required:
40
- begin
41
- IO::Buffer.new.slice(0, 0).write(STDOUT)
42
- return true
43
- rescue
44
- return false
45
- end
46
- end
47
- end
48
14
  end
49
15
  end
50
16
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2025, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  # @namespace
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.14.2"
10
+ VERSION = "1.14.4"
11
11
  end
12
12
  end
data/lib/io/event.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2025, by Samuel Williams.
4
+ # Copyright, 2021-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "event/version"
7
+ require_relative "event/support"
7
8
  require_relative "event/selector"
8
9
  require_relative "event/timers"
9
10
  require_relative "event/native"
data/license.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # MIT License
2
2
 
3
3
  Copyright, 2021, by Wander Hillen.
4
- Copyright, 2021-2025, by Samuel Williams.
4
+ Copyright, 2021-2026, by Samuel Williams.
5
5
  Copyright, 2021, by Delton Ding.
6
6
  Copyright, 2021-2024, by Benoit Daloze.
7
7
  Copyright, 2022, by Alex Matchneer.
@@ -12,6 +12,9 @@ Copyright, 2024, by Anthony Ross.
12
12
  Copyright, 2024-2025, by Shizuo Fujita.
13
13
  Copyright, 2024, by Jean Boussier.
14
14
  Copyright, 2025, by Stanislav (Stas) Katkov.
15
+ Copyright, 2025, by Luke Gruber.
16
+ Copyright, 2026, by William T. Nelson.
17
+ Copyright, 2026, by Stan Hu.
15
18
 
16
19
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
20
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -18,6 +18,10 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
18
18
 
19
19
  Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
20
 
21
+ ### v1.14.3
22
+
23
+ - Fix several implementation bugs that could cause deadlocks on blocking writes.
24
+
21
25
  ### v1.14.0
22
26
 
23
27
  - [Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods](https://socketry.github.io/io-event/releases/index#enhanced-io::event::priorityheap-with-deletion-and-bulk-insertion-methods)
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v1.14.3
4
+
5
+ - Fix several implementation bugs that could cause deadlocks on blocking writes.
6
+
3
7
  ## v1.14.0
4
8
 
5
9
  ### Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods
@@ -52,10 +56,10 @@ class MyScheduler
52
56
  def initialize
53
57
  @worker_pool = IO::Event::WorkerPool.new
54
58
  end
55
-
56
- def blocking_operation_wait(operation)
57
- @worker_pool.call(operation)
58
- end
59
+
60
+ def blocking_operation_wait(operation)
61
+ @worker_pool.call(operation)
62
+ end
59
63
  end
60
64
 
61
65
  # Usage with automatic offloading
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.2
4
+ version: 1.14.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -14,8 +14,11 @@ authors:
14
14
  - Alex Matchneer
15
15
  - Anthony Ross
16
16
  - Delton Ding
17
+ - Luke Gruber
17
18
  - Pavel Rosický
19
+ - Stan Hu
18
20
  - Stanislav (Stas) Katkov
21
+ - William T. Nelson
19
22
  bindir: bin
20
23
  cert_chain:
21
24
  - |
@@ -109,14 +112,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
112
  requirements:
110
113
  - - ">="
111
114
  - !ruby/object:Gem::Version
112
- version: 3.2.6
115
+ version: '3.3'
113
116
  required_rubygems_version: !ruby/object:Gem::Requirement
114
117
  requirements:
115
118
  - - ">="
116
119
  - !ruby/object:Gem::Version
117
120
  version: '0'
118
121
  requirements: []
119
- rubygems_version: 3.6.9
122
+ rubygems_version: 4.0.3
120
123
  specification_version: 4
121
124
  summary: An event loop.
122
125
  test_files: []
metadata.gz.sig CHANGED
Binary file