polyphony 0.34 → 0.41
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 +34 -0
- data/Gemfile +0 -11
- data/Gemfile.lock +11 -10
- data/README.md +2 -1
- data/Rakefile +6 -2
- data/TODO.md +18 -95
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/nav.html +5 -5
- data/docs/api-reference.md +1 -1
- data/docs/api-reference/fiber.md +18 -0
- data/docs/api-reference/gyro-async.md +57 -0
- data/docs/api-reference/gyro-child.md +29 -0
- data/docs/api-reference/gyro-queue.md +44 -0
- data/docs/api-reference/gyro-timer.md +51 -0
- data/docs/api-reference/gyro.md +25 -0
- data/docs/index.md +10 -7
- data/docs/main-concepts/design-principles.md +67 -9
- data/docs/main-concepts/extending.md +1 -1
- data/docs/main-concepts/fiber-scheduling.md +55 -72
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-sleeping.rb +14 -6
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/io/tunnel.rb +48 -0
- 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 +14 -25
- data/ext/{gyro → polyphony}/extconf.rb +2 -2
- data/ext/polyphony/fiber.c +112 -0
- data/ext/{gyro → polyphony}/libev.c +0 -0
- data/ext/{gyro → polyphony}/libev.h +0 -0
- data/ext/polyphony/libev_agent.c +503 -0
- data/ext/polyphony/libev_queue.c +214 -0
- data/ext/polyphony/polyphony.c +89 -0
- data/ext/{gyro/gyro.h → polyphony/polyphony.h} +49 -59
- data/ext/polyphony/polyphony_ext.c +23 -0
- data/ext/{gyro → polyphony}/socket.c +21 -19
- data/ext/{gyro → polyphony}/thread.c +55 -119
- data/ext/{gyro → polyphony}/tracing.c +1 -1
- data/lib/polyphony.rb +37 -44
- data/lib/polyphony/adapters/fs.rb +1 -4
- data/lib/polyphony/adapters/irb.rb +2 -2
- data/lib/polyphony/adapters/postgres.rb +6 -5
- data/lib/polyphony/adapters/process.rb +27 -23
- data/lib/polyphony/adapters/trace.rb +110 -105
- data/lib/polyphony/core/channel.rb +35 -35
- data/lib/polyphony/core/exceptions.rb +29 -29
- data/lib/polyphony/core/global_api.rb +94 -91
- data/lib/polyphony/core/resource_pool.rb +83 -83
- data/lib/polyphony/core/sync.rb +16 -16
- data/lib/polyphony/core/thread_pool.rb +49 -37
- data/lib/polyphony/core/throttler.rb +30 -23
- data/lib/polyphony/event.rb +27 -0
- data/lib/polyphony/extensions/core.rb +23 -14
- data/lib/polyphony/extensions/fiber.rb +269 -267
- data/lib/polyphony/extensions/io.rb +56 -26
- data/lib/polyphony/extensions/openssl.rb +5 -9
- data/lib/polyphony/extensions/socket.rb +29 -10
- data/lib/polyphony/extensions/thread.rb +19 -12
- data/lib/polyphony/net.rb +64 -60
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +3 -6
- data/test/helper.rb +14 -1
- data/test/stress.rb +17 -12
- data/test/test_agent.rb +77 -0
- data/test/{test_async.rb → test_event.rb} +17 -9
- data/test/test_ext.rb +25 -4
- data/test/test_fiber.rb +23 -14
- data/test/test_global_api.rb +5 -5
- 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 +33 -0
- data/test/test_thread.rb +38 -16
- data/test/test_thread_pool.rb +3 -3
- data/test/test_throttler.rb +0 -1
- data/test/test_trace.rb +6 -5
- metadata +34 -39
- data/ext/gyro/async.c +0 -158
- data/ext/gyro/child.c +0 -117
- data/ext/gyro/gyro.c +0 -203
- data/ext/gyro/gyro_ext.c +0 -31
- data/ext/gyro/io.c +0 -447
- data/ext/gyro/queue.c +0 -142
- data/ext/gyro/selector.c +0 -183
- data/ext/gyro/signal.c +0 -108
- data/ext/gyro/timer.c +0 -154
- data/test/test_timer.rb +0 -56
@@ -13,10 +13,10 @@ if Object.constants.include?(:Reline)
|
|
13
13
|
fiber = Fiber.current
|
14
14
|
timer = spin do
|
15
15
|
sleep timeout
|
16
|
-
fiber.cancel
|
16
|
+
fiber.cancel
|
17
17
|
end
|
18
18
|
read_ios.each do |io|
|
19
|
-
|
19
|
+
Thread.current.agent.wait_io(io, false)
|
20
20
|
return [io]
|
21
21
|
end
|
22
22
|
rescue Polyphony::Cancel
|
@@ -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,29 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module Polyphony
|
4
|
+
# Process patches
|
5
|
+
module Process
|
6
|
+
class << self
|
7
|
+
def watch(cmd = nil, &block)
|
8
|
+
terminated = nil
|
9
|
+
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
10
|
+
Thread.current.agent.waitpid(pid)
|
11
|
+
terminated = true
|
12
|
+
ensure
|
13
|
+
kill_process(pid) unless terminated || pid.nil?
|
14
|
+
end
|
4
15
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
kill_process(pid) unless terminated || pid.nil?
|
13
|
-
end
|
16
|
+
def kill_process(pid)
|
17
|
+
cancel_after(5) do
|
18
|
+
kill_and_await('TERM', pid)
|
19
|
+
end
|
20
|
+
rescue Polyphony::Cancel
|
21
|
+
kill_and_await(-9, pid)
|
22
|
+
end
|
14
23
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
24
|
+
def kill_and_await(sig, pid)
|
25
|
+
::Process.kill(sig, pid)
|
26
|
+
Thread.current.agent.waitpid(pid)
|
27
|
+
rescue SystemCallError
|
28
|
+
# ignore
|
29
|
+
puts 'SystemCallError in kill_and_await'
|
30
|
+
end
|
31
|
+
end
|
18
32
|
end
|
19
|
-
rescue Polyphony::Cancel
|
20
|
-
kill_and_await(-9, pid)
|
21
|
-
end
|
22
|
-
|
23
|
-
def kill_and_await(sig, pid)
|
24
|
-
Process.kill(sig, pid)
|
25
|
-
Gyro::Child.new(pid).await
|
26
|
-
rescue SystemCallError
|
27
|
-
# ignore
|
28
|
-
puts 'SystemCallError in kill_and_await'
|
29
33
|
end
|
@@ -1,132 +1,137 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require 'polyphony'
|
3
|
+
require_relative '../../polyphony'
|
6
4
|
|
7
5
|
STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
{ stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
|
19
|
-
self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
|
20
|
-
lineno: trp.lineno, method_id: trp.method_id,
|
21
|
-
path: trp.path, parameters: tp_params(trp),
|
22
|
-
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
23
|
-
exception: tp_raised_exception(trp) }
|
24
|
-
end
|
25
|
-
|
26
|
-
def tp_fiber(trp)
|
27
|
-
trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
|
28
|
-
end
|
29
|
-
|
30
|
-
PARAMS_EVENTS = %i[call c_call b_call].freeze
|
31
|
-
|
32
|
-
def tp_params(trp)
|
33
|
-
PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
|
34
|
-
end
|
35
|
-
|
36
|
-
RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
|
37
|
-
|
38
|
-
def tp_return_value(trp)
|
39
|
-
RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
|
40
|
-
end
|
7
|
+
module Polyphony
|
8
|
+
# Tracing functionality for Polyphony
|
9
|
+
module Trace
|
10
|
+
class << self
|
11
|
+
def new(*events)
|
12
|
+
start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
13
|
+
events = STOCK_EVENTS if events.empty?
|
14
|
+
::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
|
15
|
+
end
|
41
16
|
|
42
|
-
|
17
|
+
def trace_record(trp, start_stamp)
|
18
|
+
stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
|
43
19
|
|
44
|
-
|
45
|
-
|
46
|
-
|
20
|
+
{ stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
|
21
|
+
self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
|
22
|
+
lineno: trp.lineno, method_id: trp.method_id,
|
23
|
+
path: trp.path, parameters: tp_params(trp),
|
24
|
+
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
25
|
+
exception: tp_raised_exception(trp) }
|
26
|
+
end
|
47
27
|
|
48
|
-
def
|
49
|
-
|
50
|
-
end
|
28
|
+
def tp_fiber(trp)
|
29
|
+
trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
|
30
|
+
end
|
51
31
|
|
52
|
-
|
53
|
-
by_fiber = Hash.new { |h, f| h[f] = [] }
|
54
|
-
records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
|
55
|
-
{ by_fiber: by_fiber }
|
56
|
-
end
|
32
|
+
PARAMS_EVENTS = %i[call c_call b_call].freeze
|
57
33
|
|
58
|
-
|
59
|
-
|
60
|
-
|
34
|
+
def tp_params(trp)
|
35
|
+
PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
|
36
|
+
end
|
61
37
|
|
62
|
-
|
63
|
-
@tp = tpoint
|
64
|
-
@event = tpoint.return_value[0]
|
65
|
-
@fiber = tpoint.return_value[1]
|
66
|
-
@value = tpoint.return_value[2]
|
67
|
-
end
|
38
|
+
RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
|
68
39
|
|
69
|
-
|
70
|
-
|
71
|
-
|
40
|
+
def tp_return_value(trp)
|
41
|
+
RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
|
42
|
+
end
|
72
43
|
|
73
|
-
|
74
|
-
@tp.method_id
|
75
|
-
end
|
44
|
+
SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
|
76
45
|
|
77
|
-
|
78
|
-
|
79
|
-
|
46
|
+
def tp_schedule_value(trp)
|
47
|
+
SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
|
48
|
+
end
|
80
49
|
|
81
|
-
|
82
|
-
|
83
|
-
|
50
|
+
def tp_raised_exception(trp)
|
51
|
+
trp.event == :raise && trp.raised_exception
|
52
|
+
end
|
84
53
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
54
|
+
def analyze(records)
|
55
|
+
by_fiber = Hash.new { |h, f| h[f] = [] }
|
56
|
+
records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
|
57
|
+
{ by_fiber: by_fiber }
|
58
|
+
end
|
89
59
|
|
90
|
-
|
91
|
-
|
60
|
+
# Implements fake TracePoint instances for fiber-related events
|
61
|
+
class FiberTracePoint
|
62
|
+
attr_reader :event, :fiber, :value
|
92
63
|
|
93
|
-
|
94
|
-
|
95
|
-
|
64
|
+
def initialize(tpoint)
|
65
|
+
@tp = tpoint
|
66
|
+
@event = tpoint.return_value[0]
|
67
|
+
@fiber = tpoint.return_value[1]
|
68
|
+
@value = tpoint.return_value[2]
|
69
|
+
end
|
96
70
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
71
|
+
def lineno
|
72
|
+
@tp.lineno
|
73
|
+
end
|
101
74
|
|
102
|
-
|
103
|
-
|
75
|
+
def method_id
|
76
|
+
@tp.method_id
|
77
|
+
end
|
104
78
|
|
105
|
-
|
106
|
-
|
107
|
-
|
79
|
+
def path
|
80
|
+
@tp.path
|
81
|
+
end
|
108
82
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
83
|
+
def self
|
84
|
+
@tp.self
|
85
|
+
end
|
113
86
|
|
114
|
-
|
115
|
-
|
87
|
+
def binding
|
88
|
+
@tp.binding
|
89
|
+
end
|
90
|
+
end
|
116
91
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
92
|
+
class << ::TracePoint
|
93
|
+
POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
|
94
|
+
|
95
|
+
alias_method :orig_new, :new
|
96
|
+
def new(*args, &block)
|
97
|
+
events_mask, fiber_events_mask = event_masks(args)
|
98
|
+
|
99
|
+
orig_new(*events_mask) do |tp|
|
100
|
+
handle_tp_event(tp, fiber_events_mask, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_tp_event(tpoint, fiber_events_mask)
|
105
|
+
# next unless !$watched_fiber || Fiber.current == $watched_fiber
|
106
|
+
|
107
|
+
if tpoint.method_id == :__fiber_trace__
|
108
|
+
return if tpoint.event != :c_return
|
109
|
+
return unless fiber_events_mask.include?(tpoint.return_value[0])
|
110
|
+
|
111
|
+
tpoint = FiberTracePoint.new(tpoint)
|
112
|
+
elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
yield tpoint
|
117
|
+
end
|
118
|
+
|
119
|
+
ALL_FIBER_EVENTS = %i[
|
120
|
+
fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
|
121
|
+
fiber_ev_loop_enter fiber_ev_loop_leave
|
122
|
+
].freeze
|
123
|
+
|
124
|
+
def event_masks(events)
|
125
|
+
events.each_with_object([[], []]) do |e, masks|
|
126
|
+
case e
|
127
|
+
when /fiber_/
|
128
|
+
masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
|
129
|
+
masks[0] << :c_return unless masks[0].include?(:c_return)
|
130
|
+
else
|
131
|
+
masks[0] << e
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
130
135
|
end
|
131
136
|
end
|
132
137
|
end
|
@@ -1,46 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative './exceptions'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
5
|
+
module Polyphony
|
6
|
+
# Implements a unidirectional communication channel along the lines of Go
|
7
|
+
# (buffered) channels.
|
8
|
+
class Channel
|
9
|
+
def initialize
|
10
|
+
@payload_queue = []
|
11
|
+
@waiting_queue = []
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
def close
|
15
|
+
stop = Polyphony::MoveOn.new
|
16
|
+
@waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
|
17
|
+
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
def <<(value)
|
20
|
+
if @waiting_queue.empty?
|
21
|
+
@payload_queue << value
|
22
|
+
else
|
23
|
+
@waiting_queue.shift&.schedule(value)
|
24
|
+
end
|
25
|
+
snooze
|
25
26
|
end
|
26
|
-
snooze
|
27
|
-
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
def receive
|
29
|
+
Polyphony.ref
|
30
|
+
if @payload_queue.empty?
|
31
|
+
@waiting_queue << Fiber.current
|
32
|
+
suspend
|
33
|
+
else
|
34
|
+
receive_from_queue
|
35
|
+
end
|
36
|
+
ensure
|
37
|
+
Polyphony.unref
|
36
38
|
end
|
37
|
-
ensure
|
38
|
-
Gyro.unref
|
39
|
-
end
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
def receive_from_queue
|
41
|
+
payload = @payload_queue.shift
|
42
|
+
snooze
|
43
|
+
payload
|
44
|
+
end
|
45
45
|
end
|
46
46
|
end
|