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
|
@@ -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
|