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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +34 -14
- data/examples/core/xx-mt-scheduler.rb +349 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/performance/snooze.rb +5 -5
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +73 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +12 -4
- data/ext/gyro/async.c +58 -61
- data/ext/gyro/child.c +50 -59
- data/ext/gyro/gyro.c +84 -192
- data/ext/gyro/gyro.h +29 -2
- data/ext/gyro/gyro_ext.c +6 -0
- data/ext/gyro/io.c +87 -96
- data/ext/gyro/queue.c +109 -0
- data/ext/gyro/selector.c +117 -0
- data/ext/gyro/signal.c +44 -54
- data/ext/gyro/socket.c +15 -20
- data/ext/gyro/thread.c +203 -0
- data/ext/gyro/timer.c +79 -50
- data/ext/libev/ev.c +3 -0
- data/lib/polyphony.rb +7 -12
- data/lib/polyphony/core/global_api.rb +5 -1
- data/lib/polyphony/core/throttler.rb +6 -11
- data/lib/polyphony/extensions/core.rb +2 -0
- data/lib/polyphony/extensions/fiber.rb +11 -13
- data/lib/polyphony/extensions/thread.rb +52 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +8 -3
- data/test/test_fiber.rb +2 -2
- data/test/test_global_api.rb +4 -5
- data/test/test_gyro.rb +3 -2
- data/test/test_io.rb +1 -0
- data/test/test_supervisor.rb +3 -3
- data/test/test_thread.rb +44 -0
- data/test/test_throttler.rb +41 -0
- data/test/test_timer.rb +13 -7
- metadata +17 -6
- data/examples/core/xx-thread_cancel.rb +0 -28
- data/examples/performance/thread.rb +0 -27
- data/lib/polyphony/core/thread.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3c496d74c1fb5545a3111d9a116c55268cde00298feaea546dab8ba199fb359
|
4
|
+
data.tar.gz: 57810b0cac851c5364ed831549a5d06beef73b13f9f6615d2d9ee0558397ccfd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0df4d81349f7226544a158e4f46b2e24f71db9496c5ccee0911634f848d0bdaaebc7ac9ae231e8a29892858212f6782a4f4d8a639121249b13cf4793c133482
|
7
|
+
data.tar.gz: 9ac4943327e356c2bf8f6a5a72d75787d1e71983c2aec6784eb6daa242bee3f79c49c379e716b2fb8fc235bf48fc7d2a50825f24a541c129dab12d0b34b1da9b
|
data/.rubocop.yml
CHANGED
@@ -62,7 +62,7 @@ Style/ClassVars:
|
|
62
62
|
Exclude:
|
63
63
|
- lib/polyphony/core/coprocess.rb
|
64
64
|
|
65
|
-
Layout/
|
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/
|
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/
|
129
|
+
Naming/MethodParameterName:
|
130
130
|
Exclude:
|
131
131
|
- test/**/*.rb
|
132
132
|
- examples/**/*.rb
|
data/CHANGELOG.md
CHANGED
@@ -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
|
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,20 +1,16 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.27 Multithreaded fiber scheduling
|
2
2
|
|
3
|
-
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
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
|