fibril 0.0.1 → 0.0.5
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/README.md +20 -3
- data/examples/example_async.rb +7 -21
- data/examples/example_coop_multi_tasking.rb +33 -0
- data/examples/example_enum_tick.rb +9 -0
- data/examples/example_execution_order.rb +14 -0
- data/examples/example_future_async_await.rb +22 -0
- data/examples/example_future_sync_await.rb +23 -0
- data/examples/example_guard.rb +9 -7
- data/examples/example_guard2.rb +19 -0
- data/examples/example_guard3.rb +46 -0
- data/examples/example_http.rb +60 -0
- data/examples/example_multiple_loops.rb +78 -0
- data/examples/example_redis.rb +49 -0
- data/examples/{example_loop.rb → example_tick.rb} +1 -1
- data/examples/example_tick2.rb +22 -0
- data/examples/example_timeout.rb +9 -0
- data/fibril.gemspec +1 -1
- data/fibril.todo +22 -16
- data/lib/fibril.rb +2 -240
- data/lib/fibril/async_proxy.rb +28 -0
- data/lib/fibril/basic_object.rb +20 -0
- data/lib/fibril/control.rb +20 -0
- data/lib/fibril/core.rb +299 -0
- data/lib/fibril/extras.rb +8 -0
- data/lib/fibril/fasync_proxy.rb +36 -0
- data/lib/fibril/ffuture.rb +24 -0
- data/lib/fibril/fibril_proxy.rb +31 -0
- data/lib/fibril/forked_non_blocking_io_wrapper.rb +51 -0
- data/lib/fibril/future.rb +24 -0
- data/lib/fibril/guard.rb +123 -0
- data/lib/fibril/loop.rb +36 -6
- data/lib/fibril/non_blocking_io_wrapper.rb +60 -0
- data/lib/fibril/tick_proxy.rb +30 -0
- data/lib/fibril/version.rb +1 -1
- metadata +43 -8
- data/examples/example_1.rb +0 -71
- data/examples/example_2.rb +0 -80
- data/examples/example_3.rb +0 -82
- data/examples/example_promise.rb +0 -23
data/fibril.gemspec
CHANGED
@@ -21,11 +21,11 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.bindir = "exe"
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
|
-
|
25
24
|
spec.add_development_dependency "bundler", "~> 1.10"
|
26
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
26
|
spec.add_development_dependency "byebug", "~> 8.2.1"
|
28
27
|
spec.add_development_dependency "pry-byebug", "~> 3.3.0"
|
29
28
|
spec.add_development_dependency 'pry', '~> 0.10.2', '>= 0.10.0'
|
29
|
+
spec.add_development_dependency 'redis'
|
30
30
|
spec.add_development_dependency "minitest"
|
31
31
|
end
|
data/fibril.todo
CHANGED
@@ -6,34 +6,40 @@
|
|
6
6
|
✔ Create event loop import @done (16-02-18 21:15)
|
7
7
|
✔ Create continous and counting loop construct @done (16-02-18 21:15)
|
8
8
|
✔ Create async object @done (16-02-19 07:35)
|
9
|
-
✔ Create
|
9
|
+
✔ Create futures @done (16-02-19 07:49)
|
10
10
|
✔ Turn into gem @done (16-02-19 07:49)
|
11
|
+
✔ Create Fibril proxy @done (16-02-19 19:18)
|
12
|
+
✔ Provide optional timeout value to Fibril looping @done (16-02-23 08:58)
|
13
|
+
✔ Create smarter loop importer. Finds import statement and only evals everything past itself @done (16-02-23 09:01)
|
14
|
+
✔ Add fasync and ffuture @done (16-02-27 11:19)
|
15
|
+
✘ Add keywords syntax highlighting in Sublime @cancelled (16-03-01 18:34)
|
16
|
+
✘ future, async, fibril, await, await_all @cancelled (16-03-01 18:34)
|
11
17
|
|
12
|
-
☐ Add keywords syntax highlighting in Sublime
|
13
|
-
- promise, async, fibril, await, await_all
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
✘ Use efficient ring buffer as queue and block when full @cancelled (16-03-01 09:05)
|
20
|
+
✔ Split imports into core, and full functionality to prevent conflicts @done (16-03-01 18:41)
|
21
|
+
✔ Enumerable ticking with fibril @done (16-03-21 09:03)
|
22
|
+
✔ Enumerable concurrency, every nth @done (16-03-21 09:03)
|
23
|
+
☐ Add meaningful errors when common mistakes are made
|
24
|
+
☐ Add option to peek and consume in non blocking IO wrapper
|
19
25
|
|
20
26
|
☐ Examples:
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
✔ Fibril with tick @done (16-03-21 09:03)
|
29
|
+
✔ Fibril with async IO @done (16-03-21 09:03)
|
30
|
+
✔ Fibril external await @done (16-03-21 09:03)
|
31
|
+
✔ Fibril with internal await @done (16-03-21 09:03)
|
32
|
+
✔ Fibril wrapping redis @done (16-03-21 09:03)
|
33
|
+
✔ Futures @done (16-03-21 09:03)
|
27
34
|
|
28
35
|
☐ Disk read + write
|
29
36
|
☐ Database queries using AR
|
30
|
-
☐ Network IO
|
37
|
+
☐ Network IO = Https
|
31
38
|
☐ Asyncifying common functions
|
32
39
|
☐ Asyncifying class methods
|
33
40
|
☐ Asyncifying instance methods
|
34
41
|
☐ Asyncifying module methods
|
35
42
|
☐ Comparison to thread
|
36
43
|
☐ Comparison to event machine
|
37
|
-
|
38
|
-
☐
|
39
|
-
☐ Refactor
|
44
|
+
☐ Write performance tests
|
45
|
+
☐ Write tests
|
data/lib/fibril.rb
CHANGED
@@ -1,241 +1,3 @@
|
|
1
1
|
require "fibril/version"
|
2
|
-
require
|
3
|
-
|
4
|
-
|
5
|
-
class Fibril < Fiber
|
6
|
-
class << self
|
7
|
-
attr_accessor :running, :stopped, :queue, :task_count, :guards, :current, :id_seq
|
8
|
-
end
|
9
|
-
|
10
|
-
self.queue = []
|
11
|
-
self.guards = Hash.new{|h,k| }
|
12
|
-
self.id_seq = 0
|
13
|
-
|
14
|
-
attr_accessor :fiber, :guards, :block, :id
|
15
|
-
|
16
|
-
def self.log(msg)
|
17
|
-
# puts msg
|
18
|
-
end
|
19
|
-
|
20
|
-
def variables
|
21
|
-
@@variables ||= OpenStruct.new
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize(&blk)
|
25
|
-
self.id = Fibril.id_seq += 1
|
26
|
-
self.block = blk
|
27
|
-
self.guards = []
|
28
|
-
define_singleton_method :execute_fibril, self.block
|
29
|
-
super(&method(:execute))
|
30
|
-
Fibril.queue << self
|
31
|
-
end
|
32
|
-
|
33
|
-
def reset(guard)
|
34
|
-
copy = Fibril.new(&self.block)
|
35
|
-
copy.guards << guard
|
36
|
-
return copy
|
37
|
-
end
|
38
|
-
|
39
|
-
def execute
|
40
|
-
Fibril.task_count += 1
|
41
|
-
execute_fibril
|
42
|
-
self.guards.each(&:visit)
|
43
|
-
Fibril.task_count -= 1
|
44
|
-
Fibril.log "Ending #{id}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def tick
|
48
|
-
Fibril.enqueue self
|
49
|
-
self.yield
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.enqueue(fibril)
|
53
|
-
Fibril.log "Enqueing fibril #{fibril.id}"
|
54
|
-
Fibril.queue << fibril
|
55
|
-
end
|
56
|
-
|
57
|
-
def yield
|
58
|
-
Fibril.log "Yielding #{id}"
|
59
|
-
Fiber.yield
|
60
|
-
end
|
61
|
-
|
62
|
-
def current
|
63
|
-
self
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.deplete_guard(guard)
|
67
|
-
return unless waiters = guards[guard.id]
|
68
|
-
switches = waiters[:switches]
|
69
|
-
switches[guard.id] = true
|
70
|
-
waiters[:block][] if switches.values.all?
|
71
|
-
end
|
72
|
-
|
73
|
-
def await(*guards, &block)
|
74
|
-
|
75
|
-
if guards.length == 1 && guards[0].kind_of?(Promise)
|
76
|
-
return await_promise(guards[0])
|
77
|
-
end
|
78
|
-
|
79
|
-
await_block = {
|
80
|
-
switches: Hash[guards.map{|guard| [guard.id, false]}],
|
81
|
-
block: block
|
82
|
-
}
|
83
|
-
guards.each do |guard|
|
84
|
-
Fibril.guards[guard.id] = await_block
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def promise(&blk)
|
89
|
-
return Promise.new(&blk)
|
90
|
-
end
|
91
|
-
|
92
|
-
def await_promise(promise)
|
93
|
-
promise.await
|
94
|
-
end
|
95
|
-
|
96
|
-
def await_all(*promises)
|
97
|
-
promises.map(&:await)
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.stop
|
101
|
-
Fibril do
|
102
|
-
Fibril.stopped = true
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def resume
|
107
|
-
Fibril.current = self
|
108
|
-
Fibril.log "Resuming #{id}"
|
109
|
-
super
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.start
|
113
|
-
self.task_count = 0
|
114
|
-
self.stopped = false
|
115
|
-
self.running = true
|
116
|
-
if queue.any?
|
117
|
-
queue.shift.resume
|
118
|
-
self.loop if queue.any? || Fibril.task_count > 0
|
119
|
-
end
|
120
|
-
self.running = false
|
121
|
-
end
|
122
|
-
|
123
|
-
def self.loop
|
124
|
-
Fibril.log "Starting loop inside #{Fibril.current}"
|
125
|
-
while ((Fibril.task_count > 0 || queue.any?) && !Fibril.stopped)
|
126
|
-
Fibril.queue.shift.resume if Fibril.queue.any?
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def Guard(i, fibril)
|
131
|
-
return Guard.new(i, fibril)
|
132
|
-
end
|
133
|
-
|
134
|
-
class AsyncProxy
|
135
|
-
attr_accessor :target
|
136
|
-
|
137
|
-
def initialize(target)
|
138
|
-
self.target = target
|
139
|
-
end
|
140
|
-
|
141
|
-
def method_missing(name, *args, &block)
|
142
|
-
waiting = Fibril.current
|
143
|
-
Thread.new do
|
144
|
-
target.send(name, *args, &block).tap{ Fibril.enqueue waiting }
|
145
|
-
end.tap{
|
146
|
-
Fibril.current.yield
|
147
|
-
}.value
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
|
152
|
-
class Guard
|
153
|
-
class << self
|
154
|
-
attr_accessor :guard_seq
|
155
|
-
end
|
156
|
-
|
157
|
-
attr_accessor :fibril, :id, :break_condition, :depleted
|
158
|
-
|
159
|
-
self.guard_seq = 0
|
160
|
-
|
161
|
-
def self.create(fibril, counter=1)
|
162
|
-
self.guard_seq += 1
|
163
|
-
guard = Fibril::Guard.new(self.guard_seq, counter, fibril)
|
164
|
-
fibril.guards << guard
|
165
|
-
return guard
|
166
|
-
end
|
167
|
-
|
168
|
-
def await
|
169
|
-
Fibril.current.tick while !self.depleted
|
170
|
-
end
|
171
|
-
|
172
|
-
def initialize(id, counter, fibril)
|
173
|
-
self.id = id
|
174
|
-
self.fibril = fibril
|
175
|
-
self.break_condition = 1
|
176
|
-
end
|
177
|
-
|
178
|
-
def visit
|
179
|
-
case self.break_condition
|
180
|
-
when Proc
|
181
|
-
if self.break_condition[]
|
182
|
-
self.depleted = true
|
183
|
-
Fibril.deplete_guard(self)
|
184
|
-
else
|
185
|
-
self.fibril = self.fibril.reset(self)
|
186
|
-
end
|
187
|
-
else
|
188
|
-
self.break_condition -= 1
|
189
|
-
if self.break_condition.zero?
|
190
|
-
self.depleted = true
|
191
|
-
Fibril.deplete_guard(self)
|
192
|
-
else
|
193
|
-
self.fibril = self.fibril.reset(self)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def loop(break_condition=-1, &blck)
|
199
|
-
self.break_condition = block_given? ? blck : break_condition
|
200
|
-
self
|
201
|
-
end
|
202
|
-
|
203
|
-
def while(&blk)
|
204
|
-
loop{ !blk[] }
|
205
|
-
end
|
206
|
-
|
207
|
-
def until(&blk)
|
208
|
-
loop{ blk[] }
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
class Promise
|
213
|
-
attr_accessor :promise_thread
|
214
|
-
def initialize(&blk)
|
215
|
-
self.promise_thread = Thread.new(&blk)
|
216
|
-
end
|
217
|
-
|
218
|
-
def await
|
219
|
-
self.promise_thread.join.value
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
end
|
224
|
-
|
225
|
-
class ::BasicObject
|
226
|
-
def async
|
227
|
-
@async_proxy ||= ::Fibril::AsyncProxy.new(self)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
|
232
|
-
def Fibril(&block)
|
233
|
-
fibril = Fibril.new(&block).tap do |t|
|
234
|
-
Fibril.start unless Fibril.running
|
235
|
-
end
|
236
|
-
guard = Fibril::Guard.create(fibril)
|
237
|
-
end
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
Kernel.send :alias_method, :fibril, :Fibril
|
2
|
+
require "fibril/core"
|
3
|
+
require "fibril/extras"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Fibril::AsyncProxy
|
2
|
+
attr_accessor :target
|
3
|
+
|
4
|
+
def initialize(target)
|
5
|
+
self.target = target
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Execute target method on proxied target. Enqueue the current fibril
|
10
|
+
# to be resumed as soon as async task is finished
|
11
|
+
##
|
12
|
+
def method_missing(name, *_args, &_block)
|
13
|
+
define_singleton_method(name) do |*args, &block|
|
14
|
+
waiting = Fibril.current
|
15
|
+
Thread.new do
|
16
|
+
begin
|
17
|
+
target.send(name, *args, &block).tap{ Fibril.enqueue waiting }
|
18
|
+
rescue Exception => e
|
19
|
+
puts "Exception! #{e}"
|
20
|
+
Fibril.enqueue waiting
|
21
|
+
end
|
22
|
+
end.tap do
|
23
|
+
Fibril.current.yield
|
24
|
+
end.value
|
25
|
+
end
|
26
|
+
send(name, *_args, &_block)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class ::BasicObject
|
2
|
+
##
|
3
|
+
# Expose the async, fasync and fibril methods on all objects
|
4
|
+
##
|
5
|
+
|
6
|
+
##
|
7
|
+
# An asynchronous proxy. Executes any methods invoked via proxy on target in a separate thread
|
8
|
+
##
|
9
|
+
def async
|
10
|
+
@async_proxy ||= ::Fibril::AsyncProxy.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# An asynchronous proxy. Executes any methods invoked via proxy on target in a separate fork
|
15
|
+
##
|
16
|
+
def fasync
|
17
|
+
@fasync_proxy ||= ::Fibril::FAsyncProxy.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
##
|
2
|
+
# Expose thefuture and ffuture top level functions
|
3
|
+
##
|
4
|
+
|
5
|
+
##
|
6
|
+
# Create a new future
|
7
|
+
#
|
8
|
+
|
9
|
+
class ::BasicObject
|
10
|
+
def future(&blk)
|
11
|
+
return ::Fibril::Future.new(&blk)
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Create a new forked future
|
16
|
+
##
|
17
|
+
def ffuture(&blk)
|
18
|
+
return ::Fibril::FFuture.new(&blk)
|
19
|
+
end
|
20
|
+
end
|
data/lib/fibril/core.rb
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'fibril/guard'
|
2
|
+
require 'fibril/future'
|
3
|
+
require 'fibril/fibril_proxy'
|
4
|
+
require 'fibril/tick_proxy'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
class Fibril < Fiber
|
8
|
+
class << self
|
9
|
+
attr_accessor :running, :stopped, :queue, :task_count, :guards, :current, :id_seq, :loop_thread
|
10
|
+
end
|
11
|
+
|
12
|
+
self.queue = []
|
13
|
+
self.guards = Hash.new{|h,k| h[k] = [] }
|
14
|
+
self.id_seq = 0
|
15
|
+
self.task_count = 0
|
16
|
+
|
17
|
+
attr_accessor :fiber, :guards, :block, :id
|
18
|
+
|
19
|
+
def self.log(msg)
|
20
|
+
# puts msg
|
21
|
+
end
|
22
|
+
|
23
|
+
def guard
|
24
|
+
Fibril.guard
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.guard
|
28
|
+
@@guard ||= OpenStruct.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def variables
|
32
|
+
Fibril.variables
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.variables
|
36
|
+
@@variables ||= OpenStruct.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(&blk)
|
40
|
+
self.id = Fibril.id_seq += 1
|
41
|
+
self.block = blk
|
42
|
+
self.guards = []
|
43
|
+
define_singleton_method :execute_fibril, self.block
|
44
|
+
if Fibril.running
|
45
|
+
super(&method(:execute))
|
46
|
+
Fibril.enqueue self
|
47
|
+
else
|
48
|
+
Fibril.task_count = 0
|
49
|
+
Fibril.stopped = false
|
50
|
+
Fibril.running = true
|
51
|
+
super(&method(:execute))
|
52
|
+
Fibril.enqueue self
|
53
|
+
Fibril.start
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset(guard)
|
58
|
+
copy = Fibril.new(&self.block)
|
59
|
+
copy.guards << guard
|
60
|
+
return copy
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute
|
64
|
+
Fibril.task_count += 1
|
65
|
+
exception = nil
|
66
|
+
result = begin
|
67
|
+
execute_fibril
|
68
|
+
rescue Exception => e
|
69
|
+
exception = e
|
70
|
+
end
|
71
|
+
self.guards.each do |guard|
|
72
|
+
guard.visit(result)
|
73
|
+
end
|
74
|
+
Fibril.task_count -= 1
|
75
|
+
Fibril.log "Ending #{id}"
|
76
|
+
raise exception if exception
|
77
|
+
end
|
78
|
+
|
79
|
+
def tick
|
80
|
+
if Thread.current != Fibril.loop_thread
|
81
|
+
Fibril.log "Current thread is #{Thread.current.object_id}"
|
82
|
+
Fibril.log "Fibril thread is #{Fibril.loop_thread.object_id}"
|
83
|
+
Fibril.log "WARN: Cannot tick inside async code outside of main loop thread. This will be a noop"
|
84
|
+
elsif !Fibril.queue.empty?
|
85
|
+
Fibril.enqueue self
|
86
|
+
self.yield
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def enqueue
|
91
|
+
Fibril.enqueue(self)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.enqueue(fibril)
|
95
|
+
Fibril.log "Enqueing fibril #{fibril.id}"
|
96
|
+
Fibril.queue << fibril
|
97
|
+
end
|
98
|
+
|
99
|
+
def yield
|
100
|
+
Fibril.log "Yielding #{id}"
|
101
|
+
yield(self) if block_given?
|
102
|
+
Fiber.yield
|
103
|
+
end
|
104
|
+
|
105
|
+
def current
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.deplete_guard(guard, result)
|
110
|
+
return unless waiter_list = guards[guard.id]
|
111
|
+
waiter_list.each do |waiters|
|
112
|
+
switches = waiters[:switches]
|
113
|
+
switches[guard.id] = true
|
114
|
+
if waiters.has_key?(:to_fulfill)
|
115
|
+
Fibril.enqueue waiters[:to_fulfill] if switches.values.all?
|
116
|
+
waiters[:result] ||= []
|
117
|
+
waiters[:result] << result
|
118
|
+
else
|
119
|
+
waiters[:result] ||= []
|
120
|
+
waiters[:result] << result
|
121
|
+
waiters[:block][*sort_results(waiters[:result], waiters[:guards])] if waiters[:block] && switches.values.all?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def await_fibril(guards)
|
127
|
+
singular = guards.one?
|
128
|
+
return singular ? guards[0].result : guards.map(&:result) if guards.all?(&:result?)
|
129
|
+
await_block = {
|
130
|
+
switches: Hash[guards.map{|guard| [guard.id, false]}],
|
131
|
+
to_fulfill: Fibril.current
|
132
|
+
}
|
133
|
+
guards.each do |guard|
|
134
|
+
Fibril.guards[guard.id] << await_block
|
135
|
+
end
|
136
|
+
self.yield
|
137
|
+
return singular ? await_block[:result][0] : Fibril.sort_results(await_block[:result], guards)
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.sort_results(results, guards)
|
141
|
+
by_complete_order = guards.sort_by(&:depleted_at)
|
142
|
+
results.zip(by_complete_order).sort do |(_, guard_a), (_, guard_b)|
|
143
|
+
guards.index(guard_a) <=> guards.index(guard_b)
|
144
|
+
end.map(&:first)
|
145
|
+
end
|
146
|
+
|
147
|
+
def await(*guards, &block)
|
148
|
+
guards.map!{|guard| guard.kind_of?(Symbol) ? Fibril.guard.send(guard) : guard}
|
149
|
+
raise "Invalid guard given #{guards}" unless guards.all?{|g| g.kind_of?(Guard) || g.kind_of?(Future)}
|
150
|
+
if block_given?
|
151
|
+
return block[*guards.map(&:result)] if guards.all?(&:result?)
|
152
|
+
await_block = {
|
153
|
+
switches: Hash[guards.map{|guard| [guard.id, false]}],
|
154
|
+
block: block,
|
155
|
+
guards: guards
|
156
|
+
}
|
157
|
+
guards.each do |guard|
|
158
|
+
Fibril.guards[guard.id] << await_block
|
159
|
+
end
|
160
|
+
else
|
161
|
+
guard = guards.first
|
162
|
+
guard.kind_of?(Future) ? await_future(guard) : await_fibril(guards)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def await_future(future)
|
167
|
+
tick while future.alive?
|
168
|
+
future.await
|
169
|
+
end
|
170
|
+
|
171
|
+
def await_all(*futures)
|
172
|
+
futures.map(&:await)
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.stop
|
176
|
+
Fibril do
|
177
|
+
Fibril.stopped = true
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def resume
|
182
|
+
Fibril.current = self
|
183
|
+
Fibril.log "Resuming #{id}"
|
184
|
+
super
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.start
|
188
|
+
self.start_loop if !queue.empty?
|
189
|
+
self.running = false
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.profile(test)
|
193
|
+
starts = Time.now
|
194
|
+
result = yield
|
195
|
+
ends = Time.now
|
196
|
+
Fibril.log "#{test} took #{ends - starts}"
|
197
|
+
return result
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.start_loop
|
201
|
+
Fibril.log "Starting loop inside #{Fibril.current}"
|
202
|
+
Fibril.loop_thread = Thread.current
|
203
|
+
while pending_tasks?
|
204
|
+
Fibril.current = nil
|
205
|
+
Fibril.queue.shift.resume while !queue.empty?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.pending_tasks?
|
210
|
+
((@task_count > 0 || !@queue.empty?) && !@stopped)
|
211
|
+
end
|
212
|
+
|
213
|
+
def Guard(i, fibril)
|
214
|
+
return Guard.new(i, fibril)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
##
|
220
|
+
# Create a new fibril
|
221
|
+
##
|
222
|
+
|
223
|
+
def Fibril(*guard_names, &block)
|
224
|
+
fibril = Fibril.new(&block)
|
225
|
+
return fibril unless Fibril.running
|
226
|
+
Fibril::Guard.create(fibril).tap do |guard|
|
227
|
+
guard_names.each do |name|
|
228
|
+
Fibril.guard.send("#{name}=", guard)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class Enumerator
|
234
|
+
def fibril(*guard_names, &block)
|
235
|
+
context = self
|
236
|
+
Kernel.fibril(*guard_names){
|
237
|
+
e = Enumerator.new do |enum|
|
238
|
+
context.each do |*elm|
|
239
|
+
result = enum.yield(*elm)
|
240
|
+
tick
|
241
|
+
result
|
242
|
+
end
|
243
|
+
end
|
244
|
+
e.each(&block)
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
def n_fibrils(*guard_names, n, &block)
|
249
|
+
context = self
|
250
|
+
|
251
|
+
guards = n.times.map do |i|
|
252
|
+
Kernel.fibril{
|
253
|
+
e = Enumerator.new do |enum|
|
254
|
+
context.each.with_index do |elm, index|
|
255
|
+
next unless ((index - i) % n).zero?
|
256
|
+
result = enum.yield(*elm)
|
257
|
+
tick
|
258
|
+
result
|
259
|
+
end
|
260
|
+
end
|
261
|
+
e.each(&block)
|
262
|
+
}
|
263
|
+
end
|
264
|
+
Kernel.fibril(*guard_names){
|
265
|
+
all_results = await(*guards)
|
266
|
+
length = all_results.max{|x| x.length}.length
|
267
|
+
length.times.map do |i|
|
268
|
+
all_results.find{|list|
|
269
|
+
list[i] != nil
|
270
|
+
}[i]
|
271
|
+
end
|
272
|
+
}
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class ::BasicObject
|
277
|
+
##
|
278
|
+
# This method has two methods of use.
|
279
|
+
# Either
|
280
|
+
# A. call with block to create a new fibril
|
281
|
+
# B. call without block to create a fibril proxy. Any methods invoked on a proxy are executed on the target from
|
282
|
+
# within a new Fibril
|
283
|
+
##
|
284
|
+
def fibril(*guard_names, &block)
|
285
|
+
if block_given?
|
286
|
+
Fibril(*guard_names, &block)
|
287
|
+
else
|
288
|
+
::Fibril::FibrilProxy.new(self, *guard_names)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def tick(*guard_names, **args)
|
293
|
+
::Fibril::TickProxy.new(self, *guard_names, **args)
|
294
|
+
end
|
295
|
+
|
296
|
+
def await(*args, &block)
|
297
|
+
::Fibril.current.await(*args, &block)
|
298
|
+
end
|
299
|
+
end
|