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