polyphony 0.28 → 0.29

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -4
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE +1 -1
  6. data/README.md +23 -21
  7. data/Rakefile +2 -0
  8. data/TODO.md +0 -3
  9. data/docs/_includes/prevnext.html +17 -0
  10. data/docs/_layouts/default.html +106 -0
  11. data/docs/_sass/custom/custom.scss +21 -0
  12. data/docs/faq.md +13 -10
  13. data/docs/getting-started/installing.md +2 -0
  14. data/docs/getting-started/tutorial.md +5 -3
  15. data/docs/index.md +4 -5
  16. data/docs/technical-overview/concurrency.md +21 -19
  17. data/docs/technical-overview/design-principles.md +12 -20
  18. data/docs/technical-overview/exception-handling.md +70 -1
  19. data/docs/technical-overview/extending.md +1 -0
  20. data/docs/technical-overview/fiber-scheduling.md +109 -88
  21. data/docs/user-guide/all-about-timers.md +126 -0
  22. data/docs/user-guide/web-server.md +2 -2
  23. data/docs/user-guide.md +1 -1
  24. data/examples/core/xx-deferring-an-operation.rb +2 -2
  25. data/examples/core/xx-sleep-forever.rb +9 -0
  26. data/examples/core/xx-snooze-starve.rb +16 -0
  27. data/examples/core/xx-spin_error_backtrace.rb +1 -1
  28. data/examples/core/xx-trace.rb +1 -2
  29. data/examples/core/xx-worker-thread.rb +30 -0
  30. data/examples/io/xx-happy-eyeballs.rb +37 -0
  31. data/ext/gyro/gyro.c +8 -3
  32. data/ext/gyro/gyro.h +7 -1
  33. data/ext/gyro/queue.c +35 -3
  34. data/ext/gyro/selector.c +31 -2
  35. data/ext/gyro/thread.c +18 -16
  36. data/lib/polyphony/core/global_api.rb +0 -1
  37. data/lib/polyphony/core/thread_pool.rb +5 -0
  38. data/lib/polyphony/core/throttler.rb +0 -1
  39. data/lib/polyphony/extensions/fiber.rb +14 -3
  40. data/lib/polyphony/extensions/thread.rb +16 -4
  41. data/lib/polyphony/irb.rb +7 -1
  42. data/lib/polyphony/trace.rb +44 -11
  43. data/lib/polyphony/version.rb +1 -1
  44. data/lib/polyphony.rb +1 -0
  45. data/test/helper.rb +1 -3
  46. data/test/test_async.rb +1 -1
  47. data/test/test_cancel_scope.rb +3 -3
  48. data/test/test_fiber.rb +157 -54
  49. data/test/test_global_api.rb +51 -1
  50. data/test/test_gyro.rb +4 -156
  51. data/test/test_io.rb +1 -1
  52. data/test/test_supervisor.rb +2 -2
  53. data/test/test_thread.rb +72 -1
  54. data/test/test_thread_pool.rb +6 -2
  55. data/test/test_throttler.rb +7 -5
  56. data/test/test_trace.rb +6 -6
  57. metadata +10 -5
  58. data/examples/core/xx-extended_fibers.rb +0 -150
  59. data/examples/core/xx-mt-scheduler.rb +0 -349
@@ -16,13 +16,15 @@ class ThrottlerTest < MiniTest::Test
16
16
  end
17
17
 
18
18
  def test_throttler_with_hash_of_rate
19
- t = Polyphony::Throttler.new(rate: 100)
19
+ t = Polyphony::Throttler.new(rate: 20)
20
20
  buffer = []
21
- f = spin { loop { t.process { buffer << 1 } } }
22
- sleep 0.02
21
+ f = spin do
22
+ loop { t.process { buffer << 1 } }
23
+ end
24
+ sleep 0.25
23
25
  f.stop
24
- assert buffer.size >= 2
25
- assert buffer.size <= 3
26
+ puts "count: #{buffer.size}"
27
+ assert (2..6).include?(buffer.size)
26
28
  ensure
27
29
  t.stop
28
30
  end
data/test/test_trace.rb CHANGED
@@ -10,15 +10,15 @@ class TraceTest < MiniTest::Test
10
10
  snooze
11
11
  assert_equal 0, records.size
12
12
  ensure
13
- t.disable
13
+ t&.disable
14
14
  Gyro.trace(nil)
15
15
  end
16
16
 
17
17
  def test_tracing_enabled
18
18
  records = []
19
- t = Polyphony::Trace.new { |r| records << r if r[:event] =~ /^fiber_/ }
20
- t.enable
19
+ t = Polyphony::Trace.new(:fiber_all) { |r| records << r if r[:event] =~ /^fiber_/ }
21
20
  Gyro.trace(true)
21
+ t.enable
22
22
  snooze
23
23
  t.disable
24
24
 
@@ -27,13 +27,13 @@ class TraceTest < MiniTest::Test
27
27
  assert_equal [:fiber_schedule, :fiber_switchpoint, :fiber_run], events
28
28
  assert_equal [Fiber.current], records.map { |r| r[:fiber] }.uniq
29
29
  ensure
30
- t.disable
30
+ t&.disable
31
31
  Gyro.trace(nil)
32
32
  end
33
33
 
34
34
  def test_2_fiber_trace
35
35
  records = []
36
- t = Polyphony::Trace.new { |r| records << r if r[:event] =~ /^fiber_/ }
36
+ t = Polyphony::Trace.new(:fiber_all) { |r| records << r if r[:event] =~ /^fiber_/ }
37
37
  t.enable
38
38
  Gyro.trace(true)
39
39
 
@@ -60,7 +60,7 @@ class TraceTest < MiniTest::Test
60
60
  [Fiber.current, :fiber_run]
61
61
  ], events
62
62
  ensure
63
- t.disable
63
+ t&.disable
64
64
  Gyro.trace(nil)
65
65
  end
66
66
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.28'
4
+ version: '0.29'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-27 00:00:00.000000000 Z
11
+ date: 2020-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -255,6 +255,8 @@ files:
255
255
  - TODO.md
256
256
  - docs/_config.yml
257
257
  - docs/_includes/nav.html
258
+ - docs/_includes/prevnext.html
259
+ - docs/_layouts/default.html
258
260
  - docs/_sass/custom/custom.scss
259
261
  - docs/_sass/overrides.scss
260
262
  - docs/assets/img/echo-fibers.svg
@@ -271,6 +273,7 @@ files:
271
273
  - docs/technical-overview/extending.md
272
274
  - docs/technical-overview/fiber-scheduling.md
273
275
  - docs/user-guide.md
276
+ - docs/user-guide/all-about-timers.md
274
277
  - docs/user-guide/web-server.md
275
278
  - examples/core/01-spinning-up-fibers.rb
276
279
  - examples/core/02-awaiting-fibers.rb
@@ -279,17 +282,17 @@ files:
279
282
  - examples/core/xx-deadlock.rb
280
283
  - examples/core/xx-deferring-an-operation.rb
281
284
  - examples/core/xx-erlang-style-genserver.rb
282
- - examples/core/xx-extended_fibers.rb
283
285
  - examples/core/xx-forking.rb
284
286
  - examples/core/xx-move_on.rb
285
- - examples/core/xx-mt-scheduler.rb
286
287
  - examples/core/xx-queue-async.rb
287
288
  - examples/core/xx-readpartial.rb
288
289
  - examples/core/xx-recurrent-timer.rb
289
290
  - examples/core/xx-resource_cancel.rb
290
291
  - examples/core/xx-resource_delegate.rb
291
292
  - examples/core/xx-signals.rb
293
+ - examples/core/xx-sleep-forever.rb
292
294
  - examples/core/xx-sleeping.rb
295
+ - examples/core/xx-snooze-starve.rb
293
296
  - examples/core/xx-spin_error_backtrace.rb
294
297
  - examples/core/xx-state-machine.rb
295
298
  - examples/core/xx-supervisors.rb
@@ -302,6 +305,7 @@ files:
302
305
  - examples/core/xx-timeout.rb
303
306
  - examples/core/xx-trace.rb
304
307
  - examples/core/xx-using-a-mutex.rb
308
+ - examples/core/xx-worker-thread.rb
305
309
  - examples/interfaces/pg_client.rb
306
310
  - examples/interfaces/pg_notify.rb
307
311
  - examples/interfaces/pg_pool.rb
@@ -317,6 +321,7 @@ files:
317
321
  - examples/io/xx-echo_server.rb
318
322
  - examples/io/xx-echo_server_with_timeout.rb
319
323
  - examples/io/xx-echo_stdin.rb
324
+ - examples/io/xx-happy-eyeballs.rb
320
325
  - examples/io/xx-httparty.rb
321
326
  - examples/io/xx-irb.rb
322
327
  - examples/io/xx-net-http.rb
@@ -440,7 +445,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
440
445
  - !ruby/object:Gem::Version
441
446
  version: '0'
442
447
  requirements: []
443
- rubygems_version: 3.1.2
448
+ rubygems_version: 3.0.6
444
449
  signing_key:
445
450
  specification_version: 4
446
451
  summary: 'Polyphony: Fiber-based Concurrency for Ruby'
@@ -1,150 +0,0 @@
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
@@ -1,349 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fiber'
4
-
5
- ## An experiment to see if the Coprocess class could be implemented as an
6
- ## extension for the stock Fiber (since it's already enhanced)
7
-
8
- class Thread
9
- attr_accessor :main_fiber
10
-
11
- alias_method :orig_initialize, :initialize
12
- def initialize(*args, &block)
13
- orig_initialize do
14
- Fiber.current.setup_main_fiber
15
- block.(*args)
16
- end
17
- end
18
-
19
- def fiber_scheduler
20
- @fiber_scheduler ||= Scheduler.new
21
- end
22
- end
23
-
24
- class Scheduler
25
- def initialize
26
- # @mutex = Mutex.new
27
- @queue = Queue.new
28
- end
29
-
30
- def <<(fiber)
31
- @queue << fiber
32
- # @mutex.synchronize do
33
- # if @head
34
- # @tail.__scheduled_next__ = @tail = fiber
35
- # else
36
- # @head = @tail = fiber
37
- # end
38
- # end
39
- end
40
-
41
- def switch
42
- next_fiber = nil
43
- while true
44
- next_fiber = @queue.empty? ? Thread.current.main_fiber : @queue.pop# unless @queue.empty?
45
- # next_fiber = @queue.empty? ? nil : @queue.pop
46
- # puts "next_fiber: #{next_fiber.inspect}"
47
- # next_fiber = @mutex.synchronize { @head }
48
- break if next_fiber
49
- sleep 0
50
- end
51
-
52
- # next_next_fiber = next_fiber.__scheduled_next__
53
- # next_fiber.__scheduled_next__ = nil
54
- next_fiber.__scheduled__ = nil
55
- # @mutex.synchronize { @head = next_next_fiber || (@tail = nil) }
56
- next_fiber.transfer(next_fiber.__scheduled_value__)
57
- end
58
-
59
- def handle_events
60
- end
61
- end
62
-
63
- class Fiber
64
- def self.spin(&block)
65
- new(&wrap_block(block)).setup(block)
66
- end
67
-
68
- attr_accessor :__block__
69
- attr_reader :scheduler
70
-
71
- def self.wrap_block(block)
72
- calling_fiber = Fiber.current
73
- proc { |v| Fiber.current.run(v, calling_fiber, &block) }
74
- end
75
-
76
- def run(v, calling_fiber)
77
- raise v if v.is_a?(Exception)
78
-
79
- @running = true
80
- result = yield v
81
- @running = nil
82
- schedule_waiting_fibers(result)
83
- @scheduler.switch
84
- rescue Exception => e
85
- @running = nil
86
- parent_fiber = calling_fiber.running? ? calling_fiber : Thread.current.main_fiber
87
- parent_fiber.transfer(e)
88
- end
89
-
90
- def running?
91
- @running
92
- end
93
-
94
- def setup(block)
95
- @scheduler = Thread.current.fiber_scheduler
96
- @__block__ = block
97
- schedule
98
- self
99
- end
100
-
101
- def setup_main_fiber
102
- @scheduler = Thread.current.fiber_scheduler
103
- Thread.current.main_fiber = self
104
- end
105
-
106
- Fiber.current.setup_main_fiber
107
-
108
- attr_reader :__scheduled_value__
109
- # attr_accessor :__scheduled_next__
110
- attr_accessor :__scheduled__
111
-
112
- def inspect
113
- if @__block__
114
- "<Fiber:#{object_id} #{@__block__.source_location.join(':')} (#{@__scheduled_value__.inspect})>"
115
- else
116
- "<Fiber:#{object_id} (main) (#{@__scheduled_value__.inspect})>"
117
- end
118
- end
119
- alias_method :to_s, :inspect
120
-
121
- def self.snooze
122
- current.schedule
123
- yield_to_next
124
- end
125
-
126
- def self.suspend
127
- yield_to_next
128
- end
129
-
130
- def self.yield_to_next
131
- v = current.scheduler.switch
132
- v.is_a?(Exception) ? (raise v) : v
133
- end
134
-
135
- def schedule(value = nil)
136
- return if @__scheduled__
137
-
138
- @__scheduled__ = true
139
- @__scheduled_value__ = value
140
- @scheduler << self
141
- end
142
-
143
- def await
144
- current_fiber = Fiber.current
145
- if @waiting_fiber
146
- if @waiting_fiber.is_a?(Array)
147
- @waiting_fiber << current_fiber
148
- else
149
- @waiting_fiber = [@waiting_fiber, current_fiber]
150
- end
151
- else
152
- @waiting_fiber = current_fiber
153
- end
154
- Fiber.suspend
155
- end
156
-
157
- def schedule_waiting_fibers(v)
158
- case @waiting_fiber
159
- when Array then @waiting_fiber.each { |f| f.schedule(v) }
160
- when Fiber then @waiting_fiber.schedule(v)
161
- end
162
- end
163
- end
164
-
165
- # f1 = Fiber.spin {
166
- # p Fiber.current
167
- # Fiber.snooze
168
- # 3.times {
169
- # STDOUT << '*'
170
- # Fiber.snooze
171
- # }
172
- # :foo
173
- # }
174
-
175
- # f2 = Fiber.spin {
176
- # p Fiber.current
177
- # Fiber.snooze
178
- # 10.times {
179
- # STDOUT << '.'
180
- # Fiber.snooze
181
- # }
182
- # puts
183
- # }
184
-
185
- # v = f1.await
186
- # puts "done waiting #{v.inspect}"
187
-
188
- def test_single_thread(x, y)
189
- x.times {
190
- Fiber.spin {
191
- y.times { |i| Fiber.snooze }
192
- }
193
- }
194
-
195
- Fiber.suspend
196
- end
197
-
198
- def spin_char(char)
199
- Fiber.spin {
200
- loop {
201
- STDOUT << char
202
- Fiber.snooze
203
- }
204
- }
205
- end
206
-
207
- def test_two_threads
208
- Thread.new {
209
- spin_char('.')
210
- spin_char(':')
211
- Fiber.suspend
212
- }
213
-
214
- Thread.new {
215
- spin_char('*')
216
- spin_char('@')
217
- Fiber.suspend
218
- }
219
- end
220
-
221
- # test_two_threads
222
- # sleep
223
-
224
- def test_perf(x, y)
225
- puts "* #{x} fibers #{y} times"
226
- 3.times do
227
- t0 = Time.now
228
- # test_single_thread(1, 1000)
229
- test_single_thread(x, y)
230
- elapsed = Time.now - t0
231
- rate = (x * y / (Time.now - t0)).to_i
232
- puts "#{rate} switches/sec"
233
- end
234
- end
235
-
236
- loop {
237
- test_perf(1, 100000)
238
- }
239
- test_perf(10, 10000)
240
- test_perf(100, 1000)
241
- test_perf(1000, 100)
242
- test_perf(10000, 10)
243
- exit!
244
-
245
- def ping_pong
246
- STDOUT.sync = true
247
- f1 = nil
248
- f2 = nil
249
- count1 = 0
250
- count2 = 0
251
-
252
- Thread.new do
253
- f1 = Fiber.spin {
254
- loop {
255
- count1 += 1
256
- # STDOUT << '.'
257
- f2&.schedule
258
- Fiber.suspend
259
- }
260
- }
261
- Fiber.suspend
262
- end
263
-
264
- Thread.new do
265
- f2 = Fiber.spin {
266
- loop {
267
- count2 += 1
268
- # STDOUT << '*'
269
- f1&.schedule
270
- Fiber.suspend
271
- }
272
- }
273
- Fiber.suspend
274
- end
275
-
276
- Thread.new do
277
- last_count1 = 0
278
- last_count2 = 0
279
- last_t = Time.now
280
- loop {
281
- sleep 1
282
- t = Time.now
283
- e = t - last_t
284
- c1 = count1
285
- c2 = count2
286
- delta1 = c1 - last_count1
287
- delta2 = c2 - last_count2
288
- rate1 = (delta1.to_f / e).to_i
289
- rate2 = (delta2.to_f / e).to_i
290
- puts "#{rate1} #{rate2} (#{rate1 + rate2})"
291
- last_count1 = c1
292
- last_count2 = c2
293
- last_t = t
294
- }
295
- end
296
- end
297
-
298
- ping_pong
299
- sleep
300
- exit!
301
-
302
- def ping_pong_st
303
- STDOUT.sync = true
304
- f1 = nil
305
- f2 = nil
306
- count1 = 0
307
- count2 = 0
308
-
309
- f1 = Fiber.spin {
310
- loop {
311
- count1 += 1
312
- # STDOUT << '.'
313
- f2&.schedule
314
- Fiber.suspend
315
- }
316
- }
317
-
318
- f2 = Fiber.spin {
319
- last_count1 = 0
320
- last_count2 = 0
321
- last_t = Time.now
322
-
323
- loop {
324
- count2 += 1
325
- # STDOUT << '*'
326
- f1&.schedule
327
- Fiber.suspend
328
-
329
- next unless count2 % 100000 == 0
330
-
331
- t = Time.now
332
- e = t - last_t
333
- c1 = count1
334
- c2 = count2
335
- delta1 = c1 - last_count1
336
- delta2 = c2 - last_count2
337
- rate1 = (delta1.to_f / e).to_i
338
- rate2 = (delta2.to_f / e).to_i
339
- puts "#{rate1} #{rate2} (#{rate1 + rate2})"
340
- last_count1 = c1
341
- last_count2 = c2
342
- last_t = t
343
- }
344
- }
345
-
346
- end
347
-
348
- ping_pong_st
349
- Fiber.suspend