fiber_scheduler 0.0.1 → 0.12.0

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: 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