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