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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cf7d153b66e754a9c54176ad4aed2f7b6e5b0055
4
- data.tar.gz: 57d3036405946c6c82bfce1cea816c119d5f71fd
3
+ metadata.gz: d3e934a87b86d8c9cd7be5ed5a25a367bb137ce2
4
+ data.tar.gz: 55d5321199638e5548ae4d05dd77c368278cef2d
5
5
  SHA512:
6
- metadata.gz: 75bb606b9261c086109e971235f29fc624371db80f428e653b9918766d2346f674b4ce03e1a16ebb76239c8e22377a37b9a4b5ab3ef6e2b433eb5717f06b6479
7
- data.tar.gz: d104f2d54afe2eeef3da5caae50b4d782576b7bb396caca58fd0986213c29eb0d9c87a45ce5844f3630b43dc5d67037546ba6d2ab5d1d9546af3ad5d1da47e96
6
+ metadata.gz: b462c4ed53203e9ee173d8267c5db43e9e53546da210dc9988b8c87e7ddcb746dd08970c4b2a7e90fdd8f1a5b84e1a697423de4f26a4296d61981015b86e80f9
7
+ data.tar.gz: ca8cb1567003a08491489f3503e3c88b494a6b1ee662a6a435881e69210d5e56ed8d8a1da8761fd8b0c0c99fae47de167eb0acac841078339f33409db161245b
data/README.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Fibril
2
2
 
3
+ Fibril is a pure Ruby library that allows you to use cooperative multitasking inside your Ruby code. It is a similar concept to the event loop in JavaScript, but makes use of context switching through Fibers to prevent the need for callback functions or promises.
4
+
5
+ Traditionally you might approach concurrency in Ruby through the use of threads combined with concurrency primitives to synchronise critical sections of your code. Fibril takes an alternative approach where instead everything is synchronous and safe from concurrent state mutations unless explicitly told otherwise.
6
+
7
+ You can use Fibril to yield from a flow of execution while waiting on an asychrouous call and fibril will schedule the same flow to resume as soon the asynchronous call is complete. All without the need for callbacks.
8
+
9
+ You can be explicit in how you weave your fibrils together to ensure the order in which your multiple tasks execute is deterministic.
10
+
11
+ ## Why is it useful?
12
+ You may be interested in Fibril if:
13
+ * Your code has many fast operations that are often blocked by slow operations
14
+ * You want to use two or more Ruby libraries which require a blocking IO loop together.
15
+ * You have multiple IO operations that you wish to execute in parallel while ensuring the rest of your code executes synchronously
16
+ * You want to manipulate multiple streams of data in parallel without having to worry about synchronisation across threads.
17
+
18
+ In scenarios where many IO bound operations are the performance bottleneck of your application Fibril is likely to provide performance benefits. In other scenarios performance should be comparable to that using threads or executing all tasks synchronously.
19
+
20
+ At its simplest Fibril will allow you to write source code that looks shallow and linear while allowing multiple flows of execution to complete concurrently.
3
21
 
4
22
  ## Installation
5
23
 
@@ -19,7 +37,7 @@ Or install it yourself as:
19
37
 
20
38
  ## Usage
21
39
 
22
- TODO: Write usage instructions here
40
+ Read the wiki [here](https://github.com/wouterken/Fibril/wiki) and examine the [examples](https://github.com/wouterken/Fibril/tree/master/examples) in this repository to learn how to use Fibril.
23
41
 
24
42
  ## Development
25
43
 
@@ -29,8 +47,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
29
47
 
30
48
  ## Contributing
31
49
 
32
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/fibril.
33
-
50
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wouterken/fibril.
34
51
 
35
52
  ## License
36
53
 
@@ -1,27 +1,13 @@
1
- require_relative "../lib/fibril/loop"
1
+ require "fibril/loop"
2
2
 
3
3
  fibril{
4
- [1,2,3].async.each do |i|
5
- puts i
6
- end
4
+ [1,3,5].each do |i|
5
+ async.print "#{i}:"
6
+ end
7
7
  }
8
8
 
9
9
  fibril{
10
- [4,5,6].async.each do |i|
11
- puts i
12
- end
10
+ [2,4,6].each do |i|
11
+ async.print "#{i}:"
12
+ end
13
13
  }
14
- # Kernel.send :alias_method, :old_puts, :puts
15
- # Fibril::async :puts
16
-
17
- # puts 1
18
- # puts 2
19
- # puts 3
20
-
21
- # puts 4
22
- # puts 5
23
- # puts 6
24
-
25
- # sleep
26
-
27
- # old_puts "Done!"
@@ -0,0 +1,33 @@
1
+ require "fibril/loop"
2
+
3
+ def long_running_task
4
+ sleep 1
5
+ puts "Part 1 of long running task"
6
+ Fibril.current.tick
7
+ sleep 1
8
+ puts "Part 2 of long running task"
9
+ Fibril.current.tick
10
+ sleep 1
11
+ puts "Part 3 of long running task"
12
+ Fibril.current.tick
13
+ sleep 1
14
+ puts "Part 4 of long running task"
15
+ Fibril.current.tick
16
+ end
17
+
18
+ fibril.long_running_task
19
+
20
+ fibril(:short_task_one){
21
+ puts "I'm a short task"
22
+ }
23
+
24
+ fibril(:short_task_one){
25
+ puts "I'm also a short task"
26
+ }
27
+
28
+ await(:short_task_one){
29
+ fibril(:short_task_three){
30
+ puts "I'm a third short task"
31
+ }
32
+ }
33
+
@@ -0,0 +1,9 @@
1
+ require "fibril/loop"
2
+
3
+ [1,3,5].each.fibril do |i|
4
+ print "#{i},"
5
+ end
6
+
7
+ [2,4,6].each.fibril do |i|
8
+ print "#{i},"
9
+ end
@@ -0,0 +1,14 @@
1
+ require 'fibril/loop'
2
+
3
+ fibril{
4
+ puts "I'm executed second"
5
+ fibril{
6
+ puts "I'm executed last"
7
+ }
8
+ }
9
+
10
+ fibril{
11
+ puts "I'm executed third"
12
+ }
13
+
14
+ puts "I'm executed first"
@@ -0,0 +1,22 @@
1
+ require 'fibril/loop'
2
+
3
+ pending = future{ sleep 1; 3 }
4
+ pending2 = future{ sleep 0.1; 4 }
5
+
6
+ fibril{
7
+ puts "Enter First"
8
+ async.await(pending)
9
+ puts "Leave First"
10
+ }
11
+
12
+ fibril{
13
+ puts "Enter Second"
14
+ result = async.await_all pending, pending2
15
+ puts "Leave Second"
16
+ }
17
+
18
+ fibril{
19
+ puts "Enter Third"
20
+ async.await(pending2)
21
+ puts "Leave Third"
22
+ }
@@ -0,0 +1,23 @@
1
+ require 'fibril/loop'
2
+
3
+ pending = future{ sleep 1; 3 }
4
+ pending2 = future{ sleep 0.1; 4 }
5
+
6
+ fibril{
7
+ puts "Enter First"
8
+ await(pending)
9
+ puts "Leave First"
10
+ }
11
+
12
+ fibril{
13
+ puts "Enter Second"
14
+ await pending
15
+ await pending2
16
+ puts "Leave Second"
17
+ }
18
+
19
+ fibril{
20
+ puts "Enter Third"
21
+ await(pending2)
22
+ puts "Leave Third"
23
+ }
@@ -1,15 +1,17 @@
1
- require_relative "../lib/fibril/loop"
1
+ require 'fibril/loop'
2
2
 
3
3
  fibril{
4
- variables.guard.await
4
+ puts "Waiting on below fibril"
5
+ await(guard.g1)
5
6
  puts "First finished"
6
7
  }
7
8
 
8
- variables.guard = fibril{
9
+ guard.g1 = fibril{
9
10
  sleep 0.2
10
- puts "Second finished"
11
- }.until{ true }
11
+ puts "Middle finished"
12
+ }
12
13
 
13
- await(variables.guard){
14
- puts "Guard depleted"
14
+ puts "Waiting on above"
15
+ await(guard.g1){
16
+ puts "Third finished"
15
17
  }
@@ -0,0 +1,19 @@
1
+ require 'fibril/loop'
2
+
3
+ fibril(:g1){
4
+ sleep 1
5
+ 4
6
+ }
7
+
8
+ fibril(:g2){
9
+ 5
10
+ }
11
+
12
+ fibril{
13
+ puts "Sum is " + (await(:g1) + await(:g2)).to_s
14
+ puts "Sum is " + (await(guard.g1) + await(guard.g2)).to_s
15
+ }
16
+
17
+ await(:g2){|res|
18
+ puts "g2 available immediately: #{res}"
19
+ }
@@ -0,0 +1,46 @@
1
+ require 'fibril/loop'
2
+
3
+ [*0...10].tick(:result_one).map do |i|
4
+ print "Item-1-#{i}, "
5
+ i * 3
6
+ end
7
+
8
+ [*10...20].tick(:result_two).map do |i|
9
+ print "Item-2-#{i}, "
10
+ i * 11
11
+ end
12
+
13
+ fibril(:result_three){
14
+ async.sleep 0.5
15
+ puts "Three finished"
16
+ }
17
+
18
+ await(guard.result_one, guard.result_two){
19
+ puts "\nSum guard one & two:"
20
+ puts (await guard.result_one).zip((await guard.result_two)).inject(:+).to_s
21
+ await(guard.result_three){
22
+ puts "Continued!"
23
+ }
24
+ }
25
+
26
+ await(guard.result_one){
27
+ puts "\nGuard one results:"
28
+ puts "#{await guard.result_one}"
29
+ puts "Guard two results: #{await guard.result_two}"
30
+ }
31
+
32
+
33
+ fibril{
34
+ res = await(guard.result_four, guard.result_five)
35
+ puts "Res is #{res}"
36
+ }
37
+
38
+ fibril(:result_four){
39
+ async.sleep 0.5
40
+ "hello"
41
+ }
42
+
43
+ fibril(:result_five){
44
+ async.sleep 0.1
45
+ "world"
46
+ }
@@ -0,0 +1,60 @@
1
+ require 'net/http'
2
+ require 'fibril/loop'
3
+
4
+ def get_response_code(url, async=false)
5
+ url = URI.parse(url)
6
+ req = Net::HTTP::Get.new(url.to_s)
7
+ res = async ?
8
+ Net::HTTP.async.start(url.host, url.port, use_ssl: true) {|http|
9
+ http.request(req)
10
+ }
11
+ : Net::HTTP.start(url.host, url.port, use_ssl: true) {|http|
12
+ http.request(req)
13
+ }
14
+ variables.http_response_code2 = res.code
15
+ end
16
+
17
+
18
+ starts = Time.now
19
+ Fibril.profile(:sync_http){
20
+ get_response_code('https://www.google.com')
21
+ get_response_code('https://nz.news.yahoo.com/')
22
+ get_response_code('https://github.com')
23
+ get_response_code('https://www.engadget.com')
24
+ }
25
+ puts "Sync v1 took #{Time.now - starts}"
26
+
27
+ part_one = fibril{
28
+ starts = Time.now
29
+ fibril(:google).get_response_code('https://www.google.com', true)
30
+ fibril(:engadget).get_response_code('https://www.engadget.com', true)
31
+ fibril(:yahoo).get_response_code('https://nz.news.yahoo.com/', true)
32
+ fibril(:github).get_response_code('https://github.com',true)
33
+
34
+ fibril{
35
+ puts "#{await(:google, :yahoo, :github, :engadget)}"
36
+ puts "Async v1 took #{Time.now - starts}"
37
+ }
38
+ }
39
+
40
+ await(part_one){
41
+ start2 = Time.now
42
+ fibril(:a_engadget){
43
+ async.get_response_code('https://www.engadget.com')
44
+ }
45
+ fibril(:a_google){
46
+ async.get_response_code('https://www.google.com')
47
+ }
48
+ fibril(:a_yahoo){
49
+ async.get_response_code('https://nz.news.yahoo.com/')
50
+ }
51
+ fibril(:a_github){
52
+ async.get_response_code('https://github.com')
53
+ }
54
+
55
+
56
+ await(:a_google, :a_yahoo, :a_github, :a_engadget){|google, yahoo, github, engadget|
57
+ puts "#{[google, yahoo, github, engadget]}"
58
+ puts "Async v2 took #{Time.now - start2}"
59
+ }
60
+ }
@@ -0,0 +1,78 @@
1
+ require 'fibril/loop'
2
+ require 'net/http'
3
+
4
+ def print_inline(lines, clear=true)
5
+ puts lines
6
+ system("tput cuu #{lines.length}") if clear
7
+ end
8
+
9
+ ###
10
+ # Calculate fibonacci numbers up until a limit
11
+ ##
12
+ fibril(:fibonacci) do
13
+ variables.fib0, variables.fib1 = variables.fib1 || 1, variables.fib0.to_i + (variables.fib1 || 1)
14
+ end.until{ variables.fib0 > 1000_000_000_000_000_000_000 }
15
+
16
+
17
+ ###
18
+ # Calculate primes up until a limit
19
+ ##
20
+ fibril do
21
+ limit = 500
22
+ primes = [false] + ([true] * (limit - 1))
23
+ primes.each.with_index(1).tick(:primes).map do |prime, i|
24
+ prime ? (mult = i) : next
25
+ variables.prime = variables.prime ? variables.prime + ", #{i}" : "#{i}"
26
+ primes[(mult += i) - 1] = false while mult <= limit - i
27
+ variables.prime
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Make an HTTP request
33
+ ##
34
+ fibril(:http) do
35
+ url = URI.parse('http://www.example.com/index.html')
36
+ req = Net::HTTP::Get.new(url.to_s)
37
+ res = Net::HTTP.async.start(url.host, url.port) {|http|
38
+ http.request(req)
39
+ }
40
+ variables.http_response_code = res.code
41
+ end
42
+
43
+ ##
44
+ # Make an HTTP request
45
+ ##
46
+ fibril(:http2) do
47
+ url = URI.parse('https://www.facebook.com')
48
+ req = Net::HTTP::Get.new(url.to_s)
49
+ res = Net::HTTP.async.start(url.host, url.port, use_ssl: true) {|http|
50
+ http.request(req)
51
+ }
52
+ variables.http_response_code2 = res.code
53
+ end
54
+
55
+ ##
56
+ # Print status of above currently executing fibrils above
57
+ ##
58
+ fibril(:print_loop){
59
+ print_inline [
60
+ "Fibonacci: #{variables.fib1}",
61
+ "Prime: #{variables.prime.to_s[-18..-1]}",
62
+ "HTTP Response code (example.com): #{variables.http_response_code}",
63
+ "HTTP Response code (facebook.com): #{variables.http_response_code2}"
64
+ ]
65
+ }.until(:fibonacci, :primes, :http)
66
+
67
+ ##
68
+ # Print final result status after all fibrils have completed
69
+ ##
70
+ await(:print_loop){
71
+ print_inline [
72
+ "Fibonacci: #{variables.fib1}",
73
+ "Prime: #{variables.prime.to_s[-18..-1]}",
74
+ "HTTP Response code (example.com): #{await :http}",
75
+ "HTTP Response code (facebook.com): #{await :http2}",
76
+ "Finished!"
77
+ ], false
78
+ }
@@ -0,0 +1,49 @@
1
+ require 'fibril/loop'
2
+ require 'redis'
3
+ ##
4
+ # Example non blocking IO wrapper for Redis IO
5
+ ##
6
+ class RedisPubSubWrapper < Fibril::NonBlockingIOWrapper
7
+ attr_accessor :pub, :sub, :channel_name
8
+
9
+ def initialize(channel_name)
10
+ self.channel_name = channel_name
11
+ self.pub, self.sub = Redis.new, Redis.new
12
+ super{ sub.subscribe(channel_name){|on| on.message(&receive) }}
13
+ end
14
+
15
+ def publish(msg)
16
+ pub.publish(channel_name, msg)
17
+ end
18
+ end
19
+
20
+
21
+ ##
22
+ #
23
+ # Example:
24
+ #
25
+ ##
26
+
27
+ def publish(redis)
28
+ $total_invocation_count ||= 0
29
+ $total_invocation_count += 1
30
+ async.sleep 0.1
31
+ puts "SEND: #{"M1:I#{$total_invocation_count}"}"
32
+ redis.async.publish("M1:I#{$total_invocation_count}")
33
+ puts "SEND: #{"M2:I#{$total_invocation_count}"}"
34
+ redis.async.publish("M2:I#{$total_invocation_count}")
35
+ puts "SEND: #{"M3:I#{$total_invocation_count}"}"
36
+ redis.async.publish("M3:I#{$total_invocation_count}")
37
+ end
38
+
39
+ def recv(redis)
40
+ $total_recv_count ||= 0
41
+ $total_recv_count += 1
42
+ _channel, message = redis.await
43
+ puts "RECV: #{message}: total: #{$total_recv_count}"
44
+ end
45
+
46
+ redis = RedisPubSubWrapper.new 'test'
47
+ fibril.publish(redis).loop(10)
48
+ fibril.recv(redis).loop(30)
49
+