io-event 1.7.4 → 1.7.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9761a366e91db284c56c77d2ec19db8b6d652d98f2fe39750108e685ff3c2b12
4
- data.tar.gz: 3d9d1fab942ec257073a317cecefb06248952e5e7c2518f0298d60b476ec1905
3
+ metadata.gz: ab271a1de3eb0f5d21b0b7c92cafcc226e5ca218e7b87ebaa80f93a54fd7b945
4
+ data.tar.gz: fa6227d0b4218b277903fb1c6f1889e51bcfb7e8d8f83086edc30a336a065eff
5
5
  SHA512:
6
- metadata.gz: 4e7c4790ad17bb655136e1ce25b006c4bc97906b7b46eac2abddddd100eb882d19569da27ae195455b457ea3f243bd8ebe6475a9d0255f1bd11bf33cf66edd15
7
- data.tar.gz: 00b9bc9c8ad79828b50b67fdf9fb9a9d25664fb5d9b8c77eb70aa708ee719974b07ba6ed4c3098d76c9b33132061070d36bc39c2c5c707448a9400cef0f2a4ed
6
+ metadata.gz: d5afc83ef6364791d86b14bdf130d68fda3a7a1c720349dce6bb2140a2ee9f69015e542dbf5c724a0daa338e5f13c87f1dee566e8a36a1b0847ce5e86e1fa92b
7
+ data.tar.gz: 7d09237e5141123bf251fe686e25fca7d8391a7c25fe331e8882d7e43bf87ff9c5e89bc5c5f90d0731705b923eade10c4667344ad06d04a08544bb9a944baa28
checksums.yaml.gz.sig CHANGED
Binary file
@@ -519,7 +519,7 @@ VALUE IO_Event_Selector_EPoll_process_wait(VALUE self, VALUE fiber, VALUE _pid,
519
519
 
520
520
  RB_OBJ_WRITTEN(self, Qundef, fiber);
521
521
 
522
- int result = IO_Event_Selector_EPoll_Waiting_register(selector, 0, descriptor, &waiting);
522
+ int result = IO_Event_Selector_EPoll_Waiting_register(selector, _pid, descriptor, &waiting);
523
523
 
524
524
  if (result == -1) {
525
525
  close(descriptor);
@@ -6,9 +6,16 @@
6
6
  require_relative "../support"
7
7
 
8
8
  module IO::Event
9
+ # @namespace
9
10
  module Debug
10
11
  # Enforces the selector interface and delegates operations to a wrapped selector instance.
12
+ #
13
+ # You can enable this in the default selector by setting the `IO_EVENT_DEBUG_SELECTOR` environment variable. In addition, you can log all selector operations to a file by setting the `IO_EVENT_DEBUG_SELECTOR_LOG` environment variable. This is useful for debugging and understanding the behavior of the event loop.
11
14
  class Selector
15
+ # Wrap the given selector with debugging.
16
+ #
17
+ # @parameter selector [Selector] The selector to wrap.
18
+ # @parameter env [Hash] The environment to read configuration from.
12
19
  def self.wrap(selector, env = ENV)
13
20
  log = nil
14
21
 
@@ -19,6 +26,10 @@ module IO::Event
19
26
  return self.new(selector, log: log)
20
27
  end
21
28
 
29
+ # Initialize the debug selector with the given selector and optional log.
30
+ #
31
+ # @parameter selector [Selector] The selector to wrap.
32
+ # @parameter log [IO] The log to write debug messages to.
22
33
  def initialize(selector, log: nil)
23
34
  @selector = selector
24
35
 
@@ -33,14 +44,23 @@ module IO::Event
33
44
  @log = log
34
45
  end
35
46
 
47
+ # The idle duration of the underlying selector.
48
+ #
49
+ # @returns [Numeric] The idle duration.
36
50
  def idle_duration
37
51
  @selector.idle_duration
38
52
  end
39
53
 
54
+ # The current time.
55
+ #
56
+ # @returns [Numeric] The current time.
40
57
  def now
41
58
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
42
59
  end
43
60
 
61
+ # Log the given message.
62
+ #
63
+ # @asynchronous Will block the calling fiber and the entire event loop.
44
64
  def log(message)
45
65
  return unless @log
46
66
 
@@ -49,10 +69,12 @@ module IO::Event
49
69
  end
50
70
  end
51
71
 
72
+ # Wakeup the the selector.
52
73
  def wakeup
53
74
  @selector.wakeup
54
75
  end
55
76
 
77
+ # Close the selector.
56
78
  def close
57
79
  log("Closing selector")
58
80
 
@@ -64,60 +86,78 @@ module IO::Event
64
86
  @selector = nil
65
87
  end
66
88
 
67
- # Transfer from the calling fiber to the event loop.
89
+ # Transfer from the calling fiber to the selector.
68
90
  def transfer
69
91
  log("Transfering to event loop")
70
92
  @selector.transfer
71
93
  end
72
94
 
95
+ # Resume the given fiber with the given arguments.
73
96
  def resume(*arguments)
74
97
  log("Resuming fiber with #{arguments.inspect}")
75
98
  @selector.resume(*arguments)
76
99
  end
77
100
 
101
+ # Yield to the selector.
78
102
  def yield
79
103
  log("Yielding to event loop")
80
104
  @selector.yield
81
105
  end
82
106
 
107
+ # Push the given fiber to the selector ready list, such that it will be resumed on the next call to {select}.
108
+ #
109
+ # @parameter fiber [Fiber] The fiber that is ready.
83
110
  def push(fiber)
84
111
  log("Pushing fiber #{fiber.inspect} to ready list")
85
112
  @selector.push(fiber)
86
113
  end
87
114
 
115
+ # Raise the given exception on the given fiber.
116
+ #
117
+ # @parameter fiber [Fiber] The fiber to raise the exception on.
118
+ # @parameter arguments [Array] The arguments to use when raising the exception.
88
119
  def raise(fiber, *arguments)
89
120
  log("Raising exception on fiber #{fiber.inspect} with #{arguments.inspect}")
90
121
  @selector.raise(fiber, *arguments)
91
122
  end
92
123
 
124
+ # Check if the selector is ready.
125
+ #
126
+ # @returns [Boolean] Whether the selector is ready.
93
127
  def ready?
94
128
  @selector.ready?
95
129
  end
96
130
 
131
+ # Wait for the given process, forwarded to the underlying selector.
97
132
  def process_wait(*arguments)
98
133
  log("Waiting for process with #{arguments.inspect}")
99
134
  @selector.process_wait(*arguments)
100
135
  end
101
136
 
137
+ # Wait for the given IO, forwarded to the underlying selector.
102
138
  def io_wait(fiber, io, events)
103
139
  log("Waiting for IO #{io.inspect} for events #{events.inspect}")
104
140
  @selector.io_wait(fiber, io, events)
105
141
  end
106
142
 
143
+ # Read from the given IO, forwarded to the underlying selector.
107
144
  def io_read(fiber, io, buffer, length, offset = 0)
108
145
  log("Reading from IO #{io.inspect} with buffer #{buffer}; length #{length} offset #{offset}")
109
146
  @selector.io_read(fiber, io, buffer, length, offset)
110
147
  end
111
148
 
149
+ # Write to the given IO, forwarded to the underlying selector.
112
150
  def io_write(fiber, io, buffer, length, offset = 0)
113
151
  log("Writing to IO #{io.inspect} with buffer #{buffer}; length #{length} offset #{offset}")
114
152
  @selector.io_write(fiber, io, buffer, length, offset)
115
153
  end
116
154
 
155
+ # Forward the given method to the underlying selector.
117
156
  def respond_to?(name, include_private = false)
118
157
  @selector.respond_to?(name, include_private)
119
158
  end
120
159
 
160
+ # Select for the given duration, forwarded to the underlying selector.
121
161
  def select(duration = nil)
122
162
  log("Selecting for #{duration.inspect}")
123
163
  unless Fiber.current == @selector.loop
@@ -10,6 +10,7 @@ class IO
10
10
  # of its contents to determine priority.
11
11
  # See <https://en.wikipedia.org/wiki/Binary_heap> for explanations of the main methods.
12
12
  class PriorityHeap
13
+ # Initializes the heap.
13
14
  def initialize
14
15
  # The heap is represented with an array containing a binary tree. See
15
16
  # https://en.wikipedia.org/wiki/Binary_heap#Heap_implementation for how this array
@@ -17,18 +18,19 @@ class IO
17
18
  @contents = []
18
19
  end
19
20
 
20
- # Returns the earliest timer or nil if the heap is empty.
21
+ # @returns [Object | Nil] the smallest element in the heap without removing it, or nil if the heap is empty.
21
22
  def peek
22
23
  @contents[0]
23
24
  end
24
25
 
25
- # Returns the number of elements in the heap
26
+ # @returns [Integer] the number of elements in the heap.
26
27
  def size
27
28
  @contents.size
28
29
  end
29
30
 
30
- # Returns the earliest timer if the heap is non-empty and removes it from the heap.
31
- # Returns nil if the heap is empty. (and doesn't change the heap in that case)
31
+ # Removes and returns the smallest element in the heap, or nil if the heap is empty.
32
+ #
33
+ # @returns [Object | Nil] The smallest element in the heap, or nil if the heap is empty.
32
34
  def pop
33
35
  # If the heap is empty:
34
36
  if @contents.empty?
@@ -57,7 +59,9 @@ class IO
57
59
  return value
58
60
  end
59
61
 
60
- # Inserts a new timer into the heap, then rearranges elements until the heap invariant is true again.
62
+ # Add a new element to the heap, then rearrange elements until the heap invariant is true again.
63
+ #
64
+ # @parameter element [Object] The element to add to the heap.
61
65
  def push(element)
62
66
  # Insert the item at the end of the heap:
63
67
  @contents.push(element)
@@ -75,10 +79,9 @@ class IO
75
79
  @contents = []
76
80
  end
77
81
 
78
- # Validate the heap invariant. Every element except the root must not be smaller than
79
- # its parent element. Note that it MAY be equal.
82
+ # Validate the heap invariant. Every element except the root must not be smaller than its parent element. Note that it MAY be equal.
80
83
  def valid?
81
- # notice we skip index 0 on purpose, because it has no parent
84
+ # Notice we skip index 0 on purpose, because it has no parent
82
85
  (1..(@contents.size - 1)).all? { |e| @contents[e] >= @contents[(e - 1) / 2] }
83
86
  end
84
87
 
@@ -93,10 +96,7 @@ class IO
93
96
  parent_index = (index - 1) / 2 # watch out, integer division!
94
97
 
95
98
  while index > 0 && @contents[index] < @contents[parent_index]
96
- # if the node has a smaller value than its parent, swap these nodes
97
- # to uphold the minheap invariant and update the index of the 'current'
98
- # node. If the node is already at index 0, we can also stop because that
99
- # is the root of the heap.
99
+ # If the node has a smaller value than its parent, swap these nodes to uphold the minheap invariant and update the index of the 'current' node. If the node is already at index 0, we can also stop because that is the root of the heap.
100
100
  # swap(index, parent_index)
101
101
  @contents[index], @contents[parent_index] = @contents[parent_index], @contents[index]
102
102
 
@@ -114,8 +114,7 @@ class IO
114
114
  left_value = @contents[left_index]
115
115
 
116
116
  if left_value.nil?
117
- # This node has no children so it can't bubble down any further.
118
- # We're done here!
117
+ # This node has no children so it can't bubble down any further. We're done here!
119
118
  return
120
119
  end
121
120
 
@@ -7,6 +7,10 @@ require "io/nonblock"
7
7
 
8
8
  module IO::Event
9
9
  module Selector
10
+ # Execute the given block in non-blocking mode.
11
+ #
12
+ # @parameter io [IO] The IO object to operate on.
13
+ # @yields {...} The block to execute.
10
14
  def self.nonblock(io, &block)
11
15
  io.nonblock(&block)
12
16
  rescue Errno::EBADF
@@ -9,7 +9,9 @@ require_relative "../support"
9
9
 
10
10
  module IO::Event
11
11
  module Selector
12
+ # A pure-Ruby implementation of the event selector.
12
13
  class Select
14
+ # Initialize the selector with the given event loop fiber.
13
15
  def initialize(loop)
14
16
  @loop = loop
15
17
 
@@ -23,12 +25,13 @@ module IO::Event
23
25
  @idle_duration = 0.0
24
26
  end
25
27
 
28
+ # @attribute [Fiber] The event loop fiber.
26
29
  attr :loop
27
30
 
28
- # This is the amount of time the event loop was idle during the last select call.
31
+ # @attribute [Float] This is the amount of time the event loop was idle during the last select call.
29
32
  attr :idle_duration
30
33
 
31
- # If the event loop is currently sleeping, wake it up.
34
+ # Wake up the event loop if it is currently sleeping.
32
35
  def wakeup
33
36
  if @blocked
34
37
  @interrupt.signal
@@ -39,6 +42,7 @@ module IO::Event
39
42
  return false
40
43
  end
41
44
 
45
+ # Close the selector and release any resources.
42
46
  def close
43
47
  @interrupt.close
44
48
 
@@ -100,6 +104,7 @@ module IO::Event
100
104
  optional.nullify
101
105
  end
102
106
 
107
+ # @returns [Boolean] Whether the ready list is not empty, i.e. there are fibers ready to be resumed.
103
108
  def ready?
104
109
  !@ready.empty?
105
110
  end
@@ -144,6 +149,11 @@ module IO::Event
144
149
  end
145
150
  end
146
151
 
152
+ # Wait for the given IO to become readable or writable.
153
+ #
154
+ # @parameter fiber [Fiber] The fiber that is waiting.
155
+ # @parameter io [IO] The IO object to wait on.
156
+ # @parameter events [Integer] The events to wait for.
147
157
  def io_wait(fiber, io, events)
148
158
  waiter = @waiting[io] = Waiter.new(fiber, events, @waiting[io])
149
159
 
@@ -152,6 +162,11 @@ module IO::Event
152
162
  waiter&.invalidate
153
163
  end
154
164
 
165
+ # Wait for multiple IO objects to become readable or writable.
166
+ #
167
+ # @parameter readable [Array(IO)] The list of IO objects to wait for readability.
168
+ # @parameter writable [Array(IO)] The list of IO objects to wait for writability.
169
+ # @parameter priority [Array(IO)] The list of IO objects to wait for priority events.
155
170
  def io_select(readable, writable, priority, timeout)
156
171
  Thread.new do
157
172
  IO.select(readable, writable, priority, timeout)
@@ -161,7 +176,8 @@ module IO::Event
161
176
  EAGAIN = -Errno::EAGAIN::Errno
162
177
  EWOULDBLOCK = -Errno::EWOULDBLOCK::Errno
163
178
 
164
- def again?(errno)
179
+ # Whether the given error code indicates that the operation should be retried.
180
+ protected def again?(errno)
165
181
  errno == EAGAIN or errno == EWOULDBLOCK
166
182
  end
167
183
 
@@ -8,7 +8,12 @@ require_relative "debug/selector"
8
8
  require_relative "support"
9
9
 
10
10
  module IO::Event
11
+ # @namespace
11
12
  module Selector
13
+ # The default selector implementation, which is chosen based on the environment and available implementations.
14
+ #
15
+ # @parameter env [Hash] The environment to read configuration from.
16
+ # @returns [Class] The default selector implementation.
12
17
  def self.default(env = ENV)
13
18
  if name = env["IO_EVENT_SELECTOR"]&.to_sym
14
19
  return const_get(name)
@@ -25,6 +30,11 @@ module IO::Event
25
30
  end
26
31
  end
27
32
 
33
+ # Create a new selector instance, according to the best available implementation.
34
+ #
35
+ # @parameter loop [Fiber] The event loop fiber.
36
+ # @parameter env [Hash] The environment to read configuration from.
37
+ # @returns [Selector] The new selector instance.
28
38
  def self.new(loop, env = ENV)
29
39
  selector = default(env).new(loop)
30
40
 
@@ -5,16 +5,26 @@
5
5
 
6
6
  class IO
7
7
  module Event
8
+ # Helper methods for detecting support for various features.
8
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.
9
13
  def self.buffer?
10
14
  IO.const_defined?(:Buffer)
11
15
  end
12
16
 
17
+ # The basic fiber scheduler was introduced along side the IO::Buffer class.
18
+ #
19
+ # @returns [Boolean] Whether the IO::Buffer class is available.
20
+ #
13
21
  # To be removed on 31 Mar 2025.
14
22
  def self.fiber_scheduler_v1?
15
23
  IO.const_defined?(:Buffer)
16
24
  end
17
25
 
26
+ # More advanced read/write methods and blocking controls were introduced in Ruby 3.2.
27
+ #
18
28
  # To be removed on 31 Mar 2026.
19
29
  def self.fiber_scheduler_v2?
20
30
  # Some interface changes were back-ported incorrectly:
@@ -26,6 +36,8 @@ class IO
26
36
  IO.const_defined?(:Buffer) and Fiber.respond_to?(:blocking) and IO::Buffer.instance_method(:read).arity == -1
27
37
  end
28
38
 
39
+ # Updated inferfaces for read/write and IO::Buffer were introduced in Ruby 3.3, including pread/pwrite.
40
+ #
29
41
  # To become the default 31 Mar 2026.
30
42
  def self.fiber_scheduler_v3?
31
43
  if fiber_scheduler_v2?
@@ -7,42 +7,64 @@ require_relative "priority_heap"
7
7
 
8
8
  class IO
9
9
  module Event
10
+ # An efficient sorted set of timers.
10
11
  class Timers
12
+ # A handle to a scheduled timer.
11
13
  class Handle
14
+ # Initialize the handle with the given time and block.
15
+ #
16
+ # @parameter time [Float] The time at which the block should be called.
17
+ # @parameter block [Proc] The block to call.
12
18
  def initialize(time, block)
13
19
  @time = time
14
20
  @block = block
15
21
  end
16
22
 
23
+ # @attribute [Float] The time at which the block should be called.
24
+ attr :time
25
+
26
+ # @attribute [Proc | Nil] The block to call when the timer fires.
27
+ attr :block
28
+
29
+ # Compare the handle with another handle.
30
+ #
31
+ # @parameter other [Handle] The other handle to compare with.
32
+ # @returns [Boolean] Whether the handle is less than the other handle.
17
33
  def < other
18
34
  @time < other.time
19
35
  end
20
36
 
37
+ # Compare the handle with another handle.
38
+ #
39
+ # @parameter other [Handle] The other handle to compare with.
40
+ # @returns [Boolean] Whether the handle is greater than the other handle.
21
41
  def > other
22
42
  @time > other.time
23
43
  end
24
44
 
25
- attr :time
26
- attr :block
27
-
45
+ # Invoke the block.
28
46
  def call(...)
29
47
  @block.call(...)
30
48
  end
31
49
 
50
+ # Cancel the timer.
32
51
  def cancel!
33
52
  @block = nil
34
53
  end
35
54
 
55
+ # @returns [Boolean] Whether the timer has been cancelled.
36
56
  def cancelled?
37
57
  @block.nil?
38
58
  end
39
59
  end
40
60
 
61
+ # Initialize the timers.
41
62
  def initialize
42
63
  @heap = PriorityHeap.new
43
64
  @scheduled = []
44
65
  end
45
66
 
67
+ # @returns [Integer] The number of timers in the heap.
46
68
  def size
47
69
  flush!
48
70
 
@@ -50,7 +72,9 @@ class IO
50
72
  end
51
73
 
52
74
  # Schedule a block to be called at a specific time in the future.
75
+ #
53
76
  # @parameter time [Float] The time at which the block should be called, relative to {#now}.
77
+ # @parameter block [Proc] The block to call.
54
78
  def schedule(time, block)
55
79
  handle = Handle.new(time, block)
56
80
 
@@ -60,11 +84,18 @@ class IO
60
84
  end
61
85
 
62
86
  # Schedule a block to be called after a specific time offset, relative to the current time as returned by {#now}.
87
+ #
63
88
  # @parameter offset [#to_f] The time offset from the current time at which the block should be called.
89
+ # @yields {|now| ...} When the timer fires.
64
90
  def after(offset, &block)
65
91
  schedule(self.now + offset.to_f, block)
66
92
  end
67
93
 
94
+
95
+ # Compute the time interval until the next timer fires.
96
+ #
97
+ # @parameter now [Float] The current time.
98
+ # @returns [Float | Nil] The time interval until the next timer fires, if any.
68
99
  def wait_interval(now = self.now)
69
100
  flush!
70
101
 
@@ -77,10 +108,14 @@ class IO
77
108
  end
78
109
  end
79
110
 
111
+ # @returns [Float] The current time.
80
112
  def now
81
113
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
82
114
  end
83
115
 
116
+ # Fire all timers that are ready to fire.
117
+ #
118
+ # @parameter now [Float] The current time.
84
119
  def fire(now = self.now)
85
120
  # Flush scheduled timers into the heap:
86
121
  flush!
@@ -101,6 +136,9 @@ class IO
101
136
  end
102
137
  end
103
138
 
139
+ # Flush all scheduled timers into the heap.
140
+ #
141
+ # This is a small optimization which assumes that most timers (timeouts) will be cancelled.
104
142
  protected def flush!
105
143
  while handle = @scheduled.pop
106
144
  @heap.push(handle) unless handle.cancelled?
@@ -3,8 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
+ # @namespace
6
7
  class IO
8
+ # @namespace
7
9
  module Event
8
- VERSION = "1.7.4"
10
+ VERSION = "1.7.5"
9
11
  end
10
12
  end
data/readme.md CHANGED
@@ -10,7 +10,17 @@ The initial proof-of-concept [Async](https://github.com/socketry/async) was buil
10
10
 
11
11
  ## Usage
12
12
 
13
- Please see the [project documentation](https://socketry.github.io/io-event/).
13
+ Please see the [project documentation](https://socketry.github.io/io-event/) for more details.
14
+
15
+ - [Getting Started](https://socketry.github.io/io-event/guides/getting-started/index) - This guide explains how to use `io-event` for non-blocking IO.
16
+
17
+ ## Releases
18
+
19
+ Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
+
21
+ ### v1.7.5
22
+
23
+ - Fix `process_wait` race condition on EPoll that could cause a hang.
14
24
 
15
25
  ## Contributing
16
26
 
data/releases.md ADDED
@@ -0,0 +1,5 @@
1
+ # Releases
2
+
3
+ ## v1.7.5
4
+
5
+ - Fix `process_wait` race condition on EPoll that could cause a hang.
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.7.4
4
+ version: 1.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -45,7 +45,7 @@ cert_chain:
45
45
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
46
46
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
47
47
  -----END CERTIFICATE-----
48
- date: 2024-11-24 00:00:00.000000000 Z
48
+ date: 2024-12-15 00:00:00.000000000 Z
49
49
  dependencies: []
50
50
  description:
51
51
  email:
@@ -83,6 +83,7 @@ files:
83
83
  - lib/io/event/version.rb
84
84
  - license.md
85
85
  - readme.md
86
+ - releases.md
86
87
  homepage: https://github.com/socketry/io-event
87
88
  licenses:
88
89
  - MIT
metadata.gz.sig CHANGED
Binary file