polyphony 0.44.0 → 0.45.5
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 +8 -1
- data/CHANGELOG.md +41 -0
- data/Gemfile.lock +14 -8
- data/Rakefile +1 -1
- data/TODO.md +12 -15
- data/docs/_posts/2020-07-26-polyphony-0.44.md +77 -0
- data/docs/api-reference/thread.md +1 -1
- data/docs/getting-started/overview.md +14 -14
- data/docs/getting-started/tutorial.md +1 -1
- data/examples/adapters/redis_client.rb +3 -1
- data/examples/adapters/redis_pubsub_perf.rb +11 -8
- data/examples/adapters/sequel_mysql.rb +1 -1
- data/examples/adapters/sequel_pg.rb +24 -0
- data/examples/core/{02-awaiting-fibers.rb → await.rb} +0 -0
- data/examples/core/{xx-channels.rb → channels.rb} +0 -0
- data/examples/core/deferring-an-operation.rb +16 -0
- data/examples/core/{xx-erlang-style-genserver.rb → erlang-style-genserver.rb} +16 -9
- data/examples/core/{xx-forking.rb → forking.rb} +1 -1
- data/examples/core/handling-signals.rb +11 -0
- data/examples/core/{03-interrupting.rb → interrupt.rb} +0 -0
- data/examples/core/{xx-pingpong.rb → pingpong.rb} +7 -5
- data/examples/core/{xx-recurrent-timer.rb → recurrent-timer.rb} +1 -1
- data/examples/core/{xx-resource_delegate.rb → resource_delegate.rb} +3 -4
- data/examples/core/{01-spinning-up-fibers.rb → spin.rb} +1 -1
- data/examples/core/{xx-spin_error_backtrace.rb → spin_error_backtrace.rb} +1 -1
- data/examples/core/{xx-supervise-process.rb → supervise-process.rb} +8 -5
- data/examples/core/supervisor.rb +20 -0
- data/examples/core/{xx-thread-sleep.rb → thread-sleep.rb} +0 -0
- data/examples/core/{xx-thread_pool.rb → thread_pool.rb} +0 -0
- data/examples/core/{xx-throttling.rb → throttling.rb} +0 -0
- data/examples/core/{xx-timeout.rb → timeout.rb} +0 -0
- data/examples/core/{xx-using-a-mutex.rb → using-a-mutex.rb} +0 -0
- data/examples/core/{xx-worker-thread.rb → worker-thread.rb} +2 -2
- data/examples/io/{xx-backticks.rb → backticks.rb} +0 -0
- data/examples/io/{xx-echo_client.rb → echo_client.rb} +1 -1
- data/examples/io/{xx-echo_client_from_stdin.rb → echo_client_from_stdin.rb} +2 -2
- data/examples/io/{xx-echo_pipe.rb → echo_pipe.rb} +1 -1
- data/examples/io/{xx-echo_server.rb → echo_server.rb} +0 -0
- data/examples/io/{xx-echo_server_with_timeout.rb → echo_server_with_timeout.rb} +1 -1
- data/examples/io/{xx-echo_stdin.rb → echo_stdin.rb} +0 -0
- data/examples/io/{xx-happy-eyeballs.rb → happy-eyeballs.rb} +0 -0
- data/examples/io/{xx-httparty.rb → httparty.rb} +4 -13
- data/examples/io/{xx-irb.rb → irb.rb} +0 -0
- data/examples/io/{xx-net-http.rb → net-http.rb} +0 -0
- data/examples/io/{xx-open.rb → open.rb} +0 -0
- data/examples/io/pry.rb +18 -0
- data/examples/io/rack_server.rb +71 -0
- data/examples/io/raw.rb +14 -0
- data/examples/io/reline.rb +18 -0
- data/examples/io/{xx-system.rb → system.rb} +1 -1
- data/examples/io/{xx-tcpserver.rb → tcpserver.rb} +0 -0
- data/examples/io/{xx-tcpsocket.rb → tcpsocket.rb} +0 -0
- data/examples/io/tunnel.rb +6 -1
- data/examples/io/{xx-zip.rb → zip.rb} +0 -0
- data/examples/performance/fiber_transfer.rb +2 -1
- data/examples/performance/fs_read.rb +5 -6
- data/examples/performance/multi_snooze.rb +0 -1
- data/examples/{io/xx-switch.rb → performance/switch.rb} +2 -1
- data/examples/performance/thread-vs-fiber/{xx-httparty_multi.rb → httparty_multi.rb} +3 -4
- data/examples/performance/thread-vs-fiber/{xx-httparty_threaded.rb → httparty_threaded.rb} +0 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
- data/examples/performance/thread-vs-fiber/threaded_server.rb +1 -5
- data/examples/performance/thread_pool_perf.rb +6 -7
- data/ext/polyphony/backend.h +40 -0
- data/ext/polyphony/event.c +3 -3
- data/ext/polyphony/extconf.rb +1 -1
- data/ext/polyphony/fiber.c +90 -13
- data/ext/polyphony/{libev_agent.c → libev_backend.c} +226 -224
- data/ext/polyphony/polyphony.c +5 -7
- data/ext/polyphony/polyphony.h +18 -18
- data/ext/polyphony/polyphony_ext.c +5 -4
- data/ext/polyphony/queue.c +5 -6
- data/ext/polyphony/ring_buffer.c +0 -1
- data/ext/polyphony/runqueue.c +102 -0
- data/ext/polyphony/runqueue_ring_buffer.c +85 -0
- data/ext/polyphony/runqueue_ring_buffer.h +31 -0
- data/ext/polyphony/thread.c +53 -102
- data/lib/polyphony.rb +15 -14
- data/lib/polyphony/adapters/fs.rb +1 -1
- data/lib/polyphony/adapters/irb.rb +2 -17
- data/lib/polyphony/adapters/mysql2.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +5 -5
- data/lib/polyphony/adapters/process.rb +2 -5
- data/lib/polyphony/adapters/readline.rb +17 -0
- data/lib/polyphony/adapters/redis.rb +1 -1
- data/lib/polyphony/adapters/sequel.rb +1 -1
- data/lib/polyphony/core/global_api.rb +19 -14
- data/lib/polyphony/core/resource_pool.rb +2 -2
- data/lib/polyphony/core/sync.rb +43 -3
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/extensions/core.rb +25 -32
- data/lib/polyphony/extensions/fiber.rb +22 -45
- data/lib/polyphony/extensions/io.rb +60 -16
- data/lib/polyphony/extensions/openssl.rb +6 -6
- data/lib/polyphony/extensions/socket.rb +14 -15
- data/lib/polyphony/extensions/thread.rb +6 -5
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +5 -3
- data/test/helper.rb +1 -1
- data/test/{test_agent.rb → test_backend.rb} +22 -22
- data/test/test_fiber.rb +13 -12
- data/test/test_global_api.rb +29 -0
- data/test/test_io.rb +59 -1
- data/test/test_kernel.rb +5 -0
- data/test/test_signal.rb +14 -11
- data/test/test_socket.rb +17 -0
- data/test/test_sync.rb +73 -0
- metadata +99 -98
- data/.gitbook.yaml +0 -4
- data/examples/adapters/concurrent-ruby.rb +0 -9
- data/examples/core/04-handling-signals.rb +0 -19
- data/examples/core/xx-agent.rb +0 -102
- data/examples/core/xx-at_exit.rb +0 -29
- data/examples/core/xx-caller.rb +0 -12
- data/examples/core/xx-daemon.rb +0 -14
- data/examples/core/xx-deadlock.rb +0 -8
- data/examples/core/xx-deferring-an-operation.rb +0 -14
- data/examples/core/xx-exception-backtrace.rb +0 -40
- data/examples/core/xx-fork-cleanup.rb +0 -22
- data/examples/core/xx-fork-spin.rb +0 -42
- data/examples/core/xx-fork-terminate.rb +0 -27
- data/examples/core/xx-move_on.rb +0 -23
- data/examples/core/xx-queue-async.rb +0 -120
- data/examples/core/xx-readpartial.rb +0 -18
- data/examples/core/xx-signals.rb +0 -16
- data/examples/core/xx-sleep-forever.rb +0 -9
- data/examples/core/xx-sleeping.rb +0 -25
- data/examples/core/xx-snooze-starve.rb +0 -16
- data/examples/core/xx-spin-fork.rb +0 -49
- data/examples/core/xx-state-machine.rb +0 -51
- data/examples/core/xx-stop.rb +0 -20
- data/examples/core/xx-supervisors.rb +0 -21
- data/examples/core/xx-thread-selector-sleep.rb +0 -51
- data/examples/core/xx-thread-selector-snooze.rb +0 -46
- data/examples/core/xx-thread-snooze.rb +0 -34
- data/examples/core/xx-timer-gc.rb +0 -17
- data/examples/core/xx-trace.rb +0 -79
- data/examples/performance/xx-array.rb +0 -11
- data/examples/performance/xx-fiber-switch.rb +0 -9
- data/examples/performance/xx-snooze.rb +0 -15
- data/examples/xx-spin.rb +0 -32
- data/ext/polyphony/agent.h +0 -41
|
@@ -7,20 +7,6 @@ require_relative '../core/exceptions'
|
|
|
7
7
|
module Polyphony
|
|
8
8
|
# Fiber control API
|
|
9
9
|
module FiberControl
|
|
10
|
-
def await
|
|
11
|
-
if @running == false
|
|
12
|
-
return @result.is_a?(Exception) ? (Kernel.raise @result) : @result
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
fiber = Fiber.current
|
|
16
|
-
@waiting_fibers ||= {}
|
|
17
|
-
@waiting_fibers[fiber] = true
|
|
18
|
-
suspend
|
|
19
|
-
ensure
|
|
20
|
-
@waiting_fibers&.delete(fiber)
|
|
21
|
-
end
|
|
22
|
-
alias_method :join, :await
|
|
23
|
-
|
|
24
10
|
def interrupt(value = nil)
|
|
25
11
|
return if @running == false
|
|
26
12
|
|
|
@@ -117,7 +103,7 @@ module Polyphony
|
|
|
117
103
|
suspend
|
|
118
104
|
fibers.map(&:result)
|
|
119
105
|
ensure
|
|
120
|
-
await_select_cleanup(state)
|
|
106
|
+
await_select_cleanup(state) if state
|
|
121
107
|
end
|
|
122
108
|
alias_method :join, :await
|
|
123
109
|
|
|
@@ -179,21 +165,19 @@ module Polyphony
|
|
|
179
165
|
state[:selected] = true
|
|
180
166
|
end
|
|
181
167
|
end
|
|
182
|
-
end
|
|
183
168
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@mailbox.shift_all
|
|
169
|
+
# Creates and schedules with priority an out-of-band fiber that runs the
|
|
170
|
+
# supplied block. If any uncaught exception is raised while the fiber is
|
|
171
|
+
# running, it will bubble up to the main thread's main fiber, which will
|
|
172
|
+
# also be scheduled with priority. This method is mainly used trapping
|
|
173
|
+
# signals (see also the patched `Kernel#trap`)
|
|
174
|
+
def schedule_priority_oob_fiber(&block)
|
|
175
|
+
f = Fiber.new do
|
|
176
|
+
block.call
|
|
177
|
+
rescue Exception => e
|
|
178
|
+
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, e)
|
|
179
|
+
end
|
|
180
|
+
Thread.current.break_out_of_ev_loop(f, nil)
|
|
197
181
|
end
|
|
198
182
|
end
|
|
199
183
|
|
|
@@ -225,14 +209,14 @@ module Polyphony
|
|
|
225
209
|
def await_all_children
|
|
226
210
|
return unless @children && !@children.empty?
|
|
227
211
|
|
|
228
|
-
|
|
212
|
+
results = @children.dup
|
|
229
213
|
@on_child_done = proc do |c, r|
|
|
230
|
-
|
|
231
|
-
|
|
214
|
+
results[c] = r
|
|
215
|
+
schedule if @children.empty?
|
|
232
216
|
end
|
|
233
217
|
suspend
|
|
234
218
|
@on_child_done = nil
|
|
235
|
-
|
|
219
|
+
results.values
|
|
236
220
|
end
|
|
237
221
|
|
|
238
222
|
def shutdown_all_children
|
|
@@ -249,7 +233,6 @@ module Polyphony
|
|
|
249
233
|
@parent = parent
|
|
250
234
|
@caller = caller
|
|
251
235
|
@block = block
|
|
252
|
-
@mailbox = Polyphony::Queue.new
|
|
253
236
|
__fiber_trace__(:fiber_create, self)
|
|
254
237
|
schedule
|
|
255
238
|
end
|
|
@@ -279,7 +262,6 @@ module Polyphony
|
|
|
279
262
|
# allows the fiber to be scheduled and to receive messages.
|
|
280
263
|
def setup_raw
|
|
281
264
|
@thread = Thread.current
|
|
282
|
-
@mailbox = Polyphony::Queue.new
|
|
283
265
|
end
|
|
284
266
|
|
|
285
267
|
def setup_main_fiber
|
|
@@ -288,11 +270,10 @@ module Polyphony
|
|
|
288
270
|
@thread = Thread.current
|
|
289
271
|
@running = true
|
|
290
272
|
@children&.clear
|
|
291
|
-
@mailbox = Polyphony::Queue.new
|
|
292
273
|
end
|
|
293
274
|
|
|
294
275
|
def restart_self(first_value)
|
|
295
|
-
@mailbox =
|
|
276
|
+
@mailbox = nil
|
|
296
277
|
@when_done_procs = nil
|
|
297
278
|
@waiting_fibers = nil
|
|
298
279
|
run(first_value)
|
|
@@ -324,13 +305,10 @@ module Polyphony
|
|
|
324
305
|
def inform_dependants(result, uncaught_exception)
|
|
325
306
|
@parent&.child_done(self, result)
|
|
326
307
|
@when_done_procs&.each { |p| p.(result) }
|
|
327
|
-
@waiting_fibers&.each_key
|
|
328
|
-
|
|
329
|
-
end
|
|
330
|
-
return unless uncaught_exception && !@waiting_fibers
|
|
331
|
-
|
|
308
|
+
@waiting_fibers&.each_key { |f| f.schedule(result) }
|
|
309
|
+
|
|
332
310
|
# propagate unaught exception to parent
|
|
333
|
-
@parent&.
|
|
311
|
+
@parent&.schedule_with_priority(result) if uncaught_exception && !@waiting_fibers
|
|
334
312
|
end
|
|
335
313
|
|
|
336
314
|
def when_done(&block)
|
|
@@ -344,14 +322,13 @@ end
|
|
|
344
322
|
class ::Fiber
|
|
345
323
|
prepend Polyphony::FiberControl
|
|
346
324
|
include Polyphony::FiberSupervision
|
|
347
|
-
include Polyphony::FiberMessaging
|
|
348
325
|
include Polyphony::ChildFiberControl
|
|
349
326
|
include Polyphony::FiberLifeCycle
|
|
350
327
|
|
|
351
328
|
extend Polyphony::FiberControlClassMethods
|
|
352
329
|
|
|
353
330
|
attr_accessor :tag, :thread, :parent
|
|
354
|
-
attr_reader :result
|
|
331
|
+
attr_reader :result, :mailbox
|
|
355
332
|
|
|
356
333
|
def running?
|
|
357
334
|
@running
|
|
@@ -90,16 +90,27 @@ class ::IO
|
|
|
90
90
|
# def each_codepoint
|
|
91
91
|
# end
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
alias_method :orig_getbyte, :getbyte
|
|
94
|
+
def getbyte
|
|
95
|
+
char = getc
|
|
96
|
+
char ? char.getbyte(0) : nil
|
|
97
|
+
end
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
alias_method :orig_getc, :getc
|
|
100
|
+
def getc
|
|
101
|
+
return @read_buffer.slice!(0) if @read_buffer && !@read_buffer.empty?
|
|
102
|
+
|
|
103
|
+
@read_buffer ||= +''
|
|
104
|
+
Thread.current.backend.read(self, @read_buffer, 8192, false)
|
|
105
|
+
return @read_buffer.slice!(0) if !@read_buffer.empty?
|
|
106
|
+
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
98
109
|
|
|
99
110
|
alias_method :orig_read, :read
|
|
100
111
|
def read(len = nil)
|
|
101
112
|
@read_buffer ||= +''
|
|
102
|
-
result = Thread.current.
|
|
113
|
+
result = Thread.current.backend.read(self, @read_buffer, len, true)
|
|
103
114
|
return nil unless result
|
|
104
115
|
|
|
105
116
|
already_read = @read_buffer
|
|
@@ -110,7 +121,7 @@ class ::IO
|
|
|
110
121
|
alias_method :orig_readpartial, :read
|
|
111
122
|
def readpartial(len, str = nil)
|
|
112
123
|
@read_buffer ||= +''
|
|
113
|
-
result = Thread.current.
|
|
124
|
+
result = Thread.current.backend.read(self, @read_buffer, len, false)
|
|
114
125
|
raise EOFError unless result
|
|
115
126
|
|
|
116
127
|
if str
|
|
@@ -124,12 +135,12 @@ class ::IO
|
|
|
124
135
|
|
|
125
136
|
alias_method :orig_write, :write
|
|
126
137
|
def write(str, *args)
|
|
127
|
-
Thread.current.
|
|
138
|
+
Thread.current.backend.write(self, str, *args)
|
|
128
139
|
end
|
|
129
140
|
|
|
130
141
|
alias_method :orig_write_chevron, :<<
|
|
131
142
|
def <<(str)
|
|
132
|
-
Thread.current.
|
|
143
|
+
Thread.current.backend.write(self, str)
|
|
133
144
|
self
|
|
134
145
|
end
|
|
135
146
|
|
|
@@ -163,20 +174,29 @@ class ::IO
|
|
|
163
174
|
# def putc(obj)
|
|
164
175
|
# end
|
|
165
176
|
|
|
177
|
+
LINEFEED = "\n"
|
|
178
|
+
LINEFEED_RE = /\n$/.freeze
|
|
179
|
+
|
|
166
180
|
alias_method :orig_puts, :puts
|
|
167
181
|
def puts(*args)
|
|
168
182
|
if args.empty?
|
|
169
|
-
write
|
|
183
|
+
write LINEFEED
|
|
170
184
|
return
|
|
171
185
|
end
|
|
172
186
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
187
|
+
idx = 0
|
|
188
|
+
while idx < args.size
|
|
189
|
+
arg = args[idx]
|
|
190
|
+
args[idx] = arg = arg.to_s unless arg.is_a?(String)
|
|
191
|
+
if arg =~ LINEFEED_RE
|
|
192
|
+
idx += 1
|
|
193
|
+
else
|
|
194
|
+
args.insert(idx + 1, LINEFEED)
|
|
195
|
+
idx += 2
|
|
196
|
+
end
|
|
178
197
|
end
|
|
179
|
-
|
|
198
|
+
|
|
199
|
+
write(*args)
|
|
180
200
|
nil
|
|
181
201
|
end
|
|
182
202
|
|
|
@@ -203,7 +223,7 @@ class ::IO
|
|
|
203
223
|
end
|
|
204
224
|
|
|
205
225
|
def read_loop(&block)
|
|
206
|
-
Thread.current.
|
|
226
|
+
Thread.current.backend.read_loop(self, &block)
|
|
207
227
|
end
|
|
208
228
|
|
|
209
229
|
# alias_method :orig_read, :read
|
|
@@ -218,4 +238,28 @@ class ::IO
|
|
|
218
238
|
# end
|
|
219
239
|
# outbuf
|
|
220
240
|
# end
|
|
241
|
+
|
|
242
|
+
def wait_readable(timeout = nil)
|
|
243
|
+
if timeout
|
|
244
|
+
move_on_after(timeout) do
|
|
245
|
+
Thread.current.backend.wait_io(self, false)
|
|
246
|
+
self
|
|
247
|
+
end
|
|
248
|
+
else
|
|
249
|
+
Thread.current.backend.wait_io(self, false)
|
|
250
|
+
self
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def wait_writable(timeout = nil)
|
|
255
|
+
if timeout
|
|
256
|
+
move_on_after(timeout) do
|
|
257
|
+
Thread.current.backend.wait_io(self, true)
|
|
258
|
+
self
|
|
259
|
+
end
|
|
260
|
+
else
|
|
261
|
+
Thread.current.backend.wait_io(self, true)
|
|
262
|
+
self
|
|
263
|
+
end
|
|
264
|
+
end
|
|
221
265
|
end
|
|
@@ -28,8 +28,8 @@ class ::OpenSSL::SSL::SSLSocket
|
|
|
28
28
|
loop do
|
|
29
29
|
result = accept_nonblock(exception: false)
|
|
30
30
|
case result
|
|
31
|
-
when :wait_readable then Thread.current.
|
|
32
|
-
when :wait_writable then Thread.current.
|
|
31
|
+
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
|
32
|
+
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
|
33
33
|
else
|
|
34
34
|
return result
|
|
35
35
|
end
|
|
@@ -40,8 +40,8 @@ class ::OpenSSL::SSL::SSLSocket
|
|
|
40
40
|
def sysread(maxlen, buf = +'')
|
|
41
41
|
loop do
|
|
42
42
|
case (result = read_nonblock(maxlen, buf, exception: false))
|
|
43
|
-
when :wait_readable then Thread.current.
|
|
44
|
-
when :wait_writable then Thread.current.
|
|
43
|
+
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
|
44
|
+
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
|
45
45
|
else return result
|
|
46
46
|
end
|
|
47
47
|
end
|
|
@@ -51,8 +51,8 @@ class ::OpenSSL::SSL::SSLSocket
|
|
|
51
51
|
def syswrite(buf)
|
|
52
52
|
loop do
|
|
53
53
|
case (result = write_nonblock(buf, exception: false))
|
|
54
|
-
when :wait_readable then Thread.current.
|
|
55
|
-
when :wait_writable then Thread.current.
|
|
54
|
+
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
|
55
|
+
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
|
56
56
|
else
|
|
57
57
|
return result
|
|
58
58
|
end
|
|
@@ -5,26 +5,17 @@ require 'socket'
|
|
|
5
5
|
require_relative './io'
|
|
6
6
|
require_relative '../core/thread_pool'
|
|
7
7
|
|
|
8
|
-
class ::BasicSocket
|
|
9
|
-
def write_nonblock(string, _options = {})
|
|
10
|
-
write(string)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def read_nonblock(maxlen, str = nil, _options = {})
|
|
14
|
-
readpartial(maxlen, str)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
8
|
# Socket overrides (eventually rewritten in C)
|
|
19
9
|
class ::Socket
|
|
20
10
|
def accept
|
|
21
|
-
Thread.current.
|
|
11
|
+
Thread.current.backend.accept(self)
|
|
22
12
|
end
|
|
23
13
|
|
|
24
14
|
NO_EXCEPTION = { exception: false }.freeze
|
|
25
15
|
|
|
26
|
-
def connect(
|
|
27
|
-
|
|
16
|
+
def connect(addr)
|
|
17
|
+
addr = Addrinfo.new(addr) if addr.is_a?(String)
|
|
18
|
+
Thread.current.backend.connect(self, addr.ip_address, addr.ip_port)
|
|
28
19
|
end
|
|
29
20
|
|
|
30
21
|
def recv(maxlen, flags = 0, outbuf = nil)
|
|
@@ -33,7 +24,7 @@ class ::Socket
|
|
|
33
24
|
result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
|
|
34
25
|
case result
|
|
35
26
|
when nil then raise IOError
|
|
36
|
-
when :wait_readable then Thread.current.
|
|
27
|
+
when :wait_readable then Thread.current.backend.wait_io(self, false)
|
|
37
28
|
else
|
|
38
29
|
return result
|
|
39
30
|
end
|
|
@@ -46,7 +37,7 @@ class ::Socket
|
|
|
46
37
|
result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
|
|
47
38
|
case result
|
|
48
39
|
when nil then raise IOError
|
|
49
|
-
when :wait_readable then Thread.current.
|
|
40
|
+
when :wait_readable then Thread.current.backend.wait_io(self, false)
|
|
50
41
|
else
|
|
51
42
|
return result
|
|
52
43
|
end
|
|
@@ -128,6 +119,14 @@ class ::TCPSocket
|
|
|
128
119
|
def reuse_port
|
|
129
120
|
setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
|
130
121
|
end
|
|
122
|
+
|
|
123
|
+
def read_nonblock(len, str = nil, exception: true)
|
|
124
|
+
@io.read_nonblock(len, str, exception: exception)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def write_nonblock(buf, exception: true)
|
|
128
|
+
@io.write_nonblock(buf, exception: exception)
|
|
129
|
+
end
|
|
131
130
|
end
|
|
132
131
|
|
|
133
132
|
# Override stock TCPServer code by encapsulating a Socket instance.
|
|
@@ -16,21 +16,22 @@ class ::Thread
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def execute
|
|
19
|
-
#
|
|
19
|
+
# backend must be created in the context of the new thread, therefore it
|
|
20
20
|
# cannot be created in Thread#initialize
|
|
21
|
-
@
|
|
21
|
+
@backend = Polyphony::Backend.new
|
|
22
22
|
setup
|
|
23
23
|
@ready = true
|
|
24
24
|
result = @block.(*@args)
|
|
25
25
|
rescue Polyphony::MoveOn, Polyphony::Terminate => e
|
|
26
26
|
result = e.value
|
|
27
|
-
rescue Exception =>
|
|
27
|
+
rescue Exception => e
|
|
28
|
+
result = e
|
|
28
29
|
ensure
|
|
29
30
|
@ready = true
|
|
30
31
|
finalize(result)
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
attr_accessor :
|
|
34
|
+
attr_accessor :backend
|
|
34
35
|
|
|
35
36
|
def setup
|
|
36
37
|
@main_fiber = Fiber.current
|
|
@@ -48,7 +49,7 @@ class ::Thread
|
|
|
48
49
|
@result = result
|
|
49
50
|
signal_waiters(result)
|
|
50
51
|
end
|
|
51
|
-
@
|
|
52
|
+
@backend.finalize
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
def signal_waiters(result)
|
data/lib/polyphony/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
|
@@ -21,19 +21,21 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
s.require_paths = ["lib"]
|
|
22
22
|
s.required_ruby_version = '>= 2.6'
|
|
23
23
|
|
|
24
|
-
s.add_development_dependency '
|
|
25
|
-
s.add_development_dependency 'localhost', '1.1.4'
|
|
24
|
+
s.add_development_dependency 'rake-compiler', '1.1.1'
|
|
26
25
|
s.add_development_dependency 'minitest', '5.13.0'
|
|
27
26
|
s.add_development_dependency 'minitest-reporters', '1.4.2'
|
|
28
27
|
s.add_development_dependency 'simplecov', '0.17.1'
|
|
29
28
|
s.add_development_dependency 'rubocop', '0.85.1'
|
|
29
|
+
s.add_development_dependency 'pry', '0.13.1'
|
|
30
|
+
|
|
30
31
|
s.add_development_dependency 'pg', '1.1.4'
|
|
31
|
-
s.add_development_dependency 'rake-compiler', '1.0.5'
|
|
32
32
|
s.add_development_dependency 'redis', '4.1.0'
|
|
33
33
|
s.add_development_dependency 'hiredis', '0.6.3'
|
|
34
34
|
s.add_development_dependency 'http_parser.rb', '~>0.6.0'
|
|
35
|
+
s.add_development_dependency 'rack', '>=2.0.8', '<2.3.0'
|
|
35
36
|
s.add_development_dependency 'mysql2', '0.5.3'
|
|
36
37
|
s.add_development_dependency 'sequel', '5.34.0'
|
|
38
|
+
s.add_development_dependency 'httparty', '0.17.1'
|
|
37
39
|
|
|
38
40
|
s.add_development_dependency 'jekyll', '~>3.8.6'
|
|
39
41
|
s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
|
data/test/helper.rb
CHANGED
|
@@ -31,7 +31,7 @@ class MiniTest::Test
|
|
|
31
31
|
end
|
|
32
32
|
Fiber.current.setup_main_fiber
|
|
33
33
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
|
34
|
-
Thread.current.
|
|
34
|
+
Thread.current.backend = Polyphony::Backend.new
|
|
35
35
|
sleep 0 # apparently this helps with timer accuracy
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -2,39 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'helper'
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class BackendTest < MiniTest::Test
|
|
6
6
|
def setup
|
|
7
7
|
super
|
|
8
|
-
@
|
|
9
|
-
@
|
|
10
|
-
Thread.current.
|
|
8
|
+
@prev_backend = Thread.current.backend
|
|
9
|
+
@backend = Polyphony::Backend.new
|
|
10
|
+
Thread.current.backend = @backend
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def teardown
|
|
14
|
-
@
|
|
15
|
-
Thread.current.
|
|
14
|
+
@backend.finalize
|
|
15
|
+
Thread.current.backend = @prev_backend
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def test_sleep
|
|
19
19
|
count = 0
|
|
20
20
|
t0 = Time.now
|
|
21
21
|
spin {
|
|
22
|
-
@
|
|
22
|
+
@backend.sleep 0.01
|
|
23
23
|
count += 1
|
|
24
|
-
@
|
|
24
|
+
@backend.sleep 0.01
|
|
25
25
|
count += 1
|
|
26
|
-
@
|
|
26
|
+
@backend.sleep 0.01
|
|
27
27
|
count += 1
|
|
28
28
|
}.await
|
|
29
|
-
|
|
29
|
+
assert_in_delta 0.03, Time.now - t0, 0.005
|
|
30
30
|
assert_equal 3, count
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def test_write_read_partial
|
|
34
34
|
i, o = IO.pipe
|
|
35
35
|
buf = +''
|
|
36
|
-
f = spin { @
|
|
37
|
-
@
|
|
36
|
+
f = spin { @backend.read(i, buf, 5, false) }
|
|
37
|
+
@backend.write(o, 'Hello world')
|
|
38
38
|
return_value = f.await
|
|
39
39
|
|
|
40
40
|
assert_equal 'Hello', buf
|
|
@@ -44,10 +44,10 @@ class AgentTest < MiniTest::Test
|
|
|
44
44
|
def test_write_read_to_eof_limited_buffer
|
|
45
45
|
i, o = IO.pipe
|
|
46
46
|
buf = +''
|
|
47
|
-
f = spin { @
|
|
48
|
-
@
|
|
47
|
+
f = spin { @backend.read(i, buf, 5, true) }
|
|
48
|
+
@backend.write(o, 'Hello')
|
|
49
49
|
snooze
|
|
50
|
-
@
|
|
50
|
+
@backend.write(o, ' world')
|
|
51
51
|
snooze
|
|
52
52
|
o.close
|
|
53
53
|
return_value = f.await
|
|
@@ -59,10 +59,10 @@ class AgentTest < MiniTest::Test
|
|
|
59
59
|
def test_write_read_to_eof
|
|
60
60
|
i, o = IO.pipe
|
|
61
61
|
buf = +''
|
|
62
|
-
f = spin { @
|
|
63
|
-
@
|
|
62
|
+
f = spin { @backend.read(i, buf, 10**6, true) }
|
|
63
|
+
@backend.write(o, 'Hello')
|
|
64
64
|
snooze
|
|
65
|
-
@
|
|
65
|
+
@backend.write(o, ' world')
|
|
66
66
|
snooze
|
|
67
67
|
o.close
|
|
68
68
|
return_value = f.await
|
|
@@ -73,11 +73,11 @@ class AgentTest < MiniTest::Test
|
|
|
73
73
|
|
|
74
74
|
def test_waitpid
|
|
75
75
|
pid = fork do
|
|
76
|
-
@
|
|
76
|
+
@backend.post_fork
|
|
77
77
|
exit(42)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
result = @
|
|
80
|
+
result = @backend.waitpid(pid)
|
|
81
81
|
assert_equal [pid, 42], result
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -87,7 +87,7 @@ class AgentTest < MiniTest::Test
|
|
|
87
87
|
buf = []
|
|
88
88
|
spin do
|
|
89
89
|
buf << :ready
|
|
90
|
-
@
|
|
90
|
+
@backend.read_loop(i) { |d| buf << d }
|
|
91
91
|
buf << :done
|
|
92
92
|
end
|
|
93
93
|
|
|
@@ -107,7 +107,7 @@ class AgentTest < MiniTest::Test
|
|
|
107
107
|
|
|
108
108
|
clients = []
|
|
109
109
|
server_fiber = spin do
|
|
110
|
-
@
|
|
110
|
+
@backend.accept_loop(server) { |c| clients << c }
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
c1 = TCPSocket.new('127.0.0.1', 1234)
|