polyphony 0.43.10 → 0.45.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -1
- data/CHANGELOG.md +37 -0
- data/Gemfile.lock +16 -6
- data/Rakefile +1 -1
- data/TODO.md +15 -10
- 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 +23 -0
- data/examples/adapters/sequel_mysql_pool.rb +33 -0
- 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/{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/{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 +66 -6
- data/ext/polyphony/{libev_agent.c → libev_backend.c} +239 -235
- data/ext/polyphony/polyphony.c +3 -3
- data/ext/polyphony/polyphony.h +15 -23
- data/ext/polyphony/polyphony_ext.c +3 -4
- data/ext/polyphony/queue.c +25 -12
- data/ext/polyphony/ring_buffer.c +0 -1
- data/ext/polyphony/thread.c +36 -33
- data/lib/polyphony.rb +25 -38
- data/lib/polyphony/adapters/fs.rb +1 -1
- data/lib/polyphony/adapters/irb.rb +2 -17
- data/lib/polyphony/adapters/mysql2.rb +19 -0
- data/lib/polyphony/adapters/postgres.rb +5 -5
- data/lib/polyphony/adapters/process.rb +2 -2
- data/lib/polyphony/adapters/readline.rb +17 -0
- data/lib/polyphony/adapters/redis.rb +1 -1
- data/lib/polyphony/adapters/sequel.rb +45 -0
- data/lib/polyphony/core/exceptions.rb +11 -0
- data/lib/polyphony/core/global_api.rb +17 -12
- data/lib/polyphony/core/resource_pool.rb +20 -7
- data/lib/polyphony/core/sync.rb +46 -8
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/extensions/core.rb +38 -25
- data/lib/polyphony/extensions/fiber.rb +12 -45
- data/lib/polyphony/extensions/io.rb +45 -12
- data/lib/polyphony/extensions/openssl.rb +6 -6
- data/lib/polyphony/extensions/socket.rb +22 -15
- data/lib/polyphony/extensions/thread.rb +6 -5
- data/lib/polyphony/net.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +7 -3
- data/test/helper.rb +1 -1
- data/test/{test_agent.rb → test_backend.rb} +22 -22
- data/test/test_fiber.rb +28 -11
- data/test/test_io.rb +17 -1
- data/test/test_kernel.rb +5 -0
- data/test/test_resource_pool.rb +50 -16
- data/test/test_signal.rb +5 -29
- data/test/test_socket.rb +17 -0
- data/test/test_sync.rb +52 -0
- metadata +126 -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 -39
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/adapters/sequel'
|
5
|
+
require 'polyphony/adapters/postgres'
|
6
|
+
|
7
|
+
time_printer = spin do
|
8
|
+
last = Time.now
|
9
|
+
throttled_loop(10) do
|
10
|
+
now = Time.now
|
11
|
+
puts now - last
|
12
|
+
last = now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
db = Sequel.connect('mysql2://localhost/test')
|
17
|
+
|
18
|
+
x = 10_000
|
19
|
+
t0 = Time.now
|
20
|
+
x.times { db.execute('select 1 as test') }
|
21
|
+
puts "query rate: #{x / (Time.now - t0)} reqs/s"
|
22
|
+
|
23
|
+
time_printer.stop
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/adapters/sequel'
|
5
|
+
require 'polyphony/adapters/mysql2'
|
6
|
+
|
7
|
+
CONCURRENCY = ARGV.first ? ARGV.first.to_i : 1000
|
8
|
+
puts "concurrency: #{CONCURRENCY}"
|
9
|
+
|
10
|
+
db = Sequel.connect(
|
11
|
+
'mysql2://localhost/test',
|
12
|
+
max_connections: 100,
|
13
|
+
preconnect: true
|
14
|
+
)
|
15
|
+
|
16
|
+
t0 = Time.now
|
17
|
+
count = 0
|
18
|
+
|
19
|
+
fibers = Array.new(CONCURRENCY) do
|
20
|
+
spin do
|
21
|
+
loop do
|
22
|
+
db.execute('select sleep(0.001) as test')
|
23
|
+
count += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
sleep 0.1
|
29
|
+
fibers.first.terminate # Interrupt mid-query
|
30
|
+
|
31
|
+
sleep 2
|
32
|
+
puts "query rate: #{count / (Time.now - t0)} reqs/s"
|
33
|
+
fibers.each(&:interrupt)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/adapters/sequel'
|
5
|
+
require 'polyphony/adapters/postgres'
|
6
|
+
|
7
|
+
URL = ENV['SEQUEL_URL'] || 'postgres://localhost/test'
|
8
|
+
|
9
|
+
x = 10000
|
10
|
+
query_count = 0
|
11
|
+
|
12
|
+
spin do
|
13
|
+
db = Sequel.connect(URL)
|
14
|
+
x.times { query_count += 1; db.execute('select 1 as test') }
|
15
|
+
end
|
16
|
+
|
17
|
+
spin do
|
18
|
+
db = Sequel.connect(URL)
|
19
|
+
x.times { query_count += 1; db.execute('select 2 as test') }
|
20
|
+
end
|
21
|
+
|
22
|
+
t0 = Time.now
|
23
|
+
Fiber.current.await_all_children
|
24
|
+
puts "query rate: #{query_count / (Time.now - t0)} reqs/s; count = #{query_count}"
|
File without changes
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
spin do
|
7
|
+
puts 'two'
|
8
|
+
# spinning a fiber from the parent fiber allows us to schedule an operation to
|
9
|
+
# be performed even after the current fiber is terminated
|
10
|
+
Fiber.current.parent.spin { puts 'four' }
|
11
|
+
puts 'three'
|
12
|
+
end
|
13
|
+
|
14
|
+
puts 'one'
|
15
|
+
|
16
|
+
suspend
|
@@ -3,8 +3,10 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
module GenServer
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def start(receiver, *args)
|
8
10
|
fiber = spin do
|
9
11
|
state = receiver.initial_state(*args)
|
10
12
|
loop do
|
@@ -14,11 +16,10 @@ class GenServer
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
build_api(fiber, receiver)
|
17
|
-
snooze
|
18
19
|
fiber
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
+
def build_api(fiber, receiver)
|
22
23
|
receiver.methods(false).each do |m|
|
23
24
|
if m =~ /!$/
|
24
25
|
fiber.define_singleton_method(m) do |*args|
|
@@ -32,7 +33,7 @@ class GenServer
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
|
-
def
|
36
|
+
def cast(process, method, *args)
|
36
37
|
process << {
|
37
38
|
from: Fiber.current,
|
38
39
|
method: method,
|
@@ -40,7 +41,7 @@ class GenServer
|
|
40
41
|
}
|
41
42
|
end
|
42
43
|
|
43
|
-
def
|
44
|
+
def call(process, method, *args)
|
44
45
|
process << {
|
45
46
|
from: Fiber.current,
|
46
47
|
method: method,
|
@@ -50,21 +51,27 @@ class GenServer
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
54
|
+
# In a generic server the state is not held in an instance variable but rather
|
55
|
+
# passed as the first parameter to method calls. The return value of each method
|
56
|
+
# is an array consisting of the result and the potentially mutated state.
|
53
57
|
module Map
|
54
|
-
|
58
|
+
module_function
|
59
|
+
|
60
|
+
def initial_state(hash = {})
|
55
61
|
hash
|
56
62
|
end
|
57
63
|
|
58
|
-
def
|
64
|
+
def get(state, key)
|
59
65
|
[state[key], state]
|
60
66
|
end
|
61
67
|
|
62
|
-
def
|
68
|
+
def put!(state, key, value)
|
63
69
|
state[key] = value
|
64
70
|
[:noreply, state]
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
74
|
+
# start server with initial state
|
68
75
|
map_server = GenServer.start(Map, {foo: :bar})
|
69
76
|
|
70
77
|
puts 'getting value from map server'
|
File without changes
|
@@ -9,10 +9,12 @@ pong = spin_loop do
|
|
9
9
|
ping << 'pong'
|
10
10
|
end
|
11
11
|
|
12
|
-
ping =
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
ping = spin do
|
13
|
+
3.times do
|
14
|
+
pong << ['ping', Fiber.current]
|
15
|
+
msg = receive
|
16
|
+
puts msg
|
17
|
+
end
|
16
18
|
end
|
17
19
|
|
18
|
-
|
20
|
+
ping.await
|
@@ -10,7 +10,7 @@ class Number
|
|
10
10
|
|
11
11
|
def greet(other)
|
12
12
|
puts "You are number #{other}, I am number #{@id}"
|
13
|
-
sleep(0.
|
13
|
+
sleep rand(0.2..0.3)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -25,7 +25,6 @@ def meet(number)
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
(4..10).each { |x| spin { meet(x) } }
|
29
29
|
|
30
|
-
|
31
|
-
every(10) { puts "uptime: #{Time.now - t0}" }
|
30
|
+
sleep 1
|
@@ -8,7 +8,7 @@ Exception.__disable_sanitized_backtrace__ = true
|
|
8
8
|
supervisor = spin do
|
9
9
|
puts "parent pid #{Process.pid}"
|
10
10
|
|
11
|
-
Polyphony
|
11
|
+
Polyphony.watch_process do
|
12
12
|
puts "child pid #{Process.pid}"
|
13
13
|
puts "go to sleep"
|
14
14
|
sleep 5
|
@@ -22,9 +22,12 @@ supervisor = spin do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
begin
|
25
|
+
spin do
|
26
|
+
sleep 2.5
|
27
|
+
Process.kill('TERM', Process.pid)
|
28
|
+
end
|
29
|
+
supervisor.await
|
30
|
+
rescue SystemExit
|
31
|
+
supervisor.terminate
|
25
32
|
supervisor.await
|
26
|
-
rescue Interrupt
|
27
|
-
exit!
|
28
|
-
# supervisor.terminate
|
29
|
-
# supervisor.await
|
30
33
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
def my_sleep(t)
|
7
|
+
puts "#{t} start"
|
8
|
+
sleep(t)
|
9
|
+
puts "#{t} done"
|
10
|
+
end
|
11
|
+
|
12
|
+
spin { my_sleep(1) }
|
13
|
+
spin { my_sleep(2) }
|
14
|
+
spin { my_sleep(3) }
|
15
|
+
spin { puts "fiber count: #{Fiber.current.children.count}" }
|
16
|
+
snooze
|
17
|
+
|
18
|
+
puts "#{Time.now} supervising..."
|
19
|
+
supervise
|
20
|
+
puts "#{Time.now} done supervising"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -16,23 +16,14 @@ end
|
|
16
16
|
zones = %w{
|
17
17
|
Europe/London Europe/Paris Europe/Bucharest America/New_York Asia/Bangkok
|
18
18
|
}
|
19
|
-
# zones.each do |tzone|
|
20
|
-
# spin do
|
21
|
-
# time = get_time(tzone)
|
22
|
-
# puts "Time in #{tzone}: #{time}"
|
23
|
-
# end
|
24
|
-
# end
|
25
|
-
|
26
|
-
# suspend
|
27
19
|
|
28
20
|
def get_times(zones)
|
29
|
-
|
30
|
-
|
31
|
-
s.spin { [tzone, get_time(tzone)] }
|
32
|
-
end
|
21
|
+
fibers = zones.map do |tzone|
|
22
|
+
spin { [tzone, get_time(tzone)] }
|
33
23
|
end
|
24
|
+
Fiber.await(*fibers)
|
34
25
|
end
|
35
26
|
|
36
|
-
get_times(zones).
|
27
|
+
get_times(zones).each do |tzone, time|
|
37
28
|
puts "Time in #{tzone}: #{time}"
|
38
29
|
end
|
File without changes
|
File without changes
|
File without changes
|
data/examples/io/pry.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'polyphony/adapters/readline'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
$counter = 0
|
9
|
+
timer = spin do
|
10
|
+
throttled_loop(5) do
|
11
|
+
$counter += 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
at_exit { timer.stop }
|
16
|
+
|
17
|
+
puts 'try typing $counter to see the counter incremented in the background'
|
18
|
+
binding.pry
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'http/parser'
|
6
|
+
require 'rack'
|
7
|
+
|
8
|
+
module RackAdapter
|
9
|
+
class << self
|
10
|
+
def run(app)
|
11
|
+
->(socket, req) { respond(socket, req, app.(env(req))) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def env(req)
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
|
18
|
+
def respond(socket, request, (status_code, headers, body))
|
19
|
+
body = body.join
|
20
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\n"
|
21
|
+
socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{body}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
$connection_count = 0
|
27
|
+
|
28
|
+
def handle_client(socket, &handler)
|
29
|
+
$connection_count += 1
|
30
|
+
parser = Http::Parser.new
|
31
|
+
reqs = []
|
32
|
+
parser.on_message_complete = proc do |env|
|
33
|
+
reqs << Object.new # parser
|
34
|
+
end
|
35
|
+
socket.read_loop do |data|
|
36
|
+
parser << data
|
37
|
+
while (req = reqs.shift)
|
38
|
+
handler.call(socket, req)
|
39
|
+
req = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
rescue IOError, SystemCallError => e
|
43
|
+
# do nothing
|
44
|
+
ensure
|
45
|
+
$connection_count -= 1
|
46
|
+
socket&.close
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_request(client, parser)
|
50
|
+
status_code = "200 OK"
|
51
|
+
data = "Hello world!\n"
|
52
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
|
53
|
+
client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
54
|
+
end
|
55
|
+
|
56
|
+
server = TCPServer.open('0.0.0.0', 1234)
|
57
|
+
puts "pid #{Process.pid}"
|
58
|
+
puts "listening on port 1234"
|
59
|
+
|
60
|
+
app = RackAdapter.run(lambda { |env|
|
61
|
+
[
|
62
|
+
200,
|
63
|
+
{"Content-Type" => "text/plain"},
|
64
|
+
["Hello, world!\n"]
|
65
|
+
]
|
66
|
+
})
|
67
|
+
|
68
|
+
loop do
|
69
|
+
client = server.accept
|
70
|
+
spin { handle_client(client, &app) }
|
71
|
+
end
|