polyphony 0.28 → 0.29

Sign up to get free protection for your applications and to get access to all the features.
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