fibril 0.0.1 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3e934a87b86d8c9cd7be5ed5a25a367bb137ce2
|
4
|
+
data.tar.gz: 55d5321199638e5548ae4d05dd77c368278cef2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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/
|
33
|
-
|
50
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wouterken/fibril.
|
34
51
|
|
35
52
|
## License
|
36
53
|
|
data/examples/example_async.rb
CHANGED
@@ -1,27 +1,13 @@
|
|
1
|
-
|
1
|
+
require "fibril/loop"
|
2
2
|
|
3
3
|
fibril{
|
4
|
-
[1,
|
5
|
-
|
6
|
-
end
|
4
|
+
[1,3,5].each do |i|
|
5
|
+
async.print "#{i}:"
|
6
|
+
end
|
7
7
|
}
|
8
8
|
|
9
9
|
fibril{
|
10
|
-
[4,
|
11
|
-
|
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,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
|
+
}
|
data/examples/example_guard.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
-
|
1
|
+
require 'fibril/loop'
|
2
2
|
|
3
3
|
fibril{
|
4
|
-
|
4
|
+
puts "Waiting on below fibril"
|
5
|
+
await(guard.g1)
|
5
6
|
puts "First finished"
|
6
7
|
}
|
7
8
|
|
8
|
-
|
9
|
+
guard.g1 = fibril{
|
9
10
|
sleep 0.2
|
10
|
-
puts "
|
11
|
-
}
|
11
|
+
puts "Middle finished"
|
12
|
+
}
|
12
13
|
|
13
|
-
|
14
|
-
|
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
|
+
|