polyphony 0.36 → 0.38

Sign up to get free protection for your applications and to get access to all the features.
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