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