polyphony 0.36 → 0.38
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile +0 -11
- data/Gemfile.lock +1 -3
- data/Rakefile +4 -0
- data/TODO.md +12 -10
- data/docs/index.md +2 -1
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/ext/gyro/async.c +27 -13
- data/ext/gyro/child.c +29 -15
- data/ext/gyro/fiber.c +3 -1
- data/ext/gyro/gyro.c +0 -6
- data/ext/gyro/gyro.h +6 -0
- data/ext/gyro/io.c +24 -9
- data/ext/gyro/queue.c +21 -21
- data/ext/gyro/selector.c +23 -0
- data/ext/gyro/signal.c +24 -9
- data/ext/gyro/thread.c +12 -2
- data/ext/gyro/timer.c +33 -18
- data/lib/polyphony.rb +27 -36
- data/lib/polyphony/adapters/fs.rb +1 -4
- data/lib/polyphony/adapters/process.rb +29 -25
- data/lib/polyphony/adapters/trace.rb +129 -124
- data/lib/polyphony/core/channel.rb +36 -36
- data/lib/polyphony/core/exceptions.rb +29 -29
- data/lib/polyphony/core/global_api.rb +92 -91
- data/lib/polyphony/core/resource_pool.rb +84 -84
- data/lib/polyphony/core/sync.rb +17 -17
- data/lib/polyphony/core/thread_pool.rb +49 -37
- data/lib/polyphony/core/throttler.rb +25 -25
- data/lib/polyphony/extensions/core.rb +3 -3
- data/lib/polyphony/extensions/fiber.rb +269 -267
- data/lib/polyphony/extensions/openssl.rb +1 -1
- data/lib/polyphony/extensions/socket.rb +2 -1
- data/lib/polyphony/extensions/thread.rb +3 -3
- data/lib/polyphony/net.rb +71 -67
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +0 -3
- data/test/stress.rb +17 -12
- data/test/test_thread.rb +1 -0
- data/test/test_thread_pool.rb +2 -2
- data/test/test_throttler.rb +0 -1
- metadata +3 -16
@@ -1,29 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
3
|
+
module Polyphony
|
4
|
+
module Process
|
5
|
+
class << self
|
6
|
+
def watch(cmd = nil, &block)
|
7
|
+
terminated = nil
|
8
|
+
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
9
|
+
watcher = Gyro::Child.new(pid)
|
10
|
+
watcher.await
|
11
|
+
terminated = true
|
12
|
+
ensure
|
13
|
+
kill_process(pid) unless terminated || pid.nil?
|
14
|
+
end
|
15
|
+
|
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
|
23
|
+
|
24
|
+
def kill_and_await(sig, pid)
|
25
|
+
::Process.kill(sig, pid)
|
26
|
+
Gyro::Child.new(pid).await
|
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,133 +1,138 @@
|
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
7
|
+
module Polyphony
|
8
|
+
module Trace
|
9
|
+
class << self
|
10
|
+
def new(*events)
|
11
|
+
start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
12
|
+
events = STOCK_EVENTS if events.empty?
|
13
|
+
::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def trace_record(trp, start_stamp)
|
17
|
+
stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
|
18
|
+
|
19
|
+
{ stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
|
20
|
+
self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
|
21
|
+
lineno: trp.lineno, method_id: trp.method_id,
|
22
|
+
path: trp.path, parameters: tp_params(trp),
|
23
|
+
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
24
|
+
exception: tp_raised_exception(trp) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def tp_fiber(trp)
|
28
|
+
trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
|
29
|
+
end
|
30
|
+
|
31
|
+
PARAMS_EVENTS = %i[call c_call b_call].freeze
|
32
|
+
|
33
|
+
def tp_params(trp)
|
34
|
+
PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
|
38
|
+
|
39
|
+
def tp_return_value(trp)
|
40
|
+
RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
|
44
|
+
|
45
|
+
def tp_schedule_value(trp)
|
46
|
+
SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def tp_raised_exception(trp)
|
50
|
+
trp.event == :raise && trp.raised_exception
|
51
|
+
end
|
52
|
+
|
53
|
+
def analyze(records)
|
54
|
+
by_fiber = Hash.new { |h, f| h[f] = [] }
|
55
|
+
records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
|
56
|
+
{ by_fiber: by_fiber }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Implements fake TracePoint instances for fiber-related events
|
60
|
+
class FiberTracePoint
|
61
|
+
attr_reader :event, :fiber, :value
|
62
|
+
|
63
|
+
def initialize(tpoint)
|
64
|
+
@tp = tpoint
|
65
|
+
@event = tpoint.return_value[0]
|
66
|
+
@fiber = tpoint.return_value[1]
|
67
|
+
@value = tpoint.return_value[2]
|
68
|
+
end
|
69
|
+
|
70
|
+
def lineno
|
71
|
+
@tp.lineno
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_id
|
75
|
+
@tp.method_id
|
76
|
+
end
|
77
|
+
|
78
|
+
def path
|
79
|
+
@tp.path
|
80
|
+
end
|
81
|
+
|
82
|
+
def self
|
83
|
+
@tp.self
|
84
|
+
end
|
85
|
+
|
86
|
+
def binding
|
87
|
+
@tp.binding
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class << ::TracePoint
|
92
|
+
POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
|
93
|
+
|
94
|
+
alias_method :orig_new, :new
|
95
|
+
def new(*args, &block)
|
96
|
+
events_mask, fiber_events_mask = event_masks(args)
|
97
|
+
|
98
|
+
orig_new(*events_mask) do |tp|
|
99
|
+
handle_tp_event(tp, fiber_events_mask, &block)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_tp_event(tpoint, fiber_events_mask)
|
104
|
+
# next unless !$watched_fiber || Fiber.current == $watched_fiber
|
105
|
+
|
106
|
+
if tpoint.method_id == :__fiber_trace__
|
107
|
+
return if tpoint.event != :c_return
|
108
|
+
return unless fiber_events_mask.include?(tpoint.return_value[0])
|
109
|
+
|
110
|
+
tpoint = FiberTracePoint.new(tpoint)
|
111
|
+
elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
|
112
|
+
return
|
113
|
+
end
|
114
|
+
|
115
|
+
yield tpoint
|
116
|
+
end
|
117
|
+
|
118
|
+
ALL_FIBER_EVENTS = %i[
|
119
|
+
fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
|
120
|
+
fiber_ev_loop_enter fiber_ev_loop_leave
|
121
|
+
].freeze
|
122
|
+
|
123
|
+
def event_masks(events)
|
124
|
+
events.each_with_object([[], []]) do |e, masks|
|
125
|
+
case e
|
126
|
+
when /fiber_/
|
127
|
+
masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
|
128
|
+
masks[0] << :c_return unless masks[0].include?(:c_return)
|
129
|
+
else
|
130
|
+
masks[0] << e
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
130
134
|
end
|
131
135
|
end
|
132
136
|
end
|
133
137
|
end
|
138
|
+
|
@@ -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
|
+
Gyro.ref
|
30
|
+
if @payload_queue.empty?
|
31
|
+
@waiting_queue << Fiber.current
|
32
|
+
suspend
|
33
|
+
else
|
34
|
+
receive_from_queue
|
35
|
+
end
|
36
|
+
ensure
|
37
|
+
Gyro.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
|
-
end
|
46
|
+
end
|
@@ -1,36 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
3
|
+
module Polyphony
|
4
|
+
# Common exception class for interrupting fibers. These exceptions allow
|
5
|
+
# control of fibers. BaseException exceptions can encapsulate a value and thus
|
6
|
+
# provide a way to interrupt long-running blocking operations while still
|
7
|
+
# passing a value back to the call site. BaseException exceptions can also
|
8
|
+
# references a cancel scope in order to allow correct bubbling of exceptions
|
9
|
+
# through nested cancel scopes.
|
10
|
+
class BaseException < ::Exception
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
def initialize(value = nil)
|
14
|
+
@caller_backtrace = caller
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def backtrace
|
19
|
+
sanitize(@caller_backtrace)
|
20
|
+
end
|
21
21
|
end
|
22
|
-
end
|
23
22
|
|
24
|
-
# MoveOn is used to interrupt a long-running blocking operation, while
|
25
|
-
# continuing the rest of the computation.
|
26
|
-
class MoveOn < BaseException; end
|
23
|
+
# MoveOn is used to interrupt a long-running blocking operation, while
|
24
|
+
# continuing the rest of the computation.
|
25
|
+
class MoveOn < BaseException; end
|
27
26
|
|
28
|
-
# Cancel is used to interrupt a long-running blocking operation, bubbling the
|
29
|
-
# exception up through cancel scopes and supervisors.
|
30
|
-
class Cancel < BaseException; end
|
27
|
+
# Cancel is used to interrupt a long-running blocking operation, bubbling the
|
28
|
+
# exception up through cancel scopes and supervisors.
|
29
|
+
class Cancel < BaseException; end
|
31
30
|
|
32
|
-
# Terminate is used to interrupt a fiber once its parent fiber has terminated.
|
33
|
-
class Terminate < BaseException; end
|
31
|
+
# Terminate is used to interrupt a fiber once its parent fiber has terminated.
|
32
|
+
class Terminate < BaseException; end
|
34
33
|
|
35
|
-
# Restart is used to restart a fiber
|
36
|
-
class Restart < BaseException; end
|
34
|
+
# Restart is used to restart a fiber
|
35
|
+
class Restart < BaseException; end
|
36
|
+
end
|