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 +4 -4
- data/lib/fiber_scheduler/compatibility.rb +49 -0
- data/lib/fiber_scheduler/selector.rb +230 -0
- data/lib/fiber_scheduler/{trigger.rb → timeout.rb} +12 -4
- data/lib/fiber_scheduler/timeouts.rb +79 -0
- data/lib/fiber_scheduler/version.rb +1 -1
- data/lib/fiber_scheduler.rb +134 -65
- metadata +12 -25
- data/lib/fiber/scheduler.rb +0 -1
- data/lib/fiber_scheduler/triggers.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7fa0c2f81a443cd6c852a51ad9c8d89d30ee080013980c94c2d16335aa074f1
|
4
|
+
data.tar.gz: 83b4f910992b1f5d44976095d51c822acac07b98eb795ed1208cfd41e935e320
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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,
|
11
|
+
def initialize(duration, fiber, method, *args)
|
8
12
|
@time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
|
9
|
-
@
|
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
|
-
@
|
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
|
data/lib/fiber_scheduler.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
require "io/event"
|
2
1
|
require "resolv"
|
3
|
-
require_relative "fiber_scheduler/
|
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
|
-
|
19
|
-
Fiber.schedule
|
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
|
-
@
|
30
|
-
@
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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,
|
64
|
-
return @selector.transfer unless
|
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
|
-
|
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,
|
93
|
-
|
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
|
-
|
102
|
-
@selector.io_wait(
|
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 =
|
123
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
203
|
+
fiber
|
204
|
+
else
|
205
|
+
if current != @fiber
|
206
|
+
# nested Fiber.schedule
|
207
|
+
@nested << current
|
208
|
+
end
|
147
209
|
|
148
|
-
|
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
|
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
|
+
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:
|
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: '
|
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: '
|
40
|
+
version: '0.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: rspec
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
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: '
|
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/
|
92
|
-
- lib/fiber_scheduler/
|
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:
|
data/lib/fiber/scheduler.rb
DELETED
@@ -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
|