polyphony 0.36 → 0.38

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile +0 -11
  4. data/Gemfile.lock +1 -3
  5. data/Rakefile +4 -0
  6. data/TODO.md +12 -10
  7. data/docs/index.md +2 -1
  8. data/examples/core/xx-fork-cleanup.rb +22 -0
  9. data/ext/gyro/async.c +27 -13
  10. data/ext/gyro/child.c +29 -15
  11. data/ext/gyro/fiber.c +3 -1
  12. data/ext/gyro/gyro.c +0 -6
  13. data/ext/gyro/gyro.h +6 -0
  14. data/ext/gyro/io.c +24 -9
  15. data/ext/gyro/queue.c +21 -21
  16. data/ext/gyro/selector.c +23 -0
  17. data/ext/gyro/signal.c +24 -9
  18. data/ext/gyro/thread.c +12 -2
  19. data/ext/gyro/timer.c +33 -18
  20. data/lib/polyphony.rb +27 -36
  21. data/lib/polyphony/adapters/fs.rb +1 -4
  22. data/lib/polyphony/adapters/process.rb +29 -25
  23. data/lib/polyphony/adapters/trace.rb +129 -124
  24. data/lib/polyphony/core/channel.rb +36 -36
  25. data/lib/polyphony/core/exceptions.rb +29 -29
  26. data/lib/polyphony/core/global_api.rb +92 -91
  27. data/lib/polyphony/core/resource_pool.rb +84 -84
  28. data/lib/polyphony/core/sync.rb +17 -17
  29. data/lib/polyphony/core/thread_pool.rb +49 -37
  30. data/lib/polyphony/core/throttler.rb +25 -25
  31. data/lib/polyphony/extensions/core.rb +3 -3
  32. data/lib/polyphony/extensions/fiber.rb +269 -267
  33. data/lib/polyphony/extensions/openssl.rb +1 -1
  34. data/lib/polyphony/extensions/socket.rb +2 -1
  35. data/lib/polyphony/extensions/thread.rb +3 -3
  36. data/lib/polyphony/net.rb +71 -67
  37. data/lib/polyphony/version.rb +1 -1
  38. data/polyphony.gemspec +0 -3
  39. data/test/stress.rb +17 -12
  40. data/test/test_thread.rb +1 -0
  41. data/test/test_thread_pool.rb +2 -2
  42. data/test/test_throttler.rb +0 -1
  43. metadata +3 -16
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :stat,
4
- :read
5
-
6
3
  require 'fileutils'
7
4
 
8
- ThreadPool = import('./core/thread_pool')
5
+ require_relative './core/thread_pool'
9
6
 
10
7
  ::File.singleton_class.instance_eval do
11
8
  alias_method :orig_stat, :stat
@@ -1,29 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :watch
4
-
5
- def watch(cmd = nil, &block)
6
- terminated = nil
7
- pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
8
- watcher = Gyro::Child.new(pid)
9
- watcher.await
10
- terminated = true
11
- ensure
12
- kill_process(pid) unless terminated || pid.nil?
13
- end
14
-
15
- def kill_process(pid)
16
- cancel_after(5) do
17
- kill_and_await('TERM', pid)
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
- export :new, :analyze, :STOCK_EVENTS
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
- def new(*events)
10
- start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
11
- events = STOCK_EVENTS if events.empty?
12
- ::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
13
- end
14
-
15
- def trace_record(trp, start_stamp)
16
- stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
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
41
-
42
- SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
43
-
44
- def tp_schedule_value(trp)
45
- SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
46
- end
47
-
48
- def tp_raised_exception(trp)
49
- trp.event == :raise && trp.raised_exception
50
- end
51
-
52
- def analyze(records)
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
57
-
58
- # Implements fake TracePoint instances for fiber-related events
59
- class FiberTracePoint
60
- attr_reader :event, :fiber, :value
61
-
62
- def initialize(tpoint)
63
- @tp = tpoint
64
- @event = tpoint.return_value[0]
65
- @fiber = tpoint.return_value[1]
66
- @value = tpoint.return_value[2]
67
- end
68
-
69
- def lineno
70
- @tp.lineno
71
- end
72
-
73
- def method_id
74
- @tp.method_id
75
- end
76
-
77
- def path
78
- @tp.path
79
- end
80
-
81
- def self
82
- @tp.self
83
- end
84
-
85
- def binding
86
- @tp.binding
87
- end
88
- end
89
-
90
- class << ::TracePoint
91
- POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
92
-
93
- alias_method :orig_new, :new
94
- def new(*args, &block)
95
- events_mask, fiber_events_mask = event_masks(args)
96
-
97
- orig_new(*events_mask) do |tp|
98
- handle_tp_event(tp, fiber_events_mask, &block)
99
- end
100
- end
101
-
102
- def handle_tp_event(tpoint, fiber_events_mask)
103
- # next unless !$watched_fiber || Fiber.current == $watched_fiber
104
-
105
- if tpoint.method_id == :__fiber_trace__
106
- return if tpoint.event != :c_return
107
- return unless fiber_events_mask.include?(tpoint.return_value[0])
108
-
109
- tpoint = FiberTracePoint.new(tpoint)
110
- elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
111
- return
112
- end
113
-
114
- yield tpoint
115
- end
116
-
117
- ALL_FIBER_EVENTS = %i[
118
- fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
119
- fiber_ev_loop_enter fiber_ev_loop_leave
120
- ].freeze
121
-
122
- def event_masks(events)
123
- events.each_with_object([[], []]) do |e, masks|
124
- case e
125
- when /fiber_/
126
- masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
127
- masks[0] << :c_return unless masks[0].include?(:c_return)
128
- else
129
- masks[0] << e
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
- export_default :Channel
3
+ require_relative './exceptions'
4
4
 
5
- Exceptions = import('./exceptions')
6
-
7
- # Implements a unidirectional communication channel along the lines of Go
8
- # (buffered) channels.
9
- class Channel
10
- def initialize
11
- @payload_queue = []
12
- @waiting_queue = []
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
- def close
16
- stop = Exceptions::MoveOn.new
17
- @waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
18
- end
14
+ def close
15
+ stop = Polyphony::MoveOn.new
16
+ @waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
17
+ end
19
18
 
20
- def <<(value)
21
- if @waiting_queue.empty?
22
- @payload_queue << value
23
- else
24
- @waiting_queue.shift&.schedule(value)
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
- def receive
30
- Gyro.ref
31
- if @payload_queue.empty?
32
- @waiting_queue << Fiber.current
33
- suspend
34
- else
35
- receive_from_queue
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
- def receive_from_queue
42
- payload = @payload_queue.shift
43
- snooze
44
- payload
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
- export :BaseException, :MoveOn, :Cancel, :Terminate, :Restart
4
-
5
- # Common exception class for interrupting fibers. These exceptions allow
6
- # control of fibers. BaseException exceptions can encapsulate a value and thus
7
- # provide a way to interrupt long-running blocking operations while still
8
- # passing a value back to the call site. BaseException exceptions can also
9
- # references a cancel scope in order to allow correct bubbling of exceptions
10
- # through nested cancel scopes.
11
- class BaseException < ::Exception
12
- attr_reader :value
13
-
14
- def initialize(value = nil)
15
- @caller_backtrace = caller
16
- @value = value
17
- end
18
-
19
- def backtrace
20
- sanitize(@caller_backtrace)
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