io-event 1.2.2 → 1.3.3

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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021, by Samuel Williams.
4
+ # Copyright, 2021-2023, by Samuel Williams.
5
5
 
6
6
  module IO::Event
7
7
  # A thread safe synchronisation primative.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2023, by Samuel Williams.
5
5
 
6
6
  require 'io/nonblock'
7
7
 
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2023, by Samuel Williams.
5
+ # Copyright, 2023, by Math Ieu.
5
6
 
6
7
  require_relative '../interrupt'
7
8
  require_relative '../support'
@@ -103,14 +104,26 @@ module IO::Event
103
104
  self.fiber&.alive?
104
105
  end
105
106
 
106
- def transfer(events)
107
+ # Dispatch the given events to the list of waiting fibers. If the fiber was not waiting for the given events, it is reactivated by calling the given block.
108
+ def dispatch(events, &reactivate)
109
+ # We capture the tail here, because calling reactivate might modify it:
110
+ tail = self.tail
111
+
107
112
  if fiber = self.fiber
108
- self.fiber = nil
109
-
110
- fiber.transfer(events & self.events) if fiber.alive?
113
+ if fiber.alive?
114
+ revents = events & self.events
115
+ if revents.zero?
116
+ reactivate.call(self)
117
+ else
118
+ self.fiber = nil
119
+ fiber.transfer(revents)
120
+ end
121
+ else
122
+ self.fiber = nil
123
+ end
111
124
  end
112
-
113
- self.tail&.transfer(events)
125
+
126
+ tail&.dispatch(events, &reactivate)
114
127
  end
115
128
 
116
129
  def invalidate
@@ -147,13 +160,72 @@ module IO::Event
147
160
  errno == EAGAIN or errno == EWOULDBLOCK
148
161
  end
149
162
 
150
- if Support.fiber_scheduler_v2?
163
+ if Support.fiber_scheduler_v3?
164
+ # Ruby 3.3+, full IO::Buffer support.
165
+
166
+ # @parameter length [Integer] The minimum number of bytes to read.
167
+ # @parameter offset [Integer] The offset into the buffer to read to.
151
168
  def io_read(fiber, io, buffer, length, offset = 0)
152
169
  total = 0
153
170
 
154
171
  Selector.nonblock(io) do
155
172
  while true
156
- maximum_size = buffer.size - offset
173
+ result = Fiber.blocking{buffer.read(io, 0, offset)}
174
+
175
+ if result < 0
176
+ if again?(result)
177
+ self.io_wait(fiber, io, IO::READABLE)
178
+ else
179
+ return result
180
+ end
181
+ elsif result == 0
182
+ break
183
+ else
184
+ total += result
185
+ break if total >= length
186
+ offset += result
187
+ end
188
+ end
189
+ end
190
+
191
+ return total
192
+ end
193
+
194
+ # @parameter length [Integer] The minimum number of bytes to write.
195
+ # @parameter offset [Integer] The offset into the buffer to write from.
196
+ def io_write(fiber, io, buffer, length, offset = 0)
197
+ total = 0
198
+
199
+ Selector.nonblock(io) do
200
+ while true
201
+ result = Fiber.blocking{buffer.write(io, 0, offset)}
202
+
203
+ if result < 0
204
+ if again?(result)
205
+ self.io_wait(fiber, io, IO::READABLE)
206
+ else
207
+ return result
208
+ end
209
+ elsif result == 0
210
+ break result
211
+ else
212
+ total += result
213
+ break if total >= length
214
+ offset += result
215
+ end
216
+ end
217
+ end
218
+
219
+ return total
220
+ end
221
+ elsif Support.fiber_scheduler_v2?
222
+ # Ruby 3.2, most IO::Buffer support, but slightly clunky read/write methods.
223
+ def io_read(fiber, io, buffer, length, offset = 0)
224
+ total = 0
225
+
226
+ Selector.nonblock(io) do
227
+ maximum_size = buffer.size - offset
228
+ while maximum_size > 0
157
229
  result = Fiber.blocking{buffer.read(io, maximum_size, offset)}
158
230
 
159
231
  if again?(result)
@@ -169,6 +241,8 @@ module IO::Event
169
241
  offset += result
170
242
  break if total >= length
171
243
  end
244
+
245
+ maximum_size = buffer.size - offset
172
246
  end
173
247
  end
174
248
 
@@ -179,8 +253,8 @@ module IO::Event
179
253
  total = 0
180
254
 
181
255
  Selector.nonblock(io) do
182
- while true
183
- maximum_size = buffer.size - offset
256
+ maximum_size = buffer.size - offset
257
+ while maximum_size > 0
184
258
  result = Fiber.blocking{buffer.write(io, maximum_size, offset)}
185
259
 
186
260
  if again?(result)
@@ -196,19 +270,23 @@ module IO::Event
196
270
  offset += result
197
271
  break if total >= length
198
272
  end
273
+
274
+ maximum_size = buffer.size - offset
199
275
  end
200
276
  end
201
277
 
202
278
  return total
203
279
  end
204
280
  elsif Support.fiber_scheduler_v1?
281
+ # Ruby <= 3.1, limited IO::Buffer support.
205
282
  def io_read(fiber, _io, buffer, length, offset = 0)
283
+ # We need to avoid any internal buffering, so we use a duplicated IO object:
206
284
  io = IO.for_fd(_io.fileno, autoclose: false)
285
+
207
286
  total = 0
208
287
 
209
- while true
210
- maximum_size = buffer.size - offset
211
-
288
+ maximum_size = buffer.size - offset
289
+ while maximum_size > 0
212
290
  case result = blocking{io.read_nonblock(maximum_size, exception: false)}
213
291
  when :wait_readable
214
292
  if length > 0
@@ -233,6 +311,8 @@ module IO::Event
233
311
  break if size >= length
234
312
  length -= size
235
313
  end
314
+
315
+ maximum_size = buffer.size - offset
236
316
  end
237
317
 
238
318
  return total
@@ -243,12 +323,13 @@ module IO::Event
243
323
  end
244
324
 
245
325
  def io_write(fiber, _io, buffer, length, offset = 0)
326
+ # We need to avoid any internal buffering, so we use a duplicated IO object:
246
327
  io = IO.for_fd(_io.fileno, autoclose: false)
328
+
247
329
  total = 0
248
330
 
249
- while true
250
- maximum_size = buffer.size - offset
251
-
331
+ maximum_size = buffer.size - offset
332
+ while maximum_size > 0
252
333
  chunk = buffer.get_string(offset, maximum_size)
253
334
  case result = blocking{io.write_nonblock(chunk, exception: false)}
254
335
  when :wait_readable
@@ -269,6 +350,8 @@ module IO::Event
269
350
  break if result >= length
270
351
  length -= result
271
352
  end
353
+
354
+ maximum_size = buffer.size - offset
272
355
  end
273
356
 
274
357
  return total
@@ -329,12 +412,26 @@ module IO::Event
329
412
  end
330
413
  end
331
414
 
332
- @blocked = true
333
415
  duration = 0 unless @ready.empty?
334
- readable, writable, priority = ::IO.select(readable, writable, priority, duration)
335
- @blocked = false
416
+ error = nil
336
417
 
337
- ready = Hash.new(0)
418
+ # We need to handle interrupts on blocking IO. Every other implementation uses EINTR, but that doesn't work with `::IO.select` as it will retry the call on EINTR.
419
+ Thread.handle_interrupt(::Exception => :on_blocking) do
420
+ @blocked = true
421
+ readable, writable, priority = ::IO.select(readable, writable, priority, duration)
422
+ rescue ::Exception => error
423
+ # Requeue below...
424
+ ensure
425
+ @blocked = false
426
+ end
427
+
428
+ if error
429
+ # Requeue the error into the pending exception queue:
430
+ Thread.current.raise(error)
431
+ return 0
432
+ end
433
+
434
+ ready = Hash.new(0).compare_by_identity
338
435
 
339
436
  readable&.each do |io|
340
437
  ready[io] |= IO::READABLE
@@ -349,7 +446,11 @@ module IO::Event
349
446
  end
350
447
 
351
448
  ready.each do |io, events|
352
- @waiting.delete(io).transfer(events)
449
+ @waiting.delete(io).dispatch(events) do |waiter|
450
+ # Re-schedule the waiting IO:
451
+ waiter.tail = @waiting[io]
452
+ @waiting[io] = waiter
453
+ end
353
454
  end
354
455
 
355
456
  return ready.size
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'selector/select'
7
7
  require_relative 'debug/selector'
@@ -11,11 +11,7 @@ module IO::Event
11
11
  module Selector
12
12
  def self.default(env = ENV)
13
13
  if name = env['IO_EVENT_SELECTOR']&.to_sym
14
- if const_defined?(name)
15
- return const_get(name)
16
- else
17
- warn "Could not find IO_EVENT_SELECTOR=#{name}!"
18
- end
14
+ return const_get(name)
19
15
  end
20
16
 
21
17
  if self.const_defined?(:URing)
@@ -17,6 +17,17 @@ class IO
17
17
  def self.fiber_scheduler_v2?
18
18
  IO.const_defined?(:Buffer) and Fiber.respond_to?(:blocking) and IO::Buffer.instance_method(:read).arity == -1
19
19
  end
20
+
21
+ def self.fiber_scheduler_v3?
22
+ if fiber_scheduler_v2?
23
+ begin
24
+ IO::Buffer.new.slice(0, 0).write(STDOUT)
25
+ return true
26
+ rescue
27
+ return false
28
+ end
29
+ end
30
+ end
20
31
  end
21
32
  end
22
33
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2023, by Samuel Williams.
5
5
 
6
6
  class IO
7
7
  module Event
8
- VERSION = "1.2.2"
8
+ VERSION = "1.3.3"
9
9
  end
10
10
  end
data/lib/io/event.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021, by Samuel Williams.
4
+ # Copyright, 2021-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'event/version'
7
7
  require_relative 'event/selector'
data/license.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2021-2022, by Samuel Williams.
3
+ Copyright, 2021-2023, by Samuel Williams.
4
4
  Copyright, 2021, by Delton Ding.
5
5
  Copyright, 2021, by Benoit Daloze.
6
6
  Copyright, 2022, by Alex Matchneer.
7
7
  Copyright, 2022, by Bruno Sutic.
8
+ Copyright, 2023, by Math Ieu.
8
9
 
9
10
  Permission is hereby granted, free of charge, to any person obtaining a copy
10
11
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -16,8 +16,16 @@ Please see the [project documentation](https://socketry.github.io/io-event/).
16
16
 
17
17
  We welcome contributions to this project.
18
18
 
19
- 1. Fork it
20
- 2. Create your feature branch (`git checkout -b my-new-feature`)
21
- 3. Commit your changes (`git commit -am 'Add some feature'`)
22
- 4. Push to the branch (`git push origin my-new-feature`)
23
- 5. Create new Pull Request
19
+ 1. Fork it.
20
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
21
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
22
+ 4. Push to the branch (`git push origin my-new-feature`).
23
+ 5. Create new Pull Request.
24
+
25
+ ### Developer Certificate of Origin
26
+
27
+ This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
28
+
29
+ ### Contributor Covenant
30
+
31
+ This project is governed by [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
+ - Math Ieu
8
9
  - Bruno Sutic
10
+ - Alex Matchneer
9
11
  - Benoit Daloze
10
12
  - Delton Ding
11
- - machty
12
13
  autorequire:
13
14
  bindir: bin
14
15
  cert_chain:
@@ -41,64 +42,8 @@ cert_chain:
41
42
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
42
43
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
43
44
  -----END CERTIFICATE-----
44
- date: 2023-05-14 00:00:00.000000000 Z
45
- dependencies:
46
- - !ruby/object:Gem::Dependency
47
- name: bake
48
- requirement: !ruby/object:Gem::Requirement
49
- requirements:
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: '0'
53
- type: :development
54
- prerelease: false
55
- version_requirements: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: '0'
60
- - !ruby/object:Gem::Dependency
61
- name: bundler
62
- requirement: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: '0'
67
- type: :development
68
- prerelease: false
69
- version_requirements: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- version: '0'
74
- - !ruby/object:Gem::Dependency
75
- name: covered
76
- requirement: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: '0'
81
- type: :development
82
- prerelease: false
83
- version_requirements: !ruby/object:Gem::Requirement
84
- requirements:
85
- - - ">="
86
- - !ruby/object:Gem::Version
87
- version: '0'
88
- - !ruby/object:Gem::Dependency
89
- name: sus
90
- requirement: !ruby/object:Gem::Requirement
91
- requirements:
92
- - - "~>"
93
- - !ruby/object:Gem::Version
94
- version: '0.6'
95
- type: :development
96
- prerelease: false
97
- version_requirements: !ruby/object:Gem::Requirement
98
- requirements:
99
- - - "~>"
100
- - !ruby/object:Gem::Version
101
- version: '0.6'
45
+ date: 2023-10-24 00:00:00.000000000 Z
46
+ dependencies: []
102
47
  description:
103
48
  email:
104
49
  executables: []
@@ -112,10 +57,12 @@ files:
112
57
  - ext/io/event/event.h
113
58
  - ext/io/event/interrupt.c
114
59
  - ext/io/event/interrupt.h
60
+ - ext/io/event/selector/array.h
115
61
  - ext/io/event/selector/epoll.c
116
62
  - ext/io/event/selector/epoll.h
117
63
  - ext/io/event/selector/kqueue.c
118
64
  - ext/io/event/selector/kqueue.h
65
+ - ext/io/event/selector/list.h
119
66
  - ext/io/event/selector/pidfd.c
120
67
  - ext/io/event/selector/selector.c
121
68
  - ext/io/event/selector/selector.h
@@ -150,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
97
  - !ruby/object:Gem::Version
151
98
  version: '0'
152
99
  requirements: []
153
- rubygems_version: 3.4.7
100
+ rubygems_version: 3.4.10
154
101
  signing_key:
155
102
  specification_version: 4
156
103
  summary: An event loop.
metadata.gz.sig CHANGED
Binary file