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
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'fibril/control'
|
2
|
+
require 'fibril/basic_object'
|
3
|
+
require 'fibril/async_proxy'
|
4
|
+
require 'fibril/fasync_proxy'
|
5
|
+
require 'fibril/fibril_proxy'
|
6
|
+
require 'fibril/ffuture'
|
7
|
+
require 'fibril/non_blocking_io_wrapper'
|
8
|
+
require 'fibril/forked_non_blocking_io_wrapper'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Fibril::FAsyncProxy
|
4
|
+
attr_accessor :target
|
5
|
+
|
6
|
+
def initialize(target)
|
7
|
+
self.target = target
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Execute target method within a new fork. Enqueue the current fibril
|
12
|
+
# to be resumed as soon as async task is finished.
|
13
|
+
# The result of the forked process is passed to the parent by Marshaling.
|
14
|
+
##
|
15
|
+
def method_missing(name, *_args, &_block)
|
16
|
+
define_singleton_method(name){|*args, &block|
|
17
|
+
read, write = IO.pipe
|
18
|
+
waiting = Fibril.current
|
19
|
+
pid = fork do
|
20
|
+
result = target.send(name, *args, &block)
|
21
|
+
read.close
|
22
|
+
YAML.dump(result, write)
|
23
|
+
end
|
24
|
+
write.close
|
25
|
+
result = nil
|
26
|
+
Thread.new{
|
27
|
+
result = read.read
|
28
|
+
Process.wait(pid)
|
29
|
+
Fibril.enqueue waiting
|
30
|
+
}
|
31
|
+
Fibril.current.yield
|
32
|
+
YAML.load(result)
|
33
|
+
}
|
34
|
+
send(name, *_args, &_block)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'future'
|
2
|
+
|
3
|
+
class Fibril::FFuture < Fibril::Future
|
4
|
+
|
5
|
+
##
|
6
|
+
# A Forked future. Fulfils the same future api as Fibril::Future
|
7
|
+
# but runs the block inside a new fork instead of a new thread..
|
8
|
+
##
|
9
|
+
def initialize(&blk)
|
10
|
+
read, write = IO.pipe
|
11
|
+
pid = fork do
|
12
|
+
result = blk[]
|
13
|
+
read.close
|
14
|
+
Marshal.dump(result, write)
|
15
|
+
end
|
16
|
+
write.close
|
17
|
+
result = nil
|
18
|
+
self.future_thread = Thread.new do
|
19
|
+
result = read.read
|
20
|
+
Process.wait(pid)
|
21
|
+
Marshal.load(result)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Fibril::FibrilProxy
|
2
|
+
attr_accessor :target, :guard_names
|
3
|
+
|
4
|
+
ENUM_METHODS = %w(map each each_with_index with_index with_object detect select each_slice each_cons each_entry reverse_each)
|
5
|
+
|
6
|
+
def initialize(target, *guard_names)
|
7
|
+
self.target = target
|
8
|
+
self.guard_names = guard_names
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Execute target method within a new fibril
|
13
|
+
##
|
14
|
+
def method_missing(name, *args, &block)
|
15
|
+
|
16
|
+
context = target
|
17
|
+
|
18
|
+
return context.send(name).fibril(*self.guard_names) do |*elms, &blk|
|
19
|
+
block[*elms, &blk]
|
20
|
+
end if ENUM_METHODS.include?(name.to_s)
|
21
|
+
|
22
|
+
|
23
|
+
fibril{
|
24
|
+
context.send(name, *args, &block)
|
25
|
+
}.tap do |guard|
|
26
|
+
guard_names.each do |guard_name|
|
27
|
+
Fibril.guard.send("#{guard_name}=", guard)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative 'non_blocking_io_wrapper'
|
2
|
+
|
3
|
+
class Fibril::ForkedNonBlockingIOWrapper < Fibril::NonBlockingIOWrapper
|
4
|
+
attr_accessor :read, :write
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
def initialize(*, &block)
|
8
|
+
read, write = IO.pipe
|
9
|
+
self.read, self.write = read, write
|
10
|
+
self.response_queue = []
|
11
|
+
self.fibrils = []
|
12
|
+
define_singleton_method(:loop, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
freceive = method(:freceive)
|
17
|
+
block = method(:loop)
|
18
|
+
read, write = self.read, self.write
|
19
|
+
fibril{
|
20
|
+
fork do
|
21
|
+
read.close
|
22
|
+
block[]
|
23
|
+
exit(0)
|
24
|
+
end
|
25
|
+
|
26
|
+
write.close
|
27
|
+
begin
|
28
|
+
Fibril.current.tick
|
29
|
+
while message = read.gets
|
30
|
+
freceive[*Marshal.load(URI.unescape(message))]
|
31
|
+
Fibril.current.tick
|
32
|
+
end
|
33
|
+
rescue Exception => e
|
34
|
+
puts "Exception! : #{e}"
|
35
|
+
puts e.backtrace
|
36
|
+
end
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def receive(*args)
|
41
|
+
self.write.puts URI.escape(Marshal.dump(args)) rescue nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def freceive(*args)
|
45
|
+
if args.any?
|
46
|
+
ingest(*args)
|
47
|
+
else
|
48
|
+
method(:ingest)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Fibril::Future
|
2
|
+
attr_accessor :future_thread
|
3
|
+
|
4
|
+
##
|
5
|
+
# A future. A very thin wrapper around a thread.
|
6
|
+
# Can be used within `await` and `await_all` within a fibril
|
7
|
+
##
|
8
|
+
|
9
|
+
def initialize(&blk)
|
10
|
+
self.future_thread = Thread.new(&blk)
|
11
|
+
end
|
12
|
+
|
13
|
+
def await
|
14
|
+
self.future_thread.join.value
|
15
|
+
end
|
16
|
+
|
17
|
+
def alive?
|
18
|
+
self.future_thread.alive?
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
self.future_thread.kill
|
23
|
+
end
|
24
|
+
end
|
data/lib/fibril/guard.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
class Fibril::Guard
|
2
|
+
class << self
|
3
|
+
attr_accessor :guard_seq
|
4
|
+
end
|
5
|
+
|
6
|
+
attr_accessor :fibril, :id, :break_condition, :depleted, :timeout_period, :result, :depleted_at
|
7
|
+
|
8
|
+
class NoResult;end
|
9
|
+
|
10
|
+
self.guard_seq = 0
|
11
|
+
|
12
|
+
##
|
13
|
+
# Create a new guard for a given fibril and add a reference to it to this same fibril.
|
14
|
+
##
|
15
|
+
def self.create(fibril, counter=1)
|
16
|
+
self.guard_seq += 1
|
17
|
+
guard = Fibril::Guard.new(self.guard_seq, counter, fibril)
|
18
|
+
fibril.guards << guard
|
19
|
+
return guard
|
20
|
+
end
|
21
|
+
|
22
|
+
def result?
|
23
|
+
self.result != NoResult
|
24
|
+
end
|
25
|
+
|
26
|
+
def depleted?
|
27
|
+
depleted
|
28
|
+
end
|
29
|
+
##
|
30
|
+
# Continue to process fibrils until this guard is depleted.
|
31
|
+
##
|
32
|
+
def await
|
33
|
+
Fibril.current.tick while !self.depleted
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Schedule this guard to deplete the next time it is visited
|
38
|
+
##
|
39
|
+
def cancel
|
40
|
+
self.break_condition = 1
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Create a new guard object. A guard can have a break condition which is either
|
45
|
+
# A. A counter, guard will deplete when it has been visited this many times
|
46
|
+
# B. A break condition, guard will deplete when this proc/lambda returns true
|
47
|
+
##
|
48
|
+
def initialize(id, counter, fibril)
|
49
|
+
self.id = id
|
50
|
+
self.fibril = fibril
|
51
|
+
self.break_condition = counter
|
52
|
+
self.result = NoResult
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Visit this guard. This is called everytime the fibril associated with this guard
|
57
|
+
# completes. If the guard does not deplete the fibril resets and runs again
|
58
|
+
##
|
59
|
+
def visit(result=nil)
|
60
|
+
case self.break_condition
|
61
|
+
when Proc
|
62
|
+
if self.break_condition[]
|
63
|
+
self.deplete(result)
|
64
|
+
else
|
65
|
+
self.fibril = self.fibril.reset(self)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
self.break_condition -= 1
|
69
|
+
if self.break_condition.zero?
|
70
|
+
self.deplete(result)
|
71
|
+
else
|
72
|
+
unless timeout_period.zero?
|
73
|
+
if timeout_period > 0.1
|
74
|
+
async.sleep(timeout_period)
|
75
|
+
else
|
76
|
+
sleep(timeout_period)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
self.fibril = self.fibril.reset(self)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Deplete the guard. The guard has served its purpose
|
86
|
+
##
|
87
|
+
def deplete(result)
|
88
|
+
self.result = result
|
89
|
+
self.depleted = true
|
90
|
+
self.depleted_at = Time.now
|
91
|
+
Fibril.deplete_guard(self, result)
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Loop the fibril associated with a guard either a set number of times
|
96
|
+
# or until a block evaluates to true
|
97
|
+
##
|
98
|
+
def loop(break_condition=-1, timeout=0, &blck)
|
99
|
+
self.break_condition = block_given? ? blck : break_condition
|
100
|
+
self.timeout_period = timeout
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# The inverse of loop. Loop until a block evalutes to true
|
106
|
+
##
|
107
|
+
def while(&blk)
|
108
|
+
loop{ !blk[] }
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Equivalent of loop
|
113
|
+
##
|
114
|
+
def until(*guards, &blk)
|
115
|
+
if block_given?
|
116
|
+
loop{ blk[] }
|
117
|
+
else
|
118
|
+
loop{
|
119
|
+
guards.map{|guard| guard.kind_of?(Symbol) ? Fibril.guard.send(guard) : guard}.all?(&:depleted?)
|
120
|
+
}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/fibril/loop.rb
CHANGED
@@ -1,11 +1,41 @@
|
|
1
|
+
##
|
2
|
+
# The Fibril event loop.
|
3
|
+
# Requiring this file will execute all code between the require statement and EOF inside a fibril before exiting.
|
4
|
+
#
|
5
|
+
# Ideal for scripts that need to use cooperative multitasking without having to wrap in an extra fibril simply to
|
6
|
+
# start the loop
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# Note using fibril/loop is equivalent to wrapping the entire script in a Fibril do ... end.
|
10
|
+
# E.g
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# require 'fibril/loop'
|
14
|
+
# puts "in a fibril"
|
15
|
+
#
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# is equivalent to:
|
19
|
+
#
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# require 'fibril'
|
23
|
+
#
|
24
|
+
# Fibril do
|
25
|
+
# puts "In a fibril"
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
#
|
29
|
+
#
|
30
|
+
##
|
1
31
|
require_relative '../fibril'
|
2
32
|
require 'pry'
|
3
33
|
|
4
34
|
call_stack = caller
|
5
|
-
|
35
|
+
filename = call_stack[0][/(.*?(?=\:)):(\d+)/,1]
|
36
|
+
linenumber = call_stack[0][/(.*?(?=\:)):(\d+)/,2].to_i
|
37
|
+
lines = IO.read(filename).split("\n")[linenumber..-1]
|
6
38
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
exit(0)
|
11
|
-
end
|
39
|
+
$LOAD_PATH << '.'
|
40
|
+
fibril{ eval lines.join("\n") }
|
41
|
+
exit(0)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Fibril::NonBlockingIOWrapper
|
2
|
+
##
|
3
|
+
# A Non block IO wrapper allows you to execute a blocking IO loop inside a separate thread
|
4
|
+
# and receive all inputs inside one or more Fibrils.
|
5
|
+
#
|
6
|
+
# This allows you to have multiple block IO loops operating in parallel whilst still processing all
|
7
|
+
# resulting messages in the main thread.
|
8
|
+
##
|
9
|
+
attr_accessor :guard, :response_queue, :fibrils, :result
|
10
|
+
|
11
|
+
def initialize(*, &block)
|
12
|
+
self.response_queue = []
|
13
|
+
self.fibrils = []
|
14
|
+
define_singleton_method(:loop, &block)
|
15
|
+
future{
|
16
|
+
begin
|
17
|
+
loop()
|
18
|
+
rescue Exception => e
|
19
|
+
puts "Exception occurred in thead #{Thread.current} : #{e.message}"
|
20
|
+
puts e.backtrace
|
21
|
+
end
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Receive a message from the async IO loop if a message is sent, otherwise return a reference to the ingest method
|
27
|
+
##
|
28
|
+
def receive(*args)
|
29
|
+
if args.any?
|
30
|
+
ingest(*args)
|
31
|
+
else
|
32
|
+
method(:ingest)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Add the ingested message to the response queue and schedule all fibrils
|
38
|
+
# waiting on events to receive messages
|
39
|
+
##
|
40
|
+
def ingest(*args)
|
41
|
+
begin
|
42
|
+
self.response_queue << args
|
43
|
+
self.fibrils.shift.enqueue while self.fibrils.any?
|
44
|
+
rescue Exception => e
|
45
|
+
puts "Exception occurred when ingesting from #{self} : #{e.message}"
|
46
|
+
puts e.backtrace
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def await
|
51
|
+
##
|
52
|
+
# Set all fibrils into waiting state until there is something in the response queue
|
53
|
+
##
|
54
|
+
Fibril.current.yield{|f| self.fibrils << f} until self.response_queue.any?
|
55
|
+
##
|
56
|
+
# Return values from the response queue
|
57
|
+
##
|
58
|
+
self.response_queue.shift
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Fibril::TickProxy
|
2
|
+
attr_accessor :target, :tick_before, :tick_after, :guard_names
|
3
|
+
|
4
|
+
def initialize(target, *guard_names, tick_before: true, tick_after: false)
|
5
|
+
self.target = target
|
6
|
+
self.guard_names = guard_names
|
7
|
+
self.tick_before, self.tick_after = tick_before, tick_after
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Execute target method within a new fibril
|
12
|
+
##
|
13
|
+
def method_missing(name, *args, &block)
|
14
|
+
context = target
|
15
|
+
decorated = ->(*args){
|
16
|
+
Fibril.current.tick if tick_before
|
17
|
+
result = block[*args]
|
18
|
+
Fibril.current.tick if tick_after
|
19
|
+
result
|
20
|
+
}
|
21
|
+
fibril{
|
22
|
+
context.send(name, *args, &decorated)
|
23
|
+
}.tap do |guard|
|
24
|
+
guard_names.each do |name|
|
25
|
+
Fibril.guard.send("#{name}=", guard)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|