polyphony 0.26 → 0.27

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