polyphony 0.24 → 0.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +12 -8
- data/docs/README.md +2 -2
- data/docs/summary.md +3 -3
- data/docs/technical-overview/concurrency.md +4 -6
- data/docs/technical-overview/design-principles.md +8 -8
- data/docs/technical-overview/exception-handling.md +1 -1
- data/examples/core/{01-spinning-up-coprocesses.rb → 01-spinning-up-fibers.rb} +1 -1
- data/examples/core/{02-awaiting-coprocesses.rb → 02-awaiting-fibers.rb} +3 -3
- data/examples/core/xx-erlang-style-genserver.rb +10 -10
- data/examples/core/xx-extended_fibers.rb +150 -0
- data/examples/core/xx-sleeping.rb +9 -0
- data/examples/core/xx-supervisors.rb +1 -1
- data/examples/interfaces/pg_pool.rb +3 -3
- data/examples/performance/mem-usage.rb +19 -4
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -5
- data/ext/gyro/gyro.c +9 -15
- data/lib/polyphony/core/cancel_scope.rb +0 -2
- data/lib/polyphony/core/exceptions.rb +2 -2
- data/lib/polyphony/core/global_api.rb +7 -8
- data/lib/polyphony/core/supervisor.rb +25 -31
- data/lib/polyphony/extensions/core.rb +4 -78
- data/lib/polyphony/extensions/fiber.rb +166 -0
- data/lib/polyphony/extensions/io.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +6 -8
- data/test/test_async.rb +2 -2
- data/test/test_cancel_scope.rb +6 -6
- data/test/test_fiber.rb +382 -0
- data/test/test_global_api.rb +49 -50
- data/test/test_gyro.rb +1 -1
- data/test/test_io.rb +30 -29
- data/test/test_kernel.rb +2 -2
- data/test/test_signal.rb +1 -1
- data/test/test_supervisor.rb +27 -27
- data/test/test_timer.rb +2 -2
- metadata +7 -7
- data/examples/core/04-no-auto-run.rb +0 -16
- data/lib/polyphony/core/coprocess.rb +0 -168
- data/test/test_coprocess.rb +0 -440
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c47fca95a744d36163d87e80cd0e56a23ae9a613d1df4970475db7a7b8840ba3
|
4
|
+
data.tar.gz: eebe2e1f3dd6fd3efe80cd60444c881978d7d0ff34d8c381fdd4324e77ef14a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a266b7d3ef74d98a3a43bf4f821e3894295bae084a4ca7f067c3e63b1600ea6c6191e9fe39caa72166c0dba759bca5dc0cb47312de76e45b7028ce3c231f5166
|
7
|
+
data.tar.gz: d3cb27c65c01fa8984d88ba047fbfb30d08660a5afcfe3636af6302e47d3b45b5ba3908419eb8a867ed7ed96aa83d3ec03eadf7dd03542de04095fe7cf64c5f5
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
-
## 0.25
|
1
|
+
## 0.25 Merge Coprocess functionality into Fiber
|
2
2
|
|
3
|
-
-
|
3
|
+
- Merge `Coprocess` functionality into `Fiber`
|
4
|
+
- Get rid of the duality Coprocess - Fiber
|
5
|
+
- exception handling
|
6
|
+
- interrupting (`MoveOn`/`Cancel`)
|
7
|
+
- message passing (`receive`/`send`)
|
8
|
+
- Clear separation between scheduling code and event handling code
|
9
|
+
- Check performance difference using `http_server.rb`. We should expect a
|
10
|
+
modest increase in throughput, as well as significantly less memory usage.
|
11
|
+
- Handle calls to `#sleep` without duration (should just `#suspend`)
|
4
12
|
|
5
|
-
## 0.26
|
13
|
+
## 0.26 Move Other interface code into separate gem
|
6
14
|
|
7
|
-
-
|
8
|
-
bit hacky)
|
9
|
-
- Add supervisor test
|
10
|
-
- Homogenize HTTP 1 and HTTP 2 headers - upcase ? downcase ?
|
11
|
-
- find some demo Rack apps and test with Polyphony
|
15
|
+
- Pull out redis/postgres code, put into new `polyphony-contrib` gem
|
12
16
|
|
13
17
|
## 0.27 Working Sinatra application
|
14
18
|
|
data/docs/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
> 1. _Music_ the style of simultaneously combining a number of parts, each forming an individual melody and harmonizing with each other.
|
5
5
|
> 2. _Programming_ a Ruby gem for concurrent programming focusing on performance and developer happiness.
|
6
6
|
|
7
|
-
Polyphony is a library for building concurrent applications in Ruby. Polyphony harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html) to provide a cooperative, sequential
|
7
|
+
Polyphony is a library for building concurrent applications in Ruby. Polyphony harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html) to provide a cooperative, sequential coroutine-based concurrency model. Under the hood, Polyphony uses [libev](https://github.com/enki/libev) as a high-performance event reactor that provides timers, I/O watchers and other asynchronous event primitives.
|
8
8
|
|
9
9
|
Polyphony makes it possible to use normal Ruby built-in classes like `IO`, and `Socket` in a concurrent fashion without having to resort to threads. Polyphony takes care of context-switching automatically whenever a blocking call like `Socket#accept` or `IO#read` is issued.
|
10
10
|
|
@@ -13,7 +13,7 @@ Polyphony makes it possible to use normal Ruby built-in classes like `IO`, and `
|
|
13
13
|
* Co-operative scheduling of concurrent tasks using Ruby fibers.
|
14
14
|
* High-performance event reactor for handling I/O events and timers.
|
15
15
|
* Natural, sequential programming style that makes it easy to reason about concurrent code.
|
16
|
-
* Abstractions and constructs for controlling the execution of concurrent code:
|
16
|
+
* Abstractions and constructs for controlling the execution of concurrent code: supervisors, cancel scopes, throttling, resource pools etc.
|
17
17
|
* Code can use native networking classes and libraries, growing support for third-party gems such as `pg` and `redis`.
|
18
18
|
* Use stdlib classes such as `TCPServer` and `TCPSocket` and `Net::HTTP`.
|
19
19
|
* Competitive performance and scalability characteristics, in terms of both throughput and memory consumption.
|
data/docs/summary.md
CHANGED
@@ -23,7 +23,7 @@
|
|
23
23
|
* [Use timers](howto/timers.md)
|
24
24
|
* [Throttle recurrent operations](howto/throttle.md)
|
25
25
|
* [Cancel ongoing operations](howto/cancel.md)
|
26
|
-
* [Control
|
26
|
+
* [Control fibers](howto/fibers.md)
|
27
27
|
* [Synchronize concurrent operations](howto/synchronize.md)
|
28
28
|
* [Perform CPU-bound operations](howto/cpu-bound.md)
|
29
29
|
* [Control backpressure](howto/backpressure.md)
|
@@ -43,8 +43,7 @@
|
|
43
43
|
|
44
44
|
## API Reference
|
45
45
|
|
46
|
-
* [
|
47
|
-
* [Polyphony::Coprocess](#)
|
46
|
+
* [Fiber](#)
|
48
47
|
* [Gyro](#)
|
49
48
|
* [Gyro::Async](#)
|
50
49
|
* [Gyro::Child](#)
|
@@ -52,6 +51,7 @@
|
|
52
51
|
* [Gyro::Timer](#)
|
53
52
|
* [Kernel](#)
|
54
53
|
* [Polyphony](#)
|
54
|
+
* [Polyphony::CancelScope](#)
|
55
55
|
* [Polyphony::Mutex](#)
|
56
56
|
* [Polyphony::Pulser](#)
|
57
57
|
* [Polyphony::ResourcePool](#)
|
@@ -12,19 +12,17 @@ Polyphony takes Ruby's fibers and adds a way to schedule and switch between fibe
|
|
12
12
|
|
13
13
|
Writing concurrent applications using Polyphony's fiber-based concurrency model offers a significant performance advantage. Computational tasks can be broken down into many fine-grained concurrent operations that cost very little in memory and context-switching time. More importantly, this concurrency model lets developers express their ideas in a sequential manner, leading to source code that is easy to read and reason about.
|
14
14
|
|
15
|
-
##
|
15
|
+
## Fibers - Polyphony's basic unit of concurrency
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
A coprocess can be thought of as a fiber with enhanced powers. It makes sure any exception raised while it's running is [handled correctly](exception-handling.md). It can be interrupted or `await`ed \(just like `Thread#join`\). It provides methods for controlling its execution. Moreover, coprocesses can pass messages between themselves, turning them into autonomous actors in a fine-grained concurrent environment.
|
17
|
+
Polyphony extends the core `Fiber` class with additional functionality that allows scheduling, synchronizing, interrupting and otherwise controlling running fibers. Polyphony makes sure any exception raised while a is running is [handled correctly](exception-handling.md). Moreover, fibers can communicate with each other using message passing, turning them into autonomous actors in a fine-grained concurrent environment.
|
20
18
|
|
21
19
|
## Higher-Order Concurrency Constructs
|
22
20
|
|
23
|
-
Polyphony also provides several methods and constructs for controlling multiple
|
21
|
+
Polyphony also provides several methods and constructs for controlling multiple fibers. Methods like `cancel_after` and `move_on_after` allow interrupting a fiber that's blocking on any arbitrary operation.
|
24
22
|
|
25
23
|
Cancel scopes \(borrowed from the brilliant Python library [Trio](https://trio.readthedocs.io/en/stable/)\) allows cancelling ongoing operations for any reason with more control over cancelling behaviour.
|
26
24
|
|
27
|
-
Supervisors allow controlling multiple
|
25
|
+
Supervisors allow controlling multiple fibers. They offer enhanced exception handling and can be nested to create complex supervision trees ala [Erlang](https://adoptingerlang.org/docs/development/supervision_trees/).
|
28
26
|
|
29
27
|
Some other constructs offered by Polyphony:
|
30
28
|
|
@@ -11,14 +11,14 @@ library. Polyphony's design is based on the following principles:
|
|
11
11
|
should be no calls to initialize the event reactor, or other ceremonial code:
|
12
12
|
|
13
13
|
```ruby
|
14
|
-
require 'polyphony
|
14
|
+
require 'polyphony'
|
15
15
|
|
16
|
-
10
|
17
|
-
|
18
|
-
spin { sleep 1 }
|
19
|
-
}
|
16
|
+
# start 10 fibers, each sleeping for 1 second
|
17
|
+
10.times { spin { sleep 1 } }
|
20
18
|
|
21
19
|
puts 'going to sleep now'
|
20
|
+
# wait for other fibers to terminate
|
21
|
+
sleep
|
22
22
|
```
|
23
23
|
|
24
24
|
- Blocking operations should yield to other concurrent tasks without any
|
@@ -27,7 +27,7 @@ library. Polyphony's design is based on the following principles:
|
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
# in Polyphony, I/O ops block the current fiber, but implicitly yield to other
|
30
|
-
# concurrent
|
30
|
+
# concurrent fibers:
|
31
31
|
clients.each { |client|
|
32
32
|
spin { client.puts 'Elvis has left the chatroom' }
|
33
33
|
}
|
@@ -40,7 +40,7 @@ library. Polyphony's design is based on the following principles:
|
|
40
40
|
more compact and more legible:
|
41
41
|
|
42
42
|
```ruby
|
43
|
-
|
43
|
+
fiber = spin {
|
44
44
|
move_on_after(3) {
|
45
45
|
do_something_slow
|
46
46
|
}
|
@@ -66,7 +66,7 @@ library. Polyphony's design is based on the following principles:
|
|
66
66
|
cancel scopes:
|
67
67
|
|
68
68
|
```ruby
|
69
|
-
# wait for multiple
|
69
|
+
# wait for multiple fibers
|
70
70
|
supervise { |s|
|
71
71
|
clients.each { |client|
|
72
72
|
s.spin { client.puts 'Elvis has left the chatroom' }
|
@@ -83,5 +83,5 @@ spin do
|
|
83
83
|
end.await
|
84
84
|
```
|
85
85
|
|
86
|
-
In this example, there are four
|
86
|
+
In this example, there are four fibers, nested one within the other. An exception is raised in the inner most fiber, and having no exception handler, will bubble up through the different enclosing fibers, until reaching the top-most level, that of the root fiber, at which point the exception will cause the program to halt and print an error message.
|
87
87
|
|
@@ -10,7 +10,7 @@ def nap(tag, t)
|
|
10
10
|
puts "#{Time.now} #{tag} done napping"
|
11
11
|
end
|
12
12
|
|
13
|
-
# We launch two concurrent
|
13
|
+
# We launch two concurrent fibers, each sleeping for the given duration.
|
14
14
|
spin { nap(:a, 1) }
|
15
15
|
spin { nap(:b, 2) }
|
16
16
|
|
@@ -9,10 +9,10 @@ sleeper = spin do
|
|
9
9
|
puts 'woke up'
|
10
10
|
end
|
11
11
|
|
12
|
-
# One way to synchronize
|
13
|
-
# until the
|
12
|
+
# One way to synchronize fibers is by using `Fiber#await`, which blocks
|
13
|
+
# until the fiber has finished running or has been interrupted.
|
14
14
|
waiter = spin do
|
15
|
-
puts 'waiting for
|
15
|
+
puts 'waiting for fiber to terminate'
|
16
16
|
sleeper.await
|
17
17
|
puts 'done waiting'
|
18
18
|
end
|
@@ -5,7 +5,7 @@ require 'polyphony'
|
|
5
5
|
|
6
6
|
class GenServer
|
7
7
|
def self.start(receiver, *args)
|
8
|
-
|
8
|
+
fiber = spin do
|
9
9
|
state = receiver.initial_state(*args)
|
10
10
|
loop do
|
11
11
|
msg = receive
|
@@ -13,20 +13,20 @@ class GenServer
|
|
13
13
|
msg[:from] << reply unless reply == :noreply
|
14
14
|
end
|
15
15
|
end
|
16
|
-
build_api(
|
16
|
+
build_api(fiber, receiver)
|
17
17
|
snooze
|
18
|
-
|
18
|
+
fiber
|
19
19
|
end
|
20
20
|
|
21
|
-
def self.build_api(
|
21
|
+
def self.build_api(fiber, receiver)
|
22
22
|
receiver.methods(false).each do |m|
|
23
23
|
if m =~ /!$/
|
24
|
-
|
25
|
-
GenServer.cast(
|
24
|
+
fiber.define_singleton_method(m) do |*args|
|
25
|
+
GenServer.cast(fiber, m, *args)
|
26
26
|
end
|
27
27
|
else
|
28
|
-
|
29
|
-
GenServer.call(
|
28
|
+
fiber.define_singleton_method(m) do |*args|
|
29
|
+
GenServer.call(fiber, m, *args)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -34,7 +34,7 @@ class GenServer
|
|
34
34
|
|
35
35
|
def self.cast(process, method, *args)
|
36
36
|
process << {
|
37
|
-
from:
|
37
|
+
from: Fiber.current,
|
38
38
|
method: method,
|
39
39
|
args: args
|
40
40
|
}
|
@@ -42,7 +42,7 @@ class GenServer
|
|
42
42
|
|
43
43
|
def self.call(process, method, *args)
|
44
44
|
process << {
|
45
|
-
from:
|
45
|
+
from: Fiber.current,
|
46
46
|
method: method,
|
47
47
|
args: args
|
48
48
|
}
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'fiber'
|
5
|
+
|
6
|
+
## An experiment to see if the Coprocess class could be implemented as an
|
7
|
+
## extension for the stock Fiber (since it's already enhanced)
|
8
|
+
|
9
|
+
class Fiber
|
10
|
+
def self.spin(&block)
|
11
|
+
new(&wrap_block(block)).set_block(block).tap { |f| f.schedule }
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :__block__
|
15
|
+
|
16
|
+
def self.wrap_block(block)
|
17
|
+
calling_fiber = Fiber.current
|
18
|
+
proc { |v| Fiber.current.run(v, calling_fiber, &block) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(v, calling_fiber)
|
22
|
+
raise v if v.is_a?(Exception)
|
23
|
+
|
24
|
+
@running = true
|
25
|
+
result = yield v
|
26
|
+
@running = nil
|
27
|
+
schedule_waiting_fibers(result)
|
28
|
+
Fiber.run_next_fiber
|
29
|
+
rescue Exception => e
|
30
|
+
@running = nil
|
31
|
+
parent_fiber = calling_fiber.running? ? calling_fiber : Fiber.main_fiber
|
32
|
+
parent_fiber.transfer(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
def running?
|
36
|
+
@running
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_block(block)
|
40
|
+
@__block__ = block
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
if @__block__
|
46
|
+
"<Fiber:#{object_id} #{@__block__.source_location.join(':')} (#{__scheduled_value__.inspect})>"
|
47
|
+
else
|
48
|
+
"<Fiber:#{object_id} (main) (#{__scheduled_value__.inspect})>"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
alias_method :to_s, :inspect
|
52
|
+
|
53
|
+
# scheduling
|
54
|
+
@@scheduled_head = nil
|
55
|
+
@@scheduled_tail = nil
|
56
|
+
|
57
|
+
attr_accessor :__scheduled_next__
|
58
|
+
attr_accessor :__scheduled_value__
|
59
|
+
|
60
|
+
def self.snooze
|
61
|
+
current.schedule
|
62
|
+
yield_to_next
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.suspend
|
66
|
+
yield_to_next
|
67
|
+
end
|
68
|
+
|
69
|
+
@@main_fiber = Fiber.current
|
70
|
+
|
71
|
+
def self.main_fiber
|
72
|
+
@@main_fiber
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.yield_to_next
|
76
|
+
v = Fiber.run_next_fiber
|
77
|
+
v.is_a?(Exception) ? (raise v) : v
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.run_next_fiber
|
81
|
+
unless @@scheduled_head
|
82
|
+
return main_fiber.transfer
|
83
|
+
end
|
84
|
+
|
85
|
+
next_fiber = @@scheduled_head
|
86
|
+
next_next_fiber = @@scheduled_head.__scheduled_next__
|
87
|
+
next_fiber.__scheduled_next__ = nil
|
88
|
+
if next_next_fiber
|
89
|
+
@@scheduled_head = next_next_fiber
|
90
|
+
else
|
91
|
+
@@scheduled_head = @@scheduled_tail = nil
|
92
|
+
end
|
93
|
+
next_fiber.transfer(next_fiber.__scheduled_value__)
|
94
|
+
end
|
95
|
+
|
96
|
+
def schedule(value = nil)
|
97
|
+
@__scheduled_value__ = value
|
98
|
+
if @@scheduled_head
|
99
|
+
@@scheduled_tail.__scheduled_next__ = self
|
100
|
+
@@scheduled_tail = self
|
101
|
+
else
|
102
|
+
@@scheduled_head = @@scheduled_tail = self
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def await
|
107
|
+
current_fiber = Fiber.current
|
108
|
+
if @waiting_fiber
|
109
|
+
if @waiting_fiber.is_a?(Array)
|
110
|
+
@waiting_fiber << current_fiber
|
111
|
+
else
|
112
|
+
@waiting_fiber = [@waiting_fiber, current_fiber]
|
113
|
+
end
|
114
|
+
else
|
115
|
+
@waiting_fiber = current_fiber
|
116
|
+
end
|
117
|
+
Fiber.suspend
|
118
|
+
end
|
119
|
+
|
120
|
+
def schedule_waiting_fibers(v)
|
121
|
+
case @waiting_fiber
|
122
|
+
when Array then @waiting_fiber.each { |f| f.schedule(v) }
|
123
|
+
when Fiber then @waiting_fiber.schedule(v)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
f1 = Fiber.spin {
|
129
|
+
p Fiber.current
|
130
|
+
Fiber.snooze
|
131
|
+
3.times {
|
132
|
+
STDOUT << '*'
|
133
|
+
Fiber.snooze
|
134
|
+
}
|
135
|
+
:foo
|
136
|
+
}
|
137
|
+
|
138
|
+
f2 = Fiber.spin {
|
139
|
+
p Fiber.current
|
140
|
+
Fiber.snooze
|
141
|
+
10.times {
|
142
|
+
STDOUT << '.'
|
143
|
+
Fiber.snooze
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
# v = f1.await
|
148
|
+
# puts "done waiting #{v.inspect}"
|
149
|
+
|
150
|
+
Fiber.suspend
|
@@ -11,7 +11,7 @@ PGOPTS = {
|
|
11
11
|
sslmode: 'require'
|
12
12
|
}.freeze
|
13
13
|
|
14
|
-
DBPOOL = Polyphony::ResourcePool.new(limit:
|
14
|
+
DBPOOL = Polyphony::ResourcePool.new(limit: 16) { PG.connect(PGOPTS) }
|
15
15
|
|
16
16
|
def get_records(db)
|
17
17
|
db.query('select pg_sleep(0.001) as test')
|
@@ -28,7 +28,7 @@ DBPOOL.preheat!
|
|
28
28
|
t0 = Time.now
|
29
29
|
count = 0
|
30
30
|
|
31
|
-
|
31
|
+
fibers = CONCURRENCY.times.map do
|
32
32
|
spin do
|
33
33
|
loop do
|
34
34
|
DBPOOL.acquire do |db|
|
@@ -40,4 +40,4 @@ coprocs = CONCURRENCY.times.map do
|
|
40
40
|
end
|
41
41
|
sleep 5
|
42
42
|
puts "count: #{count} query rate: #{count / (Time.now - t0)} queries/s"
|
43
|
-
|
43
|
+
fibers.each(&:interrupt)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'fiber'
|
2
2
|
|
3
3
|
def mem_usage
|
4
|
-
`ps -o rss #{$$}`.
|
4
|
+
`ps -o rss #{$$}`.split.last.to_i
|
5
5
|
end
|
6
6
|
|
7
7
|
def calculate_fiber_memory_cost(count)
|
@@ -17,10 +17,25 @@ end
|
|
17
17
|
|
18
18
|
calculate_fiber_memory_cost(10000)
|
19
19
|
|
20
|
+
def calculate_thread_memory_cost(count)
|
21
|
+
GC.disable
|
22
|
+
rss0 = mem_usage
|
23
|
+
count.times { Thread.new { sleep 1 } }
|
24
|
+
sleep 0.5
|
25
|
+
rss1 = mem_usage
|
26
|
+
sleep 0.5
|
27
|
+
GC.start
|
28
|
+
cost = (rss1 - rss0).to_f / count
|
29
|
+
|
30
|
+
puts "thread memory cost: #{cost}KB"
|
31
|
+
end
|
32
|
+
|
33
|
+
calculate_thread_memory_cost(500)
|
34
|
+
|
20
35
|
require 'bundler/setup'
|
21
36
|
require 'polyphony'
|
22
37
|
|
23
|
-
def
|
38
|
+
def calculate_extended_fiber_memory_cost(count)
|
24
39
|
GC.disable
|
25
40
|
rss0 = mem_usage
|
26
41
|
count.times { spin { :foo } }
|
@@ -29,7 +44,7 @@ def calculate_coprocess_memory_cost(count)
|
|
29
44
|
GC.start
|
30
45
|
cost = (rss1 - rss0).to_f / count
|
31
46
|
|
32
|
-
puts "
|
47
|
+
puts "extended fiber memory cost: #{cost}KB"
|
33
48
|
end
|
34
49
|
|
35
|
-
|
50
|
+
calculate_extended_fiber_memory_cost(10000)
|
data/ext/gyro/gyro.c
CHANGED
@@ -14,7 +14,6 @@ static VALUE Gyro_suspend(VALUE self);
|
|
14
14
|
static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self);
|
15
15
|
static VALUE Fiber_schedule(int argc, VALUE *argv, VALUE self);
|
16
16
|
static VALUE Fiber_state(VALUE self);
|
17
|
-
static VALUE Fiber_mark_as_done(VALUE self);
|
18
17
|
|
19
18
|
static void Gyro_clear_scheduled_fibers();
|
20
19
|
|
@@ -29,11 +28,11 @@ static VALUE scheduled_tail;
|
|
29
28
|
ID ID_call;
|
30
29
|
ID ID_caller;
|
31
30
|
ID ID_clear;
|
32
|
-
ID ID_done;
|
33
31
|
ID ID_each;
|
34
32
|
ID ID_inspect;
|
35
33
|
ID ID_raise;
|
36
34
|
ID ID_read_watcher;
|
35
|
+
ID ID_running;
|
37
36
|
ID ID_scheduled;
|
38
37
|
ID ID_scheduled_next;
|
39
38
|
ID ID_scheduled_value;
|
@@ -45,8 +44,8 @@ ID ID_RW;
|
|
45
44
|
|
46
45
|
VALUE SYM_DEAD;
|
47
46
|
VALUE SYM_RUNNING;
|
48
|
-
VALUE SYM_PAUSED;
|
49
47
|
VALUE SYM_SCHEDULED;
|
48
|
+
VALUE SYM_SUSPENDED;
|
50
49
|
|
51
50
|
void Init_Gyro() {
|
52
51
|
mGyro = rb_define_module("Gyro");
|
@@ -69,16 +68,15 @@ void Init_Gyro() {
|
|
69
68
|
rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
|
70
69
|
rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
|
71
70
|
rb_define_method(cFiber, "state", Fiber_state, 0);
|
72
|
-
rb_define_method(cFiber, "mark_as_done!", Fiber_mark_as_done, 0);
|
73
71
|
|
74
72
|
ID_call = rb_intern("call");
|
75
73
|
ID_caller = rb_intern("caller");
|
76
74
|
ID_clear = rb_intern("clear");
|
77
|
-
ID_done = rb_intern("done");
|
78
75
|
ID_each = rb_intern("each");
|
79
76
|
ID_inspect = rb_intern("inspect");
|
80
77
|
ID_raise = rb_intern("raise");
|
81
78
|
ID_read_watcher = rb_intern("read_watcher");
|
79
|
+
ID_running = rb_intern("@running");
|
82
80
|
ID_scheduled = rb_intern("scheduled");
|
83
81
|
ID_scheduled_next = rb_intern("scheduled_next");
|
84
82
|
ID_scheduled_value = rb_intern("scheduled_value");
|
@@ -90,12 +88,12 @@ void Init_Gyro() {
|
|
90
88
|
|
91
89
|
SYM_DEAD = ID2SYM(rb_intern("dead"));
|
92
90
|
SYM_RUNNING = ID2SYM(rb_intern("running"));
|
93
|
-
SYM_PAUSED = ID2SYM(rb_intern("paused"));
|
94
91
|
SYM_SCHEDULED = ID2SYM(rb_intern("scheduled"));
|
92
|
+
SYM_SUSPENDED = ID2SYM(rb_intern("suspended"));
|
95
93
|
rb_global_variable(&SYM_DEAD);
|
96
94
|
rb_global_variable(&SYM_RUNNING);
|
97
|
-
rb_global_variable(&SYM_PAUSED);
|
98
95
|
rb_global_variable(&SYM_SCHEDULED);
|
96
|
+
rb_global_variable(&SYM_SUSPENDED);
|
99
97
|
|
100
98
|
scheduled_head = Qnil;
|
101
99
|
scheduled_tail = Qnil;
|
@@ -161,8 +159,9 @@ static VALUE Gyro_suspend(VALUE self) {
|
|
161
159
|
if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
|
162
160
|
return rb_funcall(rb_mKernel, ID_raise, 1, ret);
|
163
161
|
}
|
164
|
-
else
|
162
|
+
else {
|
165
163
|
return ret;
|
164
|
+
}
|
166
165
|
}
|
167
166
|
|
168
167
|
static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
|
@@ -181,17 +180,12 @@ static VALUE Fiber_schedule(int argc, VALUE *argv, VALUE self) {
|
|
181
180
|
}
|
182
181
|
|
183
182
|
static VALUE Fiber_state(VALUE self) {
|
184
|
-
if (!rb_fiber_alive_p(self) || (rb_ivar_get(self,
|
183
|
+
if (!rb_fiber_alive_p(self) || (rb_ivar_get(self, ID_running) == Qfalse))
|
185
184
|
return SYM_DEAD;
|
186
185
|
if (rb_fiber_current() == self) return SYM_RUNNING;
|
187
186
|
if (rb_ivar_get(self, ID_scheduled) != Qnil) return SYM_SCHEDULED;
|
188
187
|
|
189
|
-
return
|
190
|
-
}
|
191
|
-
|
192
|
-
static VALUE Fiber_mark_as_done(VALUE self) {
|
193
|
-
rb_ivar_set(self, ID_done, Qtrue);
|
194
|
-
return self;
|
188
|
+
return SYM_SUSPENDED;
|
195
189
|
}
|
196
190
|
|
197
191
|
VALUE Gyro_await() {
|
@@ -22,7 +22,6 @@ class CancelScope
|
|
22
22
|
def cancel!
|
23
23
|
@cancelled = true
|
24
24
|
@fibers.each do |f|
|
25
|
-
f.cancelled = true
|
26
25
|
f.schedule error_class.new(self, @opts[:value])
|
27
26
|
end
|
28
27
|
@on_cancel&.()
|
@@ -57,7 +56,6 @@ class CancelScope
|
|
57
56
|
def call
|
58
57
|
fiber = Fiber.current
|
59
58
|
@fibers << fiber
|
60
|
-
fiber.cancelled = nil
|
61
59
|
yield self
|
62
60
|
rescue Exceptions::MoveOn => e
|
63
61
|
e.scope == self ? e.value : raise(e)
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
export :Interrupt, :MoveOn, :Cancel
|
4
4
|
|
5
|
-
# Common exception class for interrupting
|
6
|
-
# control of
|
5
|
+
# Common exception class for interrupting fibers. These exceptions allow
|
6
|
+
# control of fibers. Interrupt exceptions can encapsulate a value and thus
|
7
7
|
# provide a way to interrupt long-running blocking operations while still
|
8
8
|
# passing a value back to the call site. Interrupt exceptions can also
|
9
9
|
# references a cancel scope in order to allow correct bubbling of exceptions
|