fibril 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -3
  3. data/examples/example_async.rb +7 -21
  4. data/examples/example_coop_multi_tasking.rb +33 -0
  5. data/examples/example_enum_tick.rb +9 -0
  6. data/examples/example_execution_order.rb +14 -0
  7. data/examples/example_future_async_await.rb +22 -0
  8. data/examples/example_future_sync_await.rb +23 -0
  9. data/examples/example_guard.rb +9 -7
  10. data/examples/example_guard2.rb +19 -0
  11. data/examples/example_guard3.rb +46 -0
  12. data/examples/example_http.rb +60 -0
  13. data/examples/example_multiple_loops.rb +78 -0
  14. data/examples/example_redis.rb +49 -0
  15. data/examples/{example_loop.rb → example_tick.rb} +1 -1
  16. data/examples/example_tick2.rb +22 -0
  17. data/examples/example_timeout.rb +9 -0
  18. data/fibril.gemspec +1 -1
  19. data/fibril.todo +22 -16
  20. data/lib/fibril.rb +2 -240
  21. data/lib/fibril/async_proxy.rb +28 -0
  22. data/lib/fibril/basic_object.rb +20 -0
  23. data/lib/fibril/control.rb +20 -0
  24. data/lib/fibril/core.rb +299 -0
  25. data/lib/fibril/extras.rb +8 -0
  26. data/lib/fibril/fasync_proxy.rb +36 -0
  27. data/lib/fibril/ffuture.rb +24 -0
  28. data/lib/fibril/fibril_proxy.rb +31 -0
  29. data/lib/fibril/forked_non_blocking_io_wrapper.rb +51 -0
  30. data/lib/fibril/future.rb +24 -0
  31. data/lib/fibril/guard.rb +123 -0
  32. data/lib/fibril/loop.rb +36 -6
  33. data/lib/fibril/non_blocking_io_wrapper.rb +60 -0
  34. data/lib/fibril/tick_proxy.rb +30 -0
  35. data/lib/fibril/version.rb +1 -1
  36. metadata +43 -8
  37. data/examples/example_1.rb +0 -71
  38. data/examples/example_2.rb +0 -80
  39. data/examples/example_3.rb +0 -82
  40. 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
@@ -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
- lines = IO.read(call_stack[0][/.*?(?=\:)/,0]).split("\n")
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
- if %r{require.*".*?fibril/loop"} =~ lines[0].gsub("'",?").gsub(/\s+/,' ').strip
8
- $LOAD_PATH << '.'
9
- fibril{ eval lines[1..-1].join("\n") }
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