polyphony 0.40 → 0.43.2
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/.github/workflows/test.yml +11 -2
- data/.gitignore +2 -2
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +29 -2
- data/Gemfile.lock +13 -10
- data/README.md +0 -1
- data/Rakefile +3 -3
- data/TODO.md +27 -97
- data/docs/_config.yml +56 -7
- data/docs/_sass/custom/custom.scss +6 -26
- data/docs/_sass/overrides.scss +0 -46
- data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/{user-guide → _user-guide}/web-server.md +0 -0
- data/docs/api-reference/fiber.md +2 -2
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/polyphony-process.md +1 -1
- data/docs/api-reference/thread.md +1 -1
- data/docs/faq.md +21 -11
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +2 -6
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +27 -19
- data/docs/index.md +6 -2
- data/docs/main-concepts/concurrency.md +0 -5
- data/docs/main-concepts/design-principles.md +69 -21
- data/docs/main-concepts/extending.md +1 -1
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/core/01-spinning-up-fibers.rb +1 -0
- data/examples/core/03-interrupting.rb +4 -1
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-sleeping.rb +14 -6
- data/examples/io/xx-irb.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +13 -36
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/ext/{gyro → polyphony}/extconf.rb +2 -2
- data/ext/{gyro → polyphony}/fiber.c +15 -22
- data/ext/{gyro → polyphony}/libev.c +0 -0
- data/ext/{gyro → polyphony}/libev.h +0 -0
- data/ext/polyphony/libev_agent.c +725 -0
- data/ext/polyphony/libev_queue.c +217 -0
- data/ext/{gyro/gyro.c → polyphony/polyphony.c} +12 -37
- data/ext/polyphony/polyphony.h +90 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/{gyro → polyphony}/thread.c +34 -151
- data/ext/{gyro → polyphony}/tracing.c +1 -1
- data/lib/polyphony.rb +19 -12
- data/lib/polyphony/adapters/irb.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +6 -5
- data/lib/polyphony/adapters/process.rb +5 -5
- data/lib/polyphony/adapters/redis.rb +3 -2
- data/lib/polyphony/adapters/trace.rb +28 -28
- data/lib/polyphony/core/channel.rb +3 -3
- data/lib/polyphony/core/exceptions.rb +1 -1
- data/lib/polyphony/core/global_api.rb +13 -11
- data/lib/polyphony/core/resource_pool.rb +3 -3
- data/lib/polyphony/core/sync.rb +2 -2
- data/lib/polyphony/core/thread_pool.rb +6 -6
- data/lib/polyphony/core/throttler.rb +13 -6
- data/lib/polyphony/event.rb +27 -0
- data/lib/polyphony/extensions/core.rb +22 -14
- data/lib/polyphony/extensions/fiber.rb +4 -4
- data/lib/polyphony/extensions/io.rb +59 -25
- data/lib/polyphony/extensions/openssl.rb +36 -16
- data/lib/polyphony/extensions/socket.rb +28 -10
- data/lib/polyphony/extensions/thread.rb +16 -9
- data/lib/polyphony/net.rb +9 -9
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +3 -3
- data/test/helper.rb +12 -1
- data/test/test_agent.rb +130 -0
- data/test/{test_async.rb → test_event.rb} +13 -7
- data/test/test_ext.rb +25 -4
- data/test/test_fiber.rb +19 -10
- data/test/test_global_api.rb +6 -6
- data/test/test_io.rb +46 -24
- data/test/test_queue.rb +74 -0
- data/test/test_signal.rb +3 -40
- data/test/test_socket.rb +34 -0
- data/test/test_thread.rb +37 -16
- data/test/test_trace.rb +6 -5
- metadata +39 -41
- data/docs/_includes/nav.html +0 -51
- data/docs/_includes/prevnext.html +0 -17
- data/docs/_layouts/default.html +0 -106
- data/docs/api-reference.md +0 -11
- data/docs/api-reference/gyro-async.md +0 -57
- data/docs/api-reference/gyro-child.md +0 -29
- data/docs/api-reference/gyro-queue.md +0 -44
- data/docs/api-reference/gyro-timer.md +0 -51
- data/docs/api-reference/gyro.md +0 -25
- data/docs/getting-started.md +0 -10
- data/docs/main-concepts.md +0 -10
- data/docs/user-guide.md +0 -10
- data/examples/core/forever_sleep.rb +0 -19
- data/ext/gyro/async.c +0 -132
- data/ext/gyro/child.c +0 -108
- data/ext/gyro/gyro.h +0 -158
- data/ext/gyro/gyro_ext.c +0 -33
- data/ext/gyro/io.c +0 -457
- data/ext/gyro/queue.c +0 -146
- data/ext/gyro/selector.c +0 -205
- data/ext/gyro/signal.c +0 -99
- data/ext/gyro/socket.c +0 -213
- data/ext/gyro/timer.c +0 -115
- data/test/test_timer.rb +0 -56
data/lib/polyphony.rb
CHANGED
@@ -1,43 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'fiber'
|
4
|
-
require_relative './
|
4
|
+
require_relative './polyphony_ext'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
module Polyphony
|
7
|
+
# Map Queue to Libev queue implementation
|
8
|
+
Queue = LibevQueue
|
9
|
+
end
|
8
10
|
|
9
11
|
require_relative './polyphony/extensions/core'
|
10
12
|
require_relative './polyphony/extensions/thread'
|
11
13
|
require_relative './polyphony/extensions/fiber'
|
12
14
|
require_relative './polyphony/extensions/io'
|
13
15
|
|
16
|
+
Thread.current.setup_fiber_scheduling
|
17
|
+
Thread.current.agent = Polyphony::LibevAgent.new
|
18
|
+
|
14
19
|
require_relative './polyphony/core/global_api'
|
15
20
|
require_relative './polyphony/core/resource_pool'
|
16
21
|
require_relative './polyphony/net'
|
17
22
|
require_relative './polyphony/adapters/process'
|
23
|
+
require_relative './polyphony/event'
|
18
24
|
|
19
25
|
# Main Polyphony API
|
20
26
|
module Polyphony
|
21
27
|
class << self
|
22
28
|
def wait_for_signal(sig)
|
29
|
+
raise "should be reimplemented"
|
30
|
+
|
23
31
|
fiber = Fiber.current
|
24
|
-
|
32
|
+
# Polyphony.ref
|
25
33
|
old_trap = trap(sig) do
|
26
|
-
|
34
|
+
# Polyphony.unref
|
27
35
|
fiber.schedule(sig)
|
28
36
|
trap(sig, old_trap)
|
29
37
|
end
|
30
38
|
suspend
|
39
|
+
|
31
40
|
end
|
32
41
|
|
33
42
|
def fork(&block)
|
34
43
|
Kernel.fork do
|
35
|
-
|
36
|
-
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# created in the context of the forked process, which rescues *all*
|
40
|
-
# exceptions, including Interrupt and SystemExit.
|
44
|
+
# # Since the fiber doing the fork will become the main fiber of the
|
45
|
+
# # forked process, we leave it behind by transferring to a new fiber
|
46
|
+
# # created in the context of the forked process, which rescues *all*
|
47
|
+
# # exceptions, including Interrupt and SystemExit.
|
41
48
|
spin_forked_block(&block).transfer
|
42
49
|
end
|
43
50
|
end
|
@@ -63,9 +70,9 @@ module Polyphony
|
|
63
70
|
trap('SIGTERM', 'DEFAULT')
|
64
71
|
trap('SIGINT', 'DEFAULT')
|
65
72
|
|
66
|
-
Thread.current.post_fork
|
67
73
|
Thread.current.setup
|
68
74
|
Fiber.current.setup_main_fiber
|
75
|
+
Thread.current.agent.post_fork
|
69
76
|
|
70
77
|
install_terminating_signal_handlers
|
71
78
|
|
@@ -10,12 +10,13 @@ module ::PG
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.connect_async(conn)
|
13
|
+
socket_io = conn.socket_io
|
13
14
|
loop do
|
14
15
|
res = conn.connect_poll
|
15
16
|
case res
|
16
17
|
when PGRES_POLLING_FAILED then raise Error, conn.error_message
|
17
|
-
when PGRES_POLLING_READING then
|
18
|
-
when PGRES_POLLING_WRITING then
|
18
|
+
when PGRES_POLLING_READING then Thread.current.agent.wait_io(socket_io, false)
|
19
|
+
when PGRES_POLLING_WRITING then Thread.current.agent.wait_io(socket_io, true)
|
19
20
|
when PGRES_POLLING_OK then return conn.setnonblocking(true)
|
20
21
|
end
|
21
22
|
end
|
@@ -41,7 +42,7 @@ class ::PG::Connection
|
|
41
42
|
|
42
43
|
def get_result(&block)
|
43
44
|
while is_busy
|
44
|
-
|
45
|
+
Thread.current.agent.wait_io(socket_io, false)
|
45
46
|
consume_input
|
46
47
|
end
|
47
48
|
orig_get_result(&block)
|
@@ -58,7 +59,7 @@ class ::PG::Connection
|
|
58
59
|
|
59
60
|
def block(_timeout = 0)
|
60
61
|
while is_busy
|
61
|
-
|
62
|
+
Thread.current.agent.wait_io(socket_io, false)
|
62
63
|
consume_input
|
63
64
|
end
|
64
65
|
end
|
@@ -96,7 +97,7 @@ class ::PG::Connection
|
|
96
97
|
return move_on_after(timeout) { wait_for_notify(&block) } if timeout
|
97
98
|
|
98
99
|
loop do
|
99
|
-
|
100
|
+
Thread.current.agent.wait_io(socket_io, false)
|
100
101
|
consume_input
|
101
102
|
notice = notifies
|
102
103
|
next unless notice
|
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Polyphony
|
4
|
+
# Process patches
|
4
5
|
module Process
|
5
6
|
class << self
|
6
7
|
def watch(cmd = nil, &block)
|
7
8
|
terminated = nil
|
8
9
|
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
9
|
-
|
10
|
-
watcher.await
|
10
|
+
Thread.current.agent.waitpid(pid)
|
11
11
|
terminated = true
|
12
12
|
ensure
|
13
13
|
kill_process(pid) unless terminated || pid.nil?
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def kill_process(pid)
|
17
17
|
cancel_after(5) do
|
18
18
|
kill_and_await('TERM', pid)
|
@@ -20,10 +20,10 @@ module Polyphony
|
|
20
20
|
rescue Polyphony::Cancel
|
21
21
|
kill_and_await(-9, pid)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def kill_and_await(sig, pid)
|
25
25
|
::Process.kill(sig, pid)
|
26
|
-
|
26
|
+
Thread.current.agent.waitpid(pid)
|
27
27
|
rescue SystemCallError
|
28
28
|
# ignore
|
29
29
|
puts 'SystemCallError in kill_and_await'
|
@@ -6,7 +6,7 @@ require 'redis'
|
|
6
6
|
require 'hiredis/reader'
|
7
7
|
|
8
8
|
# Polyphony-based Redis driver
|
9
|
-
class
|
9
|
+
class Polyphony::RedisDriver
|
10
10
|
def self.connect(config)
|
11
11
|
raise 'unix sockets not supported' if config[:scheme] == 'unix'
|
12
12
|
|
@@ -43,6 +43,7 @@ class Driver
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def format_command(args)
|
46
|
+
args = args.flatten
|
46
47
|
(+"*#{args.size}\r\n").tap do |s|
|
47
48
|
args.each do |a|
|
48
49
|
a = a.to_s
|
@@ -63,4 +64,4 @@ class Driver
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
|
-
Redis::Connection.drivers <<
|
67
|
+
Redis::Connection.drivers << Polyphony::RedisDriver
|
@@ -5,6 +5,7 @@ require_relative '../../polyphony'
|
|
5
5
|
STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
|
6
6
|
|
7
7
|
module Polyphony
|
8
|
+
# Tracing functionality for Polyphony
|
8
9
|
module Trace
|
9
10
|
class << self
|
10
11
|
def new(*events)
|
@@ -12,10 +13,10 @@ module Polyphony
|
|
12
13
|
events = STOCK_EVENTS if events.empty?
|
13
14
|
::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
def trace_record(trp, start_stamp)
|
17
18
|
stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
|
18
|
-
|
19
|
+
|
19
20
|
{ stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
|
20
21
|
self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
|
21
22
|
lineno: trp.lineno, method_id: trp.method_id,
|
@@ -23,103 +24,103 @@ module Polyphony
|
|
23
24
|
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
24
25
|
exception: tp_raised_exception(trp) }
|
25
26
|
end
|
26
|
-
|
27
|
+
|
27
28
|
def tp_fiber(trp)
|
28
29
|
trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
|
29
30
|
end
|
30
|
-
|
31
|
+
|
31
32
|
PARAMS_EVENTS = %i[call c_call b_call].freeze
|
32
|
-
|
33
|
+
|
33
34
|
def tp_params(trp)
|
34
35
|
PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
|
35
36
|
end
|
36
|
-
|
37
|
+
|
37
38
|
RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
|
38
|
-
|
39
|
+
|
39
40
|
def tp_return_value(trp)
|
40
41
|
RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
|
41
42
|
end
|
42
|
-
|
43
|
+
|
43
44
|
SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
|
44
|
-
|
45
|
+
|
45
46
|
def tp_schedule_value(trp)
|
46
47
|
SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
def tp_raised_exception(trp)
|
50
51
|
trp.event == :raise && trp.raised_exception
|
51
52
|
end
|
52
|
-
|
53
|
+
|
53
54
|
def analyze(records)
|
54
55
|
by_fiber = Hash.new { |h, f| h[f] = [] }
|
55
56
|
records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
|
56
57
|
{ by_fiber: by_fiber }
|
57
58
|
end
|
58
|
-
|
59
|
+
|
59
60
|
# Implements fake TracePoint instances for fiber-related events
|
60
61
|
class FiberTracePoint
|
61
62
|
attr_reader :event, :fiber, :value
|
62
|
-
|
63
|
+
|
63
64
|
def initialize(tpoint)
|
64
65
|
@tp = tpoint
|
65
66
|
@event = tpoint.return_value[0]
|
66
67
|
@fiber = tpoint.return_value[1]
|
67
68
|
@value = tpoint.return_value[2]
|
68
69
|
end
|
69
|
-
|
70
|
+
|
70
71
|
def lineno
|
71
72
|
@tp.lineno
|
72
73
|
end
|
73
|
-
|
74
|
+
|
74
75
|
def method_id
|
75
76
|
@tp.method_id
|
76
77
|
end
|
77
|
-
|
78
|
+
|
78
79
|
def path
|
79
80
|
@tp.path
|
80
81
|
end
|
81
|
-
|
82
|
+
|
82
83
|
def self
|
83
84
|
@tp.self
|
84
85
|
end
|
85
|
-
|
86
|
+
|
86
87
|
def binding
|
87
88
|
@tp.binding
|
88
89
|
end
|
89
90
|
end
|
90
|
-
|
91
|
+
|
91
92
|
class << ::TracePoint
|
92
93
|
POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
|
93
|
-
|
94
|
+
|
94
95
|
alias_method :orig_new, :new
|
95
96
|
def new(*args, &block)
|
96
97
|
events_mask, fiber_events_mask = event_masks(args)
|
97
|
-
|
98
|
+
|
98
99
|
orig_new(*events_mask) do |tp|
|
99
100
|
handle_tp_event(tp, fiber_events_mask, &block)
|
100
101
|
end
|
101
102
|
end
|
102
|
-
|
103
|
+
|
103
104
|
def handle_tp_event(tpoint, fiber_events_mask)
|
104
105
|
# next unless !$watched_fiber || Fiber.current == $watched_fiber
|
105
|
-
|
106
|
+
|
106
107
|
if tpoint.method_id == :__fiber_trace__
|
107
108
|
return if tpoint.event != :c_return
|
108
109
|
return unless fiber_events_mask.include?(tpoint.return_value[0])
|
109
|
-
|
110
|
+
|
110
111
|
tpoint = FiberTracePoint.new(tpoint)
|
111
112
|
elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
|
112
113
|
return
|
113
114
|
end
|
114
|
-
|
115
|
+
|
115
116
|
yield tpoint
|
116
117
|
end
|
117
|
-
|
118
|
+
|
118
119
|
ALL_FIBER_EVENTS = %i[
|
119
120
|
fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
|
120
121
|
fiber_ev_loop_enter fiber_ev_loop_leave
|
121
122
|
].freeze
|
122
|
-
|
123
|
+
|
123
124
|
def event_masks(events)
|
124
125
|
events.each_with_object([[], []]) do |e, masks|
|
125
126
|
case e
|
@@ -135,4 +136,3 @@ module Polyphony
|
|
135
136
|
end
|
136
137
|
end
|
137
138
|
end
|
138
|
-
|
@@ -26,7 +26,7 @@ module Polyphony
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def receive
|
29
|
-
|
29
|
+
Thread.current.agent.ref
|
30
30
|
if @payload_queue.empty?
|
31
31
|
@waiting_queue << Fiber.current
|
32
32
|
suspend
|
@@ -34,7 +34,7 @@ module Polyphony
|
|
34
34
|
receive_from_queue
|
35
35
|
end
|
36
36
|
ensure
|
37
|
-
|
37
|
+
Thread.current.agent.unref
|
38
38
|
end
|
39
39
|
|
40
40
|
def receive_from_queue
|
@@ -43,4 +43,4 @@ module Polyphony
|
|
43
43
|
payload
|
44
44
|
end
|
45
45
|
end
|
46
|
-
end
|
46
|
+
end
|
@@ -45,13 +45,16 @@ module Polyphony
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def every(interval)
|
48
|
-
|
48
|
+
next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
|
49
49
|
loop do
|
50
|
-
|
50
|
+
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
51
|
+
Thread.current.agent.sleep(next_time - now)
|
51
52
|
yield
|
53
|
+
loop do
|
54
|
+
next_time += interval
|
55
|
+
break if next_time > now
|
56
|
+
end
|
52
57
|
end
|
53
|
-
ensure
|
54
|
-
timer.stop
|
55
58
|
end
|
56
59
|
|
57
60
|
def move_on_after(interval, with_value: nil, &block)
|
@@ -93,28 +96,27 @@ module Polyphony
|
|
93
96
|
def sleep(duration = nil)
|
94
97
|
return sleep_forever unless duration
|
95
98
|
|
96
|
-
|
97
|
-
timer.await
|
99
|
+
Thread.current.agent.sleep duration
|
98
100
|
end
|
99
101
|
|
100
102
|
def sleep_forever
|
101
|
-
Thread.current.
|
103
|
+
Thread.current.agent.ref
|
102
104
|
suspend
|
103
105
|
ensure
|
104
|
-
Thread.current.
|
106
|
+
Thread.current.agent.unref
|
105
107
|
end
|
106
108
|
|
107
109
|
def throttled_loop(rate, count: nil, &block)
|
108
110
|
throttler = Polyphony::Throttler.new(rate)
|
109
111
|
if count
|
110
|
-
count.times { throttler.(&block) }
|
112
|
+
count.times { |_i| throttler.(&block) }
|
111
113
|
else
|
112
114
|
loop { throttler.(&block) }
|
113
115
|
end
|
114
116
|
ensure
|
115
|
-
throttler
|
117
|
+
throttler&.stop
|
116
118
|
end
|
117
119
|
end
|
118
120
|
end
|
119
121
|
|
120
|
-
Object.include Polyphony::GlobalAPI
|
122
|
+
Object.include Polyphony::GlobalAPI
|
@@ -23,13 +23,13 @@ module Polyphony
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def acquire
|
26
|
-
|
26
|
+
Thread.current.agent.ref
|
27
27
|
resource = wait_for_resource
|
28
28
|
return unless resource
|
29
29
|
|
30
30
|
yield resource
|
31
31
|
ensure
|
32
|
-
|
32
|
+
Thread.current.agent.unref
|
33
33
|
release(resource) if resource
|
34
34
|
end
|
35
35
|
|
@@ -104,4 +104,4 @@ module Polyphony
|
|
104
104
|
(@limit - @size).times { @stock << allocate }
|
105
105
|
end
|
106
106
|
end
|
107
|
-
end
|
107
|
+
end
|