fiber-scheduler 0.0.2 → 0.10.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: f5314cb38a585c244f7b789f576bd210884ce3e5502885d55cd5733c702efa33
4
- data.tar.gz: 10ba33be002eb6a5fff98f5326a3ec5278de8b6d9c5eba3ed3815f5c76e4c71b
3
+ metadata.gz: f7fa0c2f81a443cd6c852a51ad9c8d89d30ee080013980c94c2d16335aa074f1
4
+ data.tar.gz: 83b4f910992b1f5d44976095d51c822acac07b98eb795ed1208cfd41e935e320
5
5
  SHA512:
6
- metadata.gz: 495f8feca5e5695c285bbfc7aec699afafb7b4a3500f3c7b40f189a2488dd3815914f1485825567b6d4b8a236e1a85037c3dbc072d2ea7ce2a39aa771e4efc28
7
- data.tar.gz: 1c04427896b4eac157f3a2c2915d034fa02d429ed287001ab2d1eb4780ad17590aa1ac2431bf307af8da527288273b04f77971061bae3140df3af9c767effe4b
6
+ metadata.gz: 41c1fed81c488cb5e4044688bc80b2b95136b5e8401260a35d3abdb8d05e8603ea40087d96f9f730417543783729cc713dbaafe46eaa3f7b5a8936cfd617857b
7
+ data.tar.gz: 7f712132eedc368609578b6f07073aa4be1d4db11305e6b70b9627807f8acee48fd41f1a6876f214062d7da5c1bdd881c2a65830a2fe84b5ea202dc2079ae969
@@ -0,0 +1,49 @@
1
+ class FiberScheduler
2
+ module Compatibility
3
+ def fiber(*args, **opts, &block)
4
+ return super unless Compatibility.internal?
5
+
6
+ # This is `Fiber.schedule` call inside `FiberScheduler { ... }` block.
7
+ if opts[:blocking]
8
+ Fiber.new(blocking: true) {
9
+ Compatibility.set_internal!
10
+ yield
11
+ }.tap(&:resume)
12
+
13
+ elsif opts[:waiting]
14
+ parent = Fiber.current
15
+ finished = false # prevents races
16
+ blocking = false # prevents #unblock-ing a fiber that never blocked
17
+
18
+ # Don't pass *args and **opts to an unknown fiber scheduler class.
19
+ super() do
20
+ Compatibility.set_internal!
21
+ yield
22
+ ensure
23
+ finished = true
24
+ unblock(nil, parent) if blocking
25
+ end
26
+
27
+ unless finished
28
+ blocking = true
29
+ block(nil, nil)
30
+ end
31
+
32
+ else
33
+ # Don't pass *args and **opts to an unknown fiber scheduler class.
34
+ super() do
35
+ Compatibility.set_internal!
36
+ yield
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.set_internal!
42
+ Thread.current[:_fiber_scheduler] = true # Sets a FIBER local var!
43
+ end
44
+
45
+ def self.internal?
46
+ Thread.current[:_fiber_scheduler]
47
+ end
48
+ end
49
+ 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.2".freeze
2
+ VERSION = "0.10.0".freeze
3
3
  end
@@ -1,9 +1,16 @@
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(blocking: false, waiting: true, &block)
7
14
  if Fiber.scheduler.nil?
8
15
  scheduler = FiberScheduler.new
9
16
  Fiber.set_scheduler(scheduler)
@@ -14,20 +21,73 @@ module Kernel
14
21
  ensure
15
22
  Fiber.set_scheduler(nil)
16
23
  end
24
+
17
25
  else
18
- # Fiber.scheduler already set, just schedule a task.
19
- Fiber.schedule(&block)
26
+ scheduler = Fiber.scheduler
27
+ # Fiber.scheduler already set, just schedule a fiber.
28
+ if scheduler.is_a?(FiberScheduler)
29
+ # The default waiting is 'true' as that is the most intuitive behavior
30
+ # for a nested FiberScheduler call.
31
+ Fiber.schedule(blocking: blocking, waiting: waiting, &block)
32
+
33
+ # Unknown fiber scheduler class; can't just pass options to
34
+ # Fiber.schedule, handle each option separately.
35
+ else
36
+ scheduler.singleton_class.prepend(FiberScheduler::Compatibility)
37
+
38
+ if blocking
39
+ fiber = Fiber.new(blocking: true) do
40
+ FiberScheduler::Compatibility.set_internal!
41
+ yield
42
+ end
43
+ fiber.tap(&:resume)
44
+
45
+ elsif waiting
46
+ parent = Fiber.current
47
+ finished = false # prevents races
48
+ blocking = false # prevents #unblock-ing a fiber that never blocked
49
+
50
+ Fiber.schedule do
51
+ FiberScheduler::Compatibility.set_internal!
52
+ yield
53
+ ensure
54
+ finished = true
55
+ scheduler.unblock(nil, parent) if blocking
56
+ end
57
+
58
+ if Fiber.blocking?
59
+ # In a blocking fiber, which is potentially also a loop fiber so
60
+ # there's nothing we can transfer to. Run other fibers (or just
61
+ # block) until waiting fiber finishes.
62
+ until finished
63
+ scheduler.run_once
64
+ end
65
+ elsif !finished
66
+ blocking = true
67
+ scheduler.block(nil, nil)
68
+ end
69
+
70
+ else
71
+ Fiber.schedule do
72
+ FiberScheduler::Compatibility.set_internal!
73
+ yield
74
+ end
75
+ end
76
+ end
20
77
  end
21
78
  end
22
79
  end
23
80
 
24
81
  class FiberScheduler
25
- TimeoutError = Class.new(RuntimeError)
26
- IOWaitTimeout = Class.new(TimeoutError)
27
-
28
82
  def initialize
29
- @selector = IO::Event::Selector.new(Fiber.current)
30
- @triggers = Triggers.new
83
+ @fiber = Fiber.current
84
+ @selector =
85
+ if defined?(IO::Event)
86
+ IO::Event::Selector.new(@fiber)
87
+ else
88
+ Selector.new(@fiber)
89
+ end
90
+ @timeouts = Timeouts.new
31
91
 
32
92
  @count = 0
33
93
  @nested = []
@@ -35,14 +95,18 @@ class FiberScheduler
35
95
 
36
96
  def run
37
97
  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
98
+ run_once
99
+ end
100
+ end
101
+
102
+ def run_once
103
+ if @nested.empty?
104
+ @selector.select(@timeouts.interval)
105
+ @timeouts.call
106
+ else
107
+ while @nested.any?
108
+ fiber = @nested.pop
109
+ fiber.transfer
46
110
  end
47
111
  end
48
112
  end
@@ -60,18 +124,11 @@ class FiberScheduler
60
124
  end
61
125
  end
62
126
 
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
127
+ def block(blocker, duration = nil)
128
+ return @selector.transfer unless duration
70
129
 
71
- begin
130
+ @timeouts.timeout(duration, method: :transfer) do
72
131
  @selector.transfer
73
- ensure
74
- trigger.disable
75
132
  end
76
133
  end
77
134
 
@@ -89,21 +146,11 @@ class FiberScheduler
89
146
  Resolv.getaddresses(hostname)
90
147
  end
91
148
 
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
149
+ def io_wait(io, events, duration = nil)
150
+ return @selector.io_wait(Fiber.current, io, events) unless duration
100
151
 
101
- begin
102
- @selector.io_wait(fiber, io, events)
103
- rescue IOWaitTimeout
104
- false
105
- ensure
106
- trigger.disable
152
+ @timeouts.timeout(duration, method: :transfer) do
153
+ @selector.io_wait(Fiber.current, io, events)
107
154
  end
108
155
  end
109
156
 
@@ -119,32 +166,54 @@ class FiberScheduler
119
166
  @selector.process_wait(Fiber.current, pid, flags)
120
167
  end
121
168
 
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
169
+ def timeout_after(duration, exception = Timeout::Error, message = "timeout", &block)
170
+ @timeouts.timeout(duration, exception, message, &block)
133
171
  end
134
172
 
135
- def fiber(&block)
136
- unless Fiber.blocking?
137
- # nested Fiber.schedule
138
- @nested << Fiber.current
139
- end
173
+ def fiber(blocking: false, waiting: false, &block)
174
+ current = Fiber.current
175
+
176
+ if blocking
177
+ # All fibers wait on a blocking fiber, so 'waiting' option is ignored.
178
+ Fiber.new(blocking: true, &block).tap(&:resume)
179
+ elsif waiting
180
+ finished = false # prevents races
181
+ fiber = Fiber.new(blocking: false) do
182
+ @count += 1
183
+ block.call
184
+ ensure
185
+ @count -= 1
186
+ finished = true
187
+ # Resume waiting parent fiber
188
+ current.transfer
189
+ end
190
+ fiber.transfer
191
+
192
+ # Current fiber is waiting until waiting fiber finishes.
193
+ if current == @fiber
194
+ # In a top-level fiber, there's nothing we can transfer to, so run
195
+ # other fibers (or just block) until waiting fiber finishes.
196
+ until finished
197
+ run_once
198
+ end
199
+ elsif !finished
200
+ @selector.transfer
201
+ end
140
202
 
141
- fiber = Fiber.new(blocking: false) do
142
- @count += 1
143
- block.call
144
- ensure
145
- @count -= 1
146
- end
203
+ fiber
204
+ else
205
+ if current != @fiber
206
+ # nested Fiber.schedule
207
+ @nested << current
208
+ end
147
209
 
148
- fiber.tap(&:transfer)
210
+ fiber = Fiber.new(blocking: false) do
211
+ @count += 1
212
+ block.call
213
+ ensure
214
+ @count -= 1
215
+ end
216
+ fiber.tap(&:transfer)
217
+ end
149
218
  end
150
219
  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.2
4
+ version: 0.10.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-15 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.0'
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.0'
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