fiber-scheduler 0.0.2 → 0.10.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: 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