fiber_scheduler 0.0.1 → 0.12.0

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: fd5761664b48ce8937367c38ae5424c2aad1ffcbac56fac2c002de020f08b6fc
4
- data.tar.gz: 426a54cbbd96a1b5eb54794b2a6b295be2af98413cbfb7978780ca57ff8347f7
3
+ metadata.gz: e6bff2da15e7d0b5295c4e97e86c55377ce126173aeeb8ef81cade98f2a3bd1b
4
+ data.tar.gz: aac48ee81381541a770722eb369c7d8f90415caba4f796e7c830b35cb53fd10b
5
5
  SHA512:
6
- metadata.gz: 9cdf31791771ecd2fec6fee05a7849954e71a2d6596ea429ec99300a558bd8c129a8e402e7bcae6c1c093765ac9738b4944e70de26a423ae62c440f4c9d10e18
7
- data.tar.gz: 4cf8d6bd02b7a51ef03e902da0bb25bbd5fae16ebf82c1b5aa7cf68d84b723b458be1b9106258fff527e17704ffba3850a0b377b735bf21c0638b62c7930d20d
6
+ metadata.gz: 5b83d8d5a5f1a7503de979bdd7f60025b9ada9d63d98166a61516c3da82ec6b1e1a385579acc5c05764cc0ac838671757e53ee980180c5bf38625bc2b7f157ea
7
+ data.tar.gz: 1c2026341f79c0d98131f04c8d51b3be410b2fef6d732e8181a2024e149f08c02bebd5a9bc8272956396dd182451395c84a36b088f811f17b7deb96eb7983830
@@ -0,0 +1,96 @@
1
+ class FiberScheduler
2
+ module Compatibility
3
+ Close = Class.new(RuntimeError)
4
+
5
+ def fiber(*args, **opts, &block)
6
+ return super unless Compatibility.internal?
7
+
8
+ # This is `Fiber.schedule` call inside `FiberScheduler { ... }` block.
9
+ type = args.first
10
+ case type
11
+ when :blocking
12
+ Fiber.new(blocking: true) {
13
+ Compatibility.set_internal!
14
+ yield
15
+ }.tap(&:resume)
16
+
17
+ when :waiting
18
+ parent = Fiber.current
19
+ finished = false # prevents races
20
+ blocking = false # prevents #unblock-ing a fiber that never blocked
21
+
22
+ # Don't pass *args and **opts to an unknown fiber scheduler class.
23
+ fiber = super() do
24
+ Compatibility.set_internal!
25
+ yield
26
+ ensure
27
+ finished = true
28
+ unblock(nil, parent) if blocking
29
+ end
30
+
31
+ unless finished
32
+ blocking = true
33
+ block(nil, nil)
34
+ end
35
+
36
+ fiber
37
+
38
+ when :fleeting
39
+ # Transfer to current fiber some time after a fleeting fiber yields.
40
+ unblock(nil, Fiber.current)
41
+ # Alternative to #unblock: Fiber.scheduler.push(Fiber.current)
42
+
43
+ fiber = Fiber.new(blocking: false) do
44
+ Compatibility.set_internal!
45
+ yield
46
+ rescue Close
47
+ # Fiber scheduler is closing.
48
+ ensure
49
+ _fleeting.delete(Fiber.current)
50
+ end
51
+ _fleeting[fiber] = nil
52
+ fiber.tap(&:transfer)
53
+
54
+ when nil
55
+ # Don't pass *args and **opts to an unknown fiber scheduler class.
56
+ super() do
57
+ Compatibility.set_internal!
58
+ yield
59
+ end
60
+
61
+ else
62
+ raise "Unknown type"
63
+ end
64
+ end
65
+
66
+ # #close and #_fleeting handle a complexity in Async::Scheduler#close, more
67
+ # specifically this line:
68
+ # https://github.com/socketry/async/blob/456df488d801572821eaf5ec2fda10e3b9744a5f/lib/async/scheduler.rb#L55
69
+ def close
70
+ super
71
+ rescue
72
+ if _fleeting.empty?
73
+ Kernel.raise
74
+ else
75
+ # #dup is used because #_fleeting is modified during iteration.
76
+ _fleeting.dup.each do |fiber, _|
77
+ fiber.raise(Close)
78
+ end
79
+
80
+ super # retry
81
+ end
82
+ end
83
+
84
+ def _fleeting
85
+ @_fleeting ||= {}
86
+ end
87
+
88
+ def self.set_internal!
89
+ Thread.current[:_fiber_scheduler] = true # Sets a FIBER local var!
90
+ end
91
+
92
+ def self.internal?
93
+ Thread.current[:_fiber_scheduler]
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,230 @@
1
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ # The code in this class has been taken from io-event gem. It has been cleaned
22
+ # up and appropriated for use in this gem.
23
+
24
+ class FiberScheduler
25
+ class Selector
26
+ EAGAIN = Errno::EAGAIN::Errno
27
+
28
+ class Waiter
29
+ def initialize(fiber, events, tail)
30
+ @fiber = fiber
31
+ @events = events
32
+ @tail = tail
33
+ end
34
+
35
+ def alive?
36
+ @fiber&.alive?
37
+ end
38
+
39
+ def transfer(events)
40
+ if (fiber = @fiber)
41
+ @fiber = nil
42
+
43
+ fiber.transfer(events & @events) if fiber.alive?
44
+ end
45
+
46
+ @tail&.transfer(events)
47
+ end
48
+
49
+ def invalidate
50
+ @fiber = nil
51
+ end
52
+
53
+ def each(&block)
54
+ if (fiber = @fiber)
55
+ yield fiber, @events
56
+ end
57
+
58
+ @tail&.each(&block)
59
+ end
60
+ end
61
+
62
+ def initialize(fiber)
63
+ @fiber = fiber
64
+
65
+ @waiting = {}.compare_by_identity
66
+ @ready = []
67
+ end
68
+
69
+ def close
70
+ @fiber = nil
71
+ @waiting = nil
72
+ @ready = nil
73
+ end
74
+
75
+ def transfer
76
+ @fiber.transfer
77
+ end
78
+
79
+ def push(fiber)
80
+ @ready.push(fiber)
81
+ end
82
+
83
+ def io_wait(fiber, io, events)
84
+ waiter = @waiting[io] = Waiter.new(fiber, events, @waiting[io])
85
+
86
+ @fiber.transfer
87
+ ensure
88
+ waiter&.invalidate
89
+ end
90
+
91
+ def io_read(fiber, io, buffer, length)
92
+ offset = 0
93
+
94
+ loop do
95
+ maximum_size = buffer.size - offset
96
+
97
+ result = Fiber.new(blocking: true) {
98
+ io.read_nonblock(maximum_size, exception: false)
99
+ }.resume
100
+
101
+ case result
102
+ when :wait_readable
103
+ if length > 0
104
+ io_wait(fiber, io, IO::READABLE)
105
+ else
106
+ return -EAGAIN
107
+ end
108
+ when :wait_writable
109
+ if length > 0
110
+ io_wait(fiber, io, IO::WRITABLE)
111
+ else
112
+ return -EAGAIN
113
+ end
114
+ when nil
115
+ break
116
+ else
117
+ buffer.set_string(result, offset)
118
+
119
+ size = result.bytesize
120
+ offset += size
121
+ break if size >= length
122
+ length -= size
123
+ end
124
+ end
125
+
126
+ offset
127
+ end
128
+
129
+ def io_write(fiber, io, buffer, length)
130
+ offset = 0
131
+
132
+ loop do
133
+ maximum_size = buffer.size - offset
134
+
135
+ chunk = buffer.get_string(offset, maximum_size)
136
+ result = Fiber.new(blocking: true) {
137
+ io.write_nonblock(chunk, exception: false)
138
+ }.resume
139
+
140
+ case result
141
+ when :wait_readable
142
+ if length > 0
143
+ io_wait(fiber, io, IO::READABLE)
144
+ else
145
+ return -EAGAIN
146
+ end
147
+ when :wait_writable
148
+ if length > 0
149
+ io_wait(fiber, io, IO::WRITABLE)
150
+ else
151
+ return -EAGAIN
152
+ end
153
+ else
154
+ offset += result
155
+ break if result >= length
156
+ length -= result
157
+ end
158
+ end
159
+
160
+ offset
161
+ end
162
+
163
+ def process_wait(fiber, pid, flags)
164
+ reader, writer = IO.pipe
165
+
166
+ thread = Thread.new do
167
+ Process::Status.wait(pid, flags)
168
+ ensure
169
+ writer.close
170
+ end
171
+
172
+ io_wait(fiber, reader, IO::READABLE)
173
+
174
+ thread.value
175
+ ensure
176
+ reader.close
177
+ writer.close
178
+ thread&.kill
179
+ end
180
+
181
+ def select(duration = nil)
182
+ if @ready.any?
183
+ # If we have popped items from the ready list, they may influence the
184
+ # duration calculation, so we don't delay the event loop:
185
+ duration = 0
186
+
187
+ count = @ready.size
188
+ count.times do
189
+ fiber = @ready.shift
190
+ fiber.transfer if fiber.alive?
191
+ end
192
+ end
193
+
194
+ readable = []
195
+ writable = []
196
+
197
+ @waiting.each do |io, waiter|
198
+ waiter.each do |fiber, events|
199
+ if (events & IO::READABLE) > 0
200
+ readable << io
201
+ end
202
+
203
+ if (events & IO::WRITABLE) > 0
204
+ writable << io
205
+ end
206
+ end
207
+ end
208
+
209
+ duration = 0 if @ready.any?
210
+ readable, writable, _ = IO.select(readable, writable, nil, duration)
211
+
212
+ ready = Hash.new(0)
213
+
214
+ readable&.each do |io|
215
+ ready[io] |= IO::READABLE
216
+ end
217
+
218
+ writable&.each do |io|
219
+ ready[io] |= IO::WRITABLE
220
+ end
221
+
222
+ ready.each do |io, events|
223
+ waiter = @waiting.delete(io)
224
+ waiter.transfer(events)
225
+ end
226
+
227
+ ready.size
228
+ end
229
+ end
230
+ end
@@ -1,12 +1,18 @@
1
1
  class FiberScheduler
2
- class Trigger
2
+ Error = Class.new(RuntimeError)
3
+
4
+ class Timeout
3
5
  include Comparable
4
6
 
7
+ Error = Class.new(FiberScheduler::Error)
8
+
5
9
  attr_reader :time
6
10
 
7
- def initialize(duration, &block)
11
+ def initialize(duration, fiber, method, *args)
8
12
  @time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
9
- @block = block
13
+ @fiber = fiber
14
+ @method = method
15
+ @args = args
10
16
 
11
17
  @disabled = nil
12
18
  end
@@ -18,7 +24,9 @@ class FiberScheduler
18
24
  end
19
25
 
20
26
  def call
21
- @block.call
27
+ return unless @fiber.alive?
28
+
29
+ @fiber.public_send(@method, *@args)
22
30
  end
23
31
 
24
32
  def interval
@@ -0,0 +1,79 @@
1
+ require_relative "timeout"
2
+
3
+ class FiberScheduler
4
+ class Timeouts
5
+ attr_reader :timeouts
6
+
7
+ def initialize
8
+ # Array is sorted by Timeout#time
9
+ @timeouts = []
10
+ end
11
+
12
+ def call
13
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
+
15
+ while @timeouts.any? && @timeouts.first.time <= now
16
+ timeout = @timeouts.shift
17
+ unless timeout.disabled?
18
+ timeout.call
19
+ end
20
+ end
21
+ end
22
+
23
+ def timeout(duration, *args, method: :raise, fiber: Fiber.current, &block)
24
+ timeout = Timeout.new(duration, fiber, method, *args)
25
+
26
+ if @timeouts.empty?
27
+ @timeouts << timeout
28
+ else
29
+ # binary search
30
+ min = 0
31
+ max = @timeouts.size - 1
32
+ while min <= max
33
+ index = (min + max) / 2
34
+ t = @timeouts[index]
35
+
36
+ if t > timeout
37
+ if index.zero? || @timeouts[index - 1] <= timeout
38
+ # found it
39
+ break
40
+ else
41
+ # @timeouts[index - 1] > timeout
42
+ max = index - 1
43
+ end
44
+ else
45
+ # t <= timeout
46
+ index += 1
47
+ min = index
48
+ end
49
+ end
50
+
51
+ @timeouts.insert(index, timeout)
52
+ end
53
+
54
+ begin
55
+ block.call
56
+ ensure
57
+ # Timeout is disabled if the block finishes earlier.
58
+ timeout.disable
59
+ end
60
+ end
61
+
62
+ def interval
63
+ # Prune disabled timeouts
64
+ while @timeouts.first&.disabled?
65
+ @timeouts.shift
66
+ end
67
+
68
+ return if @timeouts.empty?
69
+
70
+ interval = @timeouts.first.interval
71
+
72
+ interval >= 0 ? interval : 0
73
+ end
74
+
75
+ def inspect
76
+ @timeouts.inspect
77
+ end
78
+ end
79
+ end
@@ -1,3 +1,3 @@
1
1
  class FiberScheduler
2
- VERSION = "0.0.1".freeze
2
+ VERSION = "0.12.0".freeze
3
3
  end
@@ -1,33 +1,111 @@
1
- require "io/event"
2
1
  require "resolv"
3
- require_relative "fiber_scheduler/triggers"
2
+ require_relative "fiber_scheduler/compatibility"
3
+ require_relative "fiber_scheduler/selector"
4
+ require_relative "fiber_scheduler/timeouts"
5
+
6
+ begin
7
+ # Use io/event selector if available
8
+ require "io/event"
9
+ rescue LoadError
10
+ end
4
11
 
5
12
  module Kernel
6
- def FiberScheduler(&block)
13
+ def FiberScheduler(type = nil, &block)
7
14
  if Fiber.scheduler.nil?
8
- scheduler = FiberScheduler.new
9
- Fiber.set_scheduler(scheduler)
15
+ Fiber.set_scheduler(FiberScheduler.new)
10
16
 
11
17
  begin
12
18
  yield
13
- scheduler.close
14
19
  ensure
15
20
  Fiber.set_scheduler(nil)
16
21
  end
22
+
17
23
  else
18
- # Fiber.scheduler already set, just schedule a task.
19
- Fiber.schedule(&block)
24
+ scheduler = Fiber.scheduler
25
+ # Fiber.scheduler already set, just schedule a fiber.
26
+ if scheduler.is_a?(FiberScheduler)
27
+ # The default waiting is 'true' as that is the most intuitive behavior
28
+ # for a nested FiberScheduler call.
29
+ Fiber.schedule(type, &block)
30
+
31
+ # Unknown fiber scheduler class; can't just pass options to
32
+ # Fiber.schedule, handle each option separately.
33
+ else
34
+ scheduler.singleton_class.prepend(FiberScheduler::Compatibility)
35
+
36
+ case type
37
+ when :blocking
38
+ fiber = Fiber.new(blocking: true) do
39
+ FiberScheduler::Compatibility.set_internal!
40
+ yield
41
+ end
42
+ fiber.tap(&:resume)
43
+
44
+ when :waiting
45
+ parent = Fiber.current
46
+ finished = false # prevents races
47
+ blocking = false # prevents #unblock-ing a fiber that never blocked
48
+
49
+ fiber = Fiber.schedule do
50
+ FiberScheduler::Compatibility.set_internal!
51
+ yield
52
+ ensure
53
+ finished = true
54
+ scheduler.unblock(nil, parent) if blocking
55
+ end
56
+
57
+ if Fiber.blocking?
58
+ # In a blocking fiber, which is potentially also a loop fiber so
59
+ # there's nothing we can transfer to. Run other fibers (or just
60
+ # block) until waiting fiber finishes.
61
+ until finished
62
+ scheduler.run_once
63
+ end
64
+ elsif !finished
65
+ blocking = true
66
+ scheduler.block(nil, nil)
67
+ end
68
+
69
+ fiber
70
+
71
+ when :fleeting
72
+ scheduler.unblock(nil, Fiber.current)
73
+
74
+ fiber = Fiber.new(blocking: false) do
75
+ FiberScheduler::Compatibility.set_internal!
76
+ yield
77
+ rescue FiberScheduler::Compatibility::Close
78
+ # Fiber scheduler is closing.
79
+ ensure
80
+ scheduler._fleeting.delete(Fiber.current)
81
+ end
82
+ scheduler._fleeting[fiber] = nil
83
+ fiber.tap(&:transfer)
84
+
85
+ when nil
86
+ Fiber.schedule do
87
+ FiberScheduler::Compatibility.set_internal!
88
+ yield
89
+ end
90
+
91
+ else
92
+ raise "Unknown type"
93
+ end
94
+ end
20
95
  end
21
96
  end
22
97
  end
23
98
 
24
99
  class FiberScheduler
25
- TimeoutError = Class.new(RuntimeError)
26
- IOWaitTimeout = Class.new(TimeoutError)
27
-
28
100
  def initialize
29
- @selector = IO::Event::Selector.new(Fiber.current)
30
- @triggers = Triggers.new
101
+ @fiber = Fiber.current
102
+ @selector =
103
+ if defined?(IO::Event)
104
+ IO::Event::Selector.new(@fiber)
105
+ else
106
+ Selector.new(@fiber)
107
+ end
108
+ @timeouts = Timeouts.new
31
109
 
32
110
  @count = 0
33
111
  @nested = []
@@ -35,14 +113,18 @@ class FiberScheduler
35
113
 
36
114
  def run
37
115
  while @count > 0
38
- if @nested.empty?
39
- @selector.select(@triggers.interval)
40
- @triggers.call
41
- else
42
- while @nested.any?
43
- fiber = @nested.pop
44
- fiber.transfer
45
- end
116
+ run_once
117
+ end
118
+ end
119
+
120
+ def run_once
121
+ if @nested.empty?
122
+ @selector.select(@timeouts.interval)
123
+ @timeouts.call
124
+ else
125
+ while @nested.any?
126
+ fiber = @nested.pop
127
+ fiber.transfer
46
128
  end
47
129
  end
48
130
  end
@@ -60,18 +142,11 @@ class FiberScheduler
60
142
  end
61
143
  end
62
144
 
63
- def block(blocker, timeout)
64
- return @selector.transfer unless timeout
65
-
66
- fiber = Fiber.current
67
- trigger = @triggers.add(timeout) do
68
- fiber.transfer if fiber.alive?
69
- end
145
+ def block(blocker, duration = nil)
146
+ return @selector.transfer unless duration
70
147
 
71
- begin
148
+ @timeouts.timeout(duration, method: :transfer) do
72
149
  @selector.transfer
73
- ensure
74
- trigger.disable
75
150
  end
76
151
  end
77
152
 
@@ -89,21 +164,11 @@ class FiberScheduler
89
164
  Resolv.getaddresses(hostname)
90
165
  end
91
166
 
92
- def io_wait(io, events, timeout = nil)
93
- fiber = Fiber.current
94
- return @selector.io_wait(fiber, io, events) unless timeout
95
-
96
- trigger = @triggers.raise_in(timeout, IOWaitTimeout)
97
- # trigger = @triggers.add(timeout) do
98
- # fiber.raise(IOWaitTimeout) if fiber.alive?
99
- # end
167
+ def io_wait(io, events, duration = nil)
168
+ return @selector.io_wait(Fiber.current, io, events) unless duration
100
169
 
101
- begin
102
- @selector.io_wait(fiber, io, events)
103
- rescue IOWaitTimeout
104
- false
105
- ensure
106
- trigger.disable
170
+ @timeouts.timeout(duration, method: :transfer) do
171
+ @selector.io_wait(Fiber.current, io, events)
107
172
  end
108
173
  end
109
174
 
@@ -119,32 +184,67 @@ class FiberScheduler
119
184
  @selector.process_wait(Fiber.current, pid, flags)
120
185
  end
121
186
 
122
- def timeout_after(duration, exception = TimeoutError, message = "timeout")
123
- fiber = Fiber.current
124
- trigger = @triggers.add(duration) do
125
- fiber.raise(exception, message) if fiber.alive?
126
- end
127
-
128
- begin
129
- yield duration
130
- ensure
131
- trigger.disable
132
- end
187
+ def timeout_after(duration, exception = Timeout::Error, message = "timeout", &block)
188
+ @timeouts.timeout(duration, exception, message, &block)
133
189
  end
134
190
 
135
- def fiber(&block)
136
- unless Fiber.blocking?
137
- # nested Fiber.schedule
138
- @nested << Fiber.current
139
- end
191
+ def fiber(type = nil, &block)
192
+ current = Fiber.current
140
193
 
141
- fiber = Fiber.new(blocking: false) do
142
- @count += 1
143
- block.call
144
- ensure
145
- @count -= 1
146
- end
194
+ case type
195
+ when :blocking
196
+ Fiber.new(blocking: true, &block).tap(&:resume)
197
+
198
+ when :waiting
199
+ finished = false # prevents races
200
+ fiber = Fiber.new(blocking: false) do
201
+ @count += 1
202
+ block.call
203
+ ensure
204
+ @count -= 1
205
+ finished = true
206
+ # Resume waiting parent fiber
207
+ current.transfer
208
+ end
209
+ fiber.transfer
210
+
211
+ # Current fiber is waiting until waiting fiber finishes.
212
+ if current == @fiber
213
+ # In a top-level fiber, there's nothing we can transfer to, so run
214
+ # other fibers (or just block) until waiting fiber finishes.
215
+ until finished
216
+ run_once
217
+ end
218
+ elsif !finished
219
+ @selector.transfer
220
+ end
147
221
 
148
- fiber.tap(&:transfer)
222
+ fiber
223
+
224
+ when :fleeting
225
+ if current != @fiber
226
+ # nested Fiber.schedule
227
+ @nested << current
228
+ end
229
+
230
+ Fiber.new(blocking: false, &block).tap(&:transfer)
231
+
232
+ when nil
233
+ if current != @fiber
234
+ # nested Fiber.schedule
235
+ @nested << current
236
+ end
237
+
238
+ fiber = Fiber.new(blocking: false) do
239
+ @count += 1
240
+ block.call
241
+ ensure
242
+ @count -= 1
243
+ end
244
+ fiber.tap(&:transfer)
245
+
246
+ else
247
+ raise "Unknown type"
248
+ end
149
249
  end
150
250
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiber_scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Sutic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-11 00:00:00.000000000 Z
11
+ date: 2022-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: io-event
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: async
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -39,33 +25,33 @@ dependencies:
39
25
  - !ruby/object:Gem::Version
40
26
  version: '2'
41
27
  - !ruby/object:Gem::Dependency
42
- name: rspec
28
+ name: fiber_scheduler_spec
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '3.11'
33
+ version: '0.10'
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '3.11'
40
+ version: '0.10'
55
41
  - !ruby/object:Gem::Dependency
56
- name: rubocop-rspec
42
+ name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '2.8'
47
+ version: '3.11'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '2.8'
54
+ version: '3.11'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: standard
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -86,10 +72,11 @@ executables: []
86
72
  extensions: []
87
73
  extra_rdoc_files: []
88
74
  files:
89
- - lib/fiber/scheduler.rb
90
75
  - lib/fiber_scheduler.rb
91
- - lib/fiber_scheduler/trigger.rb
92
- - lib/fiber_scheduler/triggers.rb
76
+ - lib/fiber_scheduler/compatibility.rb
77
+ - lib/fiber_scheduler/selector.rb
78
+ - lib/fiber_scheduler/timeout.rb
79
+ - lib/fiber_scheduler/timeouts.rb
93
80
  - lib/fiber_scheduler/version.rb
94
81
  homepage: https://github.com/bruno-/fiber_scheduler
95
82
  licenses:
@@ -1 +0,0 @@
1
- require_relative "../fiber_scheduler"
@@ -1,72 +0,0 @@
1
- require_relative "trigger"
2
-
3
- class FiberScheduler
4
- class Triggers
5
- def initialize
6
- # Array is sorted by Trigger#time
7
- @triggers = []
8
- end
9
-
10
- def call
11
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
-
13
- while @triggers.any? && @triggers.first.time <= now
14
- trigger = @triggers.shift
15
- unless trigger.disabled?
16
- trigger.call
17
- end
18
- end
19
- end
20
-
21
- def add(duration, &block)
22
- trigger = Trigger.new(duration, &block)
23
-
24
- if @triggers.empty?
25
- @triggers << trigger
26
- return trigger
27
- end
28
-
29
- # binary search
30
- min = 0
31
- max = @triggers.size - 1
32
- while min <= max
33
- index = (min + max) / 2
34
- t = @triggers[index]
35
-
36
- if t > trigger
37
- if index.zero? || @triggers[index - 1] <= trigger
38
- # found it
39
- break
40
- else
41
- # @triggers[index - 1] > trigger
42
- max = index - 1
43
- end
44
- else
45
- # t <= trigger
46
- index += 1
47
- min = index
48
- end
49
- end
50
-
51
- @triggers.insert(index, trigger)
52
- trigger
53
- end
54
-
55
- def interval
56
- # Prune disabled triggers
57
- while @triggers.first&.disabled?
58
- @triggers.shift
59
- end
60
-
61
- return if @triggers.empty?
62
-
63
- interval = @triggers.first.interval
64
-
65
- interval >= 0 ? interval : 0
66
- end
67
-
68
- def inspect
69
- @triggers.inspect
70
- end
71
- end
72
- end