io-event 1.2.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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