polyphony 0.24 → 0.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/TODO.md +12 -8
  5. data/docs/README.md +2 -2
  6. data/docs/summary.md +3 -3
  7. data/docs/technical-overview/concurrency.md +4 -6
  8. data/docs/technical-overview/design-principles.md +8 -8
  9. data/docs/technical-overview/exception-handling.md +1 -1
  10. data/examples/core/{01-spinning-up-coprocesses.rb → 01-spinning-up-fibers.rb} +1 -1
  11. data/examples/core/{02-awaiting-coprocesses.rb → 02-awaiting-fibers.rb} +3 -3
  12. data/examples/core/xx-erlang-style-genserver.rb +10 -10
  13. data/examples/core/xx-extended_fibers.rb +150 -0
  14. data/examples/core/xx-sleeping.rb +9 -0
  15. data/examples/core/xx-supervisors.rb +1 -1
  16. data/examples/interfaces/pg_pool.rb +3 -3
  17. data/examples/performance/mem-usage.rb +19 -4
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -5
  19. data/ext/gyro/gyro.c +9 -15
  20. data/lib/polyphony/core/cancel_scope.rb +0 -2
  21. data/lib/polyphony/core/exceptions.rb +2 -2
  22. data/lib/polyphony/core/global_api.rb +7 -8
  23. data/lib/polyphony/core/supervisor.rb +25 -31
  24. data/lib/polyphony/extensions/core.rb +4 -78
  25. data/lib/polyphony/extensions/fiber.rb +166 -0
  26. data/lib/polyphony/extensions/io.rb +2 -1
  27. data/lib/polyphony/version.rb +1 -1
  28. data/lib/polyphony.rb +6 -8
  29. data/test/test_async.rb +2 -2
  30. data/test/test_cancel_scope.rb +6 -6
  31. data/test/test_fiber.rb +382 -0
  32. data/test/test_global_api.rb +49 -50
  33. data/test/test_gyro.rb +1 -1
  34. data/test/test_io.rb +30 -29
  35. data/test/test_kernel.rb +2 -2
  36. data/test/test_signal.rb +1 -1
  37. data/test/test_supervisor.rb +27 -27
  38. data/test/test_timer.rb +2 -2
  39. metadata +7 -7
  40. data/examples/core/04-no-auto-run.rb +0 -16
  41. data/lib/polyphony/core/coprocess.rb +0 -168
  42. data/test/test_coprocess.rb +0 -440
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a8da629de739b6091367c7220fd4a69dd83d7c78eb6d8c55c67385c8ca3edb2
4
- data.tar.gz: 351f78d8b9635b6bc2c9838d652781a66009554287cfb5486c9fe0a0748ed6d6
3
+ metadata.gz: c47fca95a744d36163d87e80cd0e56a23ae9a613d1df4970475db7a7b8840ba3
4
+ data.tar.gz: eebe2e1f3dd6fd3efe80cd60444c881978d7d0ff34d8c381fdd4324e77ef14a6
5
5
  SHA512:
6
- metadata.gz: 768ed2703dd01653e5c71c2d17799b0c42fd9eb03f382dd3e4d3e4a337f7055d7a68643f479160febd3aa9412a9b66ccf0adf79f1608882ef0d61b625c8f7ed2
7
- data.tar.gz: eab163c7ce680640def7fe6971359969202e6cb53238e8006c479e158c9b488bf73a2e575008a94cee1320fcf6899276da85df33d2fb00f97fa8133ad8fc3f06
6
+ metadata.gz: a266b7d3ef74d98a3a43bf4f821e3894295bae084a4ca7f067c3e63b1600ea6c6191e9fe39caa72166c0dba759bca5dc0cb47312de76e45b7028ce3c231f5166
7
+ data.tar.gz: d3cb27c65c01fa8984d88ba047fbfb30d08660a5afcfe3636af6302e47d3b45b5ba3908419eb8a867ed7ed96aa83d3ec03eadf7dd03542de04095fe7cf64c5f5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ 0.25 2020-01-10
2
+ ---------------
3
+
4
+ * Fold `Coprocess` functionality into `Fiber`
5
+ * Add support for indefinite `#sleep`
6
+
1
7
  0.24 2020-01-08
2
8
  ---------------
3
9
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.24)
4
+ polyphony (0.25)
5
5
  modulation (~> 1.0)
6
6
 
7
7
  GEM
data/TODO.md CHANGED
@@ -1,14 +1,18 @@
1
- ## 0.25 Move Other interface code into separate gem
1
+ ## 0.25 Merge Coprocess functionality into Fiber
2
2
 
3
- - Pull out redis/postgres code, put into new `polyphony-contrib` gem
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 Full Rack adapter implementation
13
+ ## 0.26 Move Other interface code into separate gem
6
14
 
7
- - Work better mechanism supervising multiple coprocesses (`when_done` feels a
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 coprocess-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.
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: coprocesses, supervisors, cancel scopes, throttling, resource pools etc.
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 coprocesses](howto/coprocesses.md)
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
- * [Polyphony::CancelScope](#)
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
- ## Coprocesses - Polyphony's basic unit of concurrency
15
+ ## Fibers - Polyphony's basic unit of concurrency
16
16
 
17
- While stock Ruby fibers can be used with Polyphony without any problem, the API they provide is very basic, and necessitates writing quite a bit of boilerplate code whenever they need to be synchronized, interrupted or otherwise controlled. For this reason, Polyphony provides entities that encapsulate fibers and provide a richer API, making it easier to compose concurrent applications employing fibers.
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 coprocesses. Methods like `cancel_after` and `move_on_after` allow interrupting a coprocess that's blocking on any arbitrary operation.
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 coprocesses. They offer enhanced exception handling and can be nested to create complex supervision trees ala [Erlang](https://adoptingerlang.org/docs/development/supervision_trees/).
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/auto_run'
14
+ require 'polyphony'
15
15
 
16
- 10.times {
17
- # start 10 coprocesses, each sleeping for 1 second
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 coprocesses:
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
- coprocess = spin {
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 coprocesses
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 coprocesses, nested one within the other. An exception is raised in the inner most coprocess, and having no exception handler, will bubble up through the different enclosing coprocesses, 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.
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 coprocesses, each sleeping for the given duration.
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 coprocesses is by using `Coprocess#await`, which blocks
13
- # until the coprocess has finished running or has been interrupted.
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 coprocess to terminate'
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
- coprocess = spin do
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(coprocess, receiver)
16
+ build_api(fiber, receiver)
17
17
  snooze
18
- coprocess
18
+ fiber
19
19
  end
20
20
 
21
- def self.build_api(coprocess, receiver)
21
+ def self.build_api(fiber, receiver)
22
22
  receiver.methods(false).each do |m|
23
23
  if m =~ /!$/
24
- coprocess.define_singleton_method(m) do |*args|
25
- GenServer.cast(coprocess, m, *args)
24
+ fiber.define_singleton_method(m) do |*args|
25
+ GenServer.cast(fiber, m, *args)
26
26
  end
27
27
  else
28
- coprocess.define_singleton_method(m) do |*args|
29
- GenServer.call(coprocess, m, *args)
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: Polyphony::Coprocess.current,
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: Polyphony::Coprocess.current,
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
@@ -3,6 +3,15 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ spin {
9
+ 10.times {
10
+ STDOUT << '.'
11
+ sleep 0.1
12
+ }
13
+ }
14
+
6
15
  puts 'going to sleep...'
7
16
  sleep 1
8
17
  puts 'woke up'
@@ -15,7 +15,7 @@ supervise do |s|
15
15
  s.spin { my_sleep(2) }
16
16
  s.spin { my_sleep(3) }
17
17
  s.spin do
18
- puts "fiber count: #{Polyphony::Coprocess.list.size}"
18
+ puts "fiber count: #{Fiber.count}"
19
19
  end
20
20
  end
21
21
  puts "#{Time.now} done waiting"
@@ -11,7 +11,7 @@ PGOPTS = {
11
11
  sslmode: 'require'
12
12
  }.freeze
13
13
 
14
- DBPOOL = Polyphony::ResourcePool.new(limit: 8) { PG.connect(PGOPTS) }
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
- coprocs = CONCURRENCY.times.map do
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
- coprocs.each(&:interrupt)
43
+ fibers.each(&:interrupt)
@@ -1,7 +1,7 @@
1
1
  require 'fiber'
2
2
 
3
3
  def mem_usage
4
- `ps -o rss #{$$}`.strip.split.last.to_i
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 calculate_coprocess_memory_cost(count)
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 "coprocess memory cost: #{cost}KB"
47
+ puts "extended fiber memory cost: #{cost}KB"
33
48
  end
34
49
 
35
- calculate_coprocess_memory_cost(10000)
50
+ calculate_extended_fiber_memory_cost(10000)
@@ -54,11 +54,7 @@ spin do
54
54
  client = server.accept
55
55
  spin { handle_client(client) }
56
56
  end
57
- rescue Exception => e
58
- puts "uncaught exception: #{e.inspect}"
59
- puts e.backtrace.join("\n")
60
- exit!
61
- server.close
62
57
  end
63
58
 
59
+ puts "pid #{Process.pid}"
64
60
  suspend
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, ID_done) != Qnil))
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 SYM_PAUSED;
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 coprocesses. These exceptions allow
6
- # control of coprocesses. Interrupt exceptions can encapsulate a value and thus
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