polyphony 0.26 → 0.27

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +34 -14
  6. data/examples/core/xx-mt-scheduler.rb +349 -0
  7. data/examples/core/xx-queue-async.rb +120 -0
  8. data/examples/core/xx-readpartial.rb +18 -0
  9. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  10. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  11. data/examples/core/xx-thread-sleep.rb +17 -0
  12. data/examples/core/xx-thread-snooze.rb +34 -0
  13. data/examples/performance/snooze.rb +5 -5
  14. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +73 -0
  15. data/examples/performance/thread-vs-fiber/polyphony_server.rb +12 -4
  16. data/ext/gyro/async.c +58 -61
  17. data/ext/gyro/child.c +50 -59
  18. data/ext/gyro/gyro.c +84 -192
  19. data/ext/gyro/gyro.h +29 -2
  20. data/ext/gyro/gyro_ext.c +6 -0
  21. data/ext/gyro/io.c +87 -96
  22. data/ext/gyro/queue.c +109 -0
  23. data/ext/gyro/selector.c +117 -0
  24. data/ext/gyro/signal.c +44 -54
  25. data/ext/gyro/socket.c +15 -20
  26. data/ext/gyro/thread.c +203 -0
  27. data/ext/gyro/timer.c +79 -50
  28. data/ext/libev/ev.c +3 -0
  29. data/lib/polyphony.rb +7 -12
  30. data/lib/polyphony/core/global_api.rb +5 -1
  31. data/lib/polyphony/core/throttler.rb +6 -11
  32. data/lib/polyphony/extensions/core.rb +2 -0
  33. data/lib/polyphony/extensions/fiber.rb +11 -13
  34. data/lib/polyphony/extensions/thread.rb +52 -0
  35. data/lib/polyphony/version.rb +1 -1
  36. data/test/helper.rb +8 -3
  37. data/test/test_fiber.rb +2 -2
  38. data/test/test_global_api.rb +4 -5
  39. data/test/test_gyro.rb +3 -2
  40. data/test/test_io.rb +1 -0
  41. data/test/test_supervisor.rb +3 -3
  42. data/test/test_thread.rb +44 -0
  43. data/test/test_throttler.rb +41 -0
  44. data/test/test_timer.rb +13 -7
  45. metadata +17 -6
  46. data/examples/core/xx-thread_cancel.rb +0 -28
  47. data/examples/performance/thread.rb +0 -27
  48. data/lib/polyphony/core/thread.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d97b4ef9db1f6463947a3d5e0e300ee0155bd576ee878a4866557a2cc97cf785
4
- data.tar.gz: 8fa0f7e075ce3c7bd9cf28270d06b0fbfe399657571aeaa23773400cb6535f58
3
+ metadata.gz: f3c496d74c1fb5545a3111d9a116c55268cde00298feaea546dab8ba199fb359
4
+ data.tar.gz: 57810b0cac851c5364ed831549a5d06beef73b13f9f6615d2d9ee0558397ccfd
5
5
  SHA512:
6
- metadata.gz: ac3eb507f068f54f70f5e1a79d964c39943f7ac7cfa945da02847f59ec53fc23739b2633428258d0caaf91bfdacc25cfcb6fa73175f8d43c7ed38fdd81f0a360
7
- data.tar.gz: 75402a0ca525da7aa02fbe933c4f76328d9272ecb9dd7e404003b85a92e327882ca50d63eed49140593628cf0989d1a8cf7aac6abab5ad766124b1e5e7d00348
6
+ metadata.gz: b0df4d81349f7226544a158e4f46b2e24f71db9496c5ccee0911634f848d0bdaaebc7ac9ae231e8a29892858212f6782a4f4d8a639121249b13cf4793c133482
7
+ data.tar.gz: 9ac4943327e356c2bf8f6a5a72d75787d1e71983c2aec6784eb6daa242bee3f79c49c379e716b2fb8fc235bf48fc7d2a50825f24a541c129dab12d0b34b1da9b
@@ -62,7 +62,7 @@ Style/ClassVars:
62
62
  Exclude:
63
63
  - lib/polyphony/core/coprocess.rb
64
64
 
65
- Layout/AlignHash:
65
+ Layout/HashAlignment:
66
66
  EnforcedColonStyle: table
67
67
  EnforcedHashRocketStyle: table
68
68
 
@@ -76,7 +76,7 @@ Naming/MethodName:
76
76
  Exclude:
77
77
  - test/test_signal.rb
78
78
 
79
- Lint/HandleExceptions:
79
+ Lint/SuppressedException:
80
80
  Exclude:
81
81
  - lib/polyphony/http/server/http1.rb
82
82
  - lib/polyphony/http/server/http2.rb
@@ -126,7 +126,7 @@ Style/FormatStringToken:
126
126
  - test/**/*.rb
127
127
  - examples/**/*.rb
128
128
 
129
- Naming/UncommunicativeMethodParamName:
129
+ Naming/MethodParameterName:
130
130
  Exclude:
131
131
  - test/**/*.rb
132
132
  - examples/**/*.rb
@@ -1,3 +1,11 @@
1
+ 0.27 2020-01-19
2
+ ---------------
3
+
4
+ * Reimplement `Throttler` using recurring timer
5
+ * Add `Gyro::Selector` for wrapping libev
6
+ * Add `Gyro::Queue`, a fiber-aware thread-safe queue
7
+ * Implement multithreaded fiber scheduling
8
+
1
9
  0.26 2020-01-12
2
10
  ---------------
3
11
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.26)
4
+ polyphony (0.27)
5
5
  modulation (~> 1.0)
6
6
 
7
7
  GEM
data/TODO.md CHANGED
@@ -1,20 +1,16 @@
1
- ## 0.26 Real IO#gets and IO#read
1
+ ## 0.27 Multithreaded fiber scheduling
2
2
 
3
- - More tests
4
- - Implement some basic stuff missing:
5
- - override `IO#eof?` since it too reads into buffer
6
- - real `IO#gets` (with buffering)
7
- - `IO#read` (read to EOF)
8
- - `IO.foreach`
9
- - `Process.waitpid`
3
+ - `Gyro_schedule_fiber` - schedule using fiber's associated thread (store thread
4
+ ref in fiber), instead of current thread
5
+ - Check why first call to `#sleep` returns too early in tests. Check the
6
+ sleep behaviour in a spawned thread.
10
7
 
11
- ## 0.27 Working Sinatra application
8
+ ## 0.28 Working Sinatra application
12
9
 
13
- - Pull out redis/postgres code, put into new `polyphony-xxx` gems
14
10
  - app with database access (postgresql)
15
11
  - benchmarks!
16
12
 
17
- ## 0.28 Sidekick
13
+ ## 0.29 Sidekick
18
14
 
19
15
  Plan of action:
20
16
 
@@ -22,11 +18,35 @@ Plan of action:
22
18
  - test performance
23
19
  - proceed from there
24
20
 
25
- ## 0.29 Testing && Docs
21
+ ## 0.30 Testing && Docs
22
+
23
+ - Pull out redis/postgres code, put into new `polyphony-xxx` gems
24
+
25
+ ## 0.31 Integration
26
+
27
+ ## 0.32 Real IO#gets and IO#read
28
+
29
+ - More tests
30
+ - Implement some basic stuff missing:
31
+ - override `IO#eof?` since it too reads into buffer
32
+ - real `IO#gets` (with buffering)
33
+ - `IO#read` (read to EOF)
34
+ - `IO.foreach`
35
+ - `Process.waitpid`
36
+
37
+ ## 0.32 Support for multithreaded apps
38
+
39
+ - Move fiber scheduling to the `Thread` class
40
+ - Gyro selector conforming to the selector interface:
41
+
42
+ ```ruby
43
+ class Selector
44
+ def wait
45
+ end
46
+ ```
26
47
 
27
- ## 0.30 Integration
48
+ - Better separation between
28
49
 
29
- - Sidekick
30
50
  - Rails?
31
51
 
32
52
  # DNS
@@ -0,0 +1,349 @@
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