polyphony 0.79 → 0.81.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile.lock +2 -1
  4. data/examples/core/raw_buffer_test.rb +8 -0
  5. data/examples/core/zlib_stream.rb +16 -0
  6. data/ext/polyphony/backend_common.c +2 -1
  7. data/ext/polyphony/backend_common.h +7 -2
  8. data/ext/polyphony/backend_io_uring.c +69 -30
  9. data/ext/polyphony/polyphony.c +8 -0
  10. data/ext/polyphony/polyphony.h +11 -0
  11. data/lib/polyphony/adapters/fs.rb +4 -0
  12. data/lib/polyphony/adapters/process.rb +14 -1
  13. data/lib/polyphony/adapters/redis.rb +28 -0
  14. data/lib/polyphony/adapters/sequel.rb +19 -1
  15. data/lib/polyphony/core/debug.rb +129 -72
  16. data/lib/polyphony/core/exceptions.rb +21 -6
  17. data/lib/polyphony/core/global_api.rb +228 -73
  18. data/lib/polyphony/core/resource_pool.rb +65 -20
  19. data/lib/polyphony/core/sync.rb +57 -12
  20. data/lib/polyphony/core/thread_pool.rb +42 -5
  21. data/lib/polyphony/core/throttler.rb +21 -5
  22. data/lib/polyphony/core/timer.rb +125 -1
  23. data/lib/polyphony/extensions/exception.rb +36 -6
  24. data/lib/polyphony/extensions/fiber.rb +238 -57
  25. data/lib/polyphony/extensions/io.rb +4 -2
  26. data/lib/polyphony/extensions/kernel.rb +9 -4
  27. data/lib/polyphony/extensions/object.rb +8 -0
  28. data/lib/polyphony/extensions/openssl.rb +3 -1
  29. data/lib/polyphony/extensions/socket.rb +458 -39
  30. data/lib/polyphony/extensions/thread.rb +108 -43
  31. data/lib/polyphony/extensions/timeout.rb +12 -1
  32. data/lib/polyphony/extensions.rb +1 -0
  33. data/lib/polyphony/net.rb +66 -7
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +0 -2
  36. data/test/test_backend.rb +6 -2
  37. data/test/test_global_api.rb +0 -23
  38. data/test/test_resource_pool.rb +1 -1
  39. data/test/test_throttler.rb +0 -6
  40. data/test/test_trace.rb +87 -0
  41. metadata +10 -8
  42. data/lib/polyphony/core/channel.rb +0 -15
@@ -5,7 +5,8 @@ module Polyphony
5
5
  class ResourcePool
6
6
  attr_reader :limit, :size
7
7
 
8
- # Initializes a new resource pool
8
+ # Initializes a new resource pool.
9
+ #
9
10
  # @param opts [Hash] options
10
11
  # @param &block [Proc] allocator block
11
12
  def initialize(opts, &block)
@@ -16,10 +17,27 @@ module Polyphony
16
17
  @acquired_resources = {}
17
18
  end
18
19
 
20
+ # Returns number of currently available resources.
21
+ #
22
+ # @return [Integer] size of resource stock
19
23
  def available
20
24
  @stock.size
21
25
  end
22
26
 
27
+ # Acquires a resource, passing it to the given block. If no resource is
28
+ # available, blocks until a resource becomes available. After the block has
29
+ # run, the resource is released back to the pool. The resource is passed to
30
+ # the block as its only argument.
31
+ #
32
+ # This method is re-entrant: if called from the same fiber, it will immediately
33
+ # return the resource currently acquired by the fiber.
34
+ #
35
+ # rows = db_pool.acquire do |db|
36
+ # db.query(sql).to_a
37
+ # end
38
+ #
39
+ # @param &block [Proc] code to run
40
+ # @return [any] return value of block
23
41
  def acquire(&block)
24
42
  fiber = Fiber.current
25
43
  return yield @acquired_resources[fiber] if @acquired_resources[fiber]
@@ -27,6 +45,50 @@ module Polyphony
27
45
  acquire_from_stock(fiber, &block)
28
46
  end
29
47
 
48
+ # Acquires a resource, proxies the method calls to the resource, then
49
+ # releases it. Methods can also be called with blocks, as in the following
50
+ # example:
51
+ #
52
+ # db_pool.query(sql) { |result|
53
+ # process_result_rows(result)
54
+ # }
55
+ #
56
+ # @param sym [Symbol] method name
57
+ # @param *args [Array<any>] method arguments
58
+ # @param &block [Proc] block passed to method
59
+ def method_missing(sym, *args, &block)
60
+ acquire { |r| r.send(sym, *args, &block) }
61
+ end
62
+
63
+ # :no-doc:
64
+ def respond_to_missing?(*_args)
65
+ true
66
+ end
67
+
68
+ # Discards the currently-acquired resource
69
+ # instead of returning it to the pool when done.
70
+ #
71
+ # @return [Polyphony::ResourcePool] self
72
+ def discard!
73
+ @size -= 1 if @acquired_resources.delete(Fiber.current)
74
+ self
75
+ end
76
+
77
+ # Fills the pool to capacity.
78
+ #
79
+ # @return [Polyphony::ResourcePool] self
80
+ def fill!
81
+ add_to_stock while @size < @limit
82
+ self
83
+ end
84
+
85
+ private
86
+
87
+ # Acquires a resource from stock, yielding it to the given block.
88
+ #
89
+ # @param fiber [Fiber] the fiber the resource will be associated with
90
+ # @param &block [Proc] given block
91
+ # @return [any] return value of block
30
92
  def acquire_from_stock(fiber)
31
93
  add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
32
94
  resource = @stock.shift
@@ -39,30 +101,13 @@ module Polyphony
39
101
  end
40
102
  end
41
103
 
42
- def method_missing(sym, *args, &block)
43
- acquire { |r| r.send(sym, *args, &block) }
44
- end
45
-
46
- def respond_to_missing?(*_args)
47
- true
48
- end
49
-
50
- # Allocates a resource
104
+ # Creates a resource, adding it to the stock.
105
+ #
51
106
  # @return [any] allocated resource
52
107
  def add_to_stock
53
108
  @size += 1
54
109
  resource = @allocator.call
55
110
  @stock << resource
56
111
  end
57
-
58
- # Discards the currently-acquired resource
59
- # instead of returning it to the pool when done.
60
- def discard!
61
- @size -= 1 if @acquired_resources.delete(Fiber.current)
62
- end
63
-
64
- def preheat!
65
- add_to_stock while @size < @limit
66
- end
67
112
  end
68
113
  end
@@ -1,56 +1,94 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- # Implements mutex lock for synchronizing access to a shared resource
4
+
5
+ # Implements mutex lock for synchronizing access to a shared resource. This
6
+ # class replaces the stock `Thread::Mutex` class.
5
7
  class Mutex
8
+
9
+ # Initializes a new mutex.
6
10
  def initialize
7
11
  @store = Queue.new
8
12
  @store << :token
9
13
  end
10
14
 
15
+ # Locks the mutex, runs the block, then unlocks it.
16
+ #
17
+ # This method is re-entrant. Recursive calls from the given block will not
18
+ # block.
19
+ #
20
+ # @param &block [Proc] code to run
21
+ # @return [any] return value of block
11
22
  def synchronize(&block)
12
23
  return yield if @holding_fiber == Fiber.current
13
24
 
14
25
  synchronize_not_holding(&block)
15
26
  end
16
27
 
17
- def synchronize_not_holding
18
- @token = @store.shift
19
- begin
20
- @holding_fiber = Fiber.current
21
- yield
22
- ensure
23
- @holding_fiber = nil
24
- @store << @token if @token
25
- end
26
- end
27
-
28
+ # Conditionally releases the mutex. This method is used by condition
29
+ # variables.
30
+ #
31
+ # @return [void]
28
32
  def conditional_release
29
33
  @store << @token
30
34
  @token = nil
31
35
  @holding_fiber = nil
32
36
  end
33
37
 
38
+ # Conditionally reacquires the mutex. This method is used by condition
39
+ # variables.
40
+ #
41
+ # @return [void]
34
42
  def conditional_reacquire
35
43
  @token = @store.shift
36
44
  @holding_fiber = Fiber.current
37
45
  end
38
46
 
47
+ # Returns the fiber currently owning the mutex.
48
+ #
49
+ # @return [Fiber, nil] current owner or nil
39
50
  def owned?
40
51
  @holding_fiber == Fiber.current
41
52
  end
42
53
 
54
+ # Returns a truthy value if the mutex is currently locked.
55
+ #
56
+ # @return [any] truthy if fiber is currently locked
43
57
  def locked?
44
58
  @holding_fiber
45
59
  end
60
+
61
+ private
62
+
63
+ # Helper method for performing a `#synchronize` when not currently holding
64
+ # the mutex.
65
+ #
66
+ # @return [any] return value of given block.
67
+ def synchronize_not_holding
68
+ @token = @store.shift
69
+ begin
70
+ @holding_fiber = Fiber.current
71
+ yield
72
+ ensure
73
+ @holding_fiber = nil
74
+ @store << @token if @token
75
+ end
76
+ end
46
77
  end
47
78
 
48
79
  # Implements a fiber-aware ConditionVariable
49
80
  class ConditionVariable
81
+
82
+ # Initializes the condition variable.
50
83
  def initialize
51
84
  @queue = Polyphony::Queue.new
52
85
  end
53
86
 
87
+ # Waits for the condition variable to be signalled.
88
+ #
89
+ # @param mutex [Polyphony::Mutex] mutex to release while waiting for signal
90
+ # @param timeout [Number, nil] timeout in seconds (currently not implemented)
91
+ # @return [void]
54
92
  def wait(mutex, _timeout = nil)
55
93
  mutex.conditional_release
56
94
  @queue << Fiber.current
@@ -58,11 +96,18 @@ module Polyphony
58
96
  mutex.conditional_reacquire
59
97
  end
60
98
 
99
+ # Signals the condition variable, causing the first fiber in the waiting
100
+ # queue to be resumed.
101
+ #
102
+ # @return [void]
61
103
  def signal
62
104
  fiber = @queue.shift
63
105
  fiber.schedule
64
106
  end
65
107
 
108
+ # Resumes all waiting fibers.
109
+ #
110
+ # @return [void]
66
111
  def broadcast
67
112
  while (fiber = @queue.shift)
68
113
  fiber.schedule
@@ -3,15 +3,25 @@
3
3
  require 'etc'
4
4
 
5
5
  module Polyphony
6
+
6
7
  # Implements a pool of threads
7
8
  class ThreadPool
9
+
10
+ # The pool size.
8
11
  attr_reader :size
9
12
 
13
+ # Runs the given block on an available thread from the default thread pool.
14
+ #
15
+ # @param &block [Proc] given block
16
+ # @return [any] return value of given block
10
17
  def self.process(&block)
11
18
  @default_pool ||= new
12
19
  @default_pool.process(&block)
13
20
  end
14
21
 
22
+ # Resets the default thread pool.
23
+ #
24
+ # @return [void]
15
25
  def self.reset
16
26
  return unless @default_pool
17
27
 
@@ -19,12 +29,20 @@ module Polyphony
19
29
  @default_pool = nil
20
30
  end
21
31
 
32
+ # Initializes the thread pool. The pool size defaults to the number of
33
+ # available CPU cores.
34
+ #
35
+ # @param size [Integer] number of threads in pool
22
36
  def initialize(size = Etc.nprocessors)
23
37
  @size = size
24
38
  @task_queue = Polyphony::Queue.new
25
39
  @threads = (1..@size).map { Thread.new { thread_loop } }
26
40
  end
27
41
 
42
+ # Runs the given block on an available thread from the pool.
43
+ #
44
+ # @param &block [Proc] given block
45
+ # @return [any] return value of block
28
46
  def process(&block)
29
47
  setup unless @task_queue
30
48
 
@@ -33,6 +51,12 @@ module Polyphony
33
51
  watcher.await
34
52
  end
35
53
 
54
+ # Adds a task to be performed asynchronously on a thread from the pool. This
55
+ # method does not block. The task will be performed once a thread becomes
56
+ # available.
57
+ #
58
+ # @param &block [Proc] given block
59
+ # @return [Polyphony::ThreadPool] self
36
60
  def cast(&block)
37
61
  setup unless @task_queue
38
62
 
@@ -40,16 +64,34 @@ module Polyphony
40
64
  self
41
65
  end
42
66
 
67
+ # Returns true if there are any currently running tasks, or any pending
68
+ # tasks waiting for a thread to become available.
69
+ #
70
+ # @return [bool] true if the pool is busy
43
71
  def busy?
44
72
  !@task_queue.empty?
45
73
  end
46
74
 
75
+ # Stops and waits for all threads in the queue to terminate.
76
+ def stop
77
+ @threads.each(&:kill)
78
+ @threads.each(&:join)
79
+ end
80
+
81
+ private
82
+
83
+ # Runs a processing loop on a worker thread.
84
+ #
85
+ # @return [void]
47
86
  def thread_loop
48
87
  while true
49
88
  run_queued_task
50
89
  end
51
90
  end
52
91
 
92
+ # Runs the first queued task in the task queue.
93
+ #
94
+ # @return [void]
53
95
  def run_queued_task
54
96
  (block, watcher) = @task_queue.shift
55
97
  result = block.()
@@ -57,10 +99,5 @@ module Polyphony
57
99
  rescue Exception => e
58
100
  watcher ? watcher.signal(e) : raise(e)
59
101
  end
60
-
61
- def stop
62
- @threads.each(&:kill)
63
- @threads.each(&:join)
64
- end
65
102
  end
66
103
  end
@@ -3,31 +3,47 @@
3
3
  module Polyphony
4
4
  # Implements general-purpose throttling
5
5
  class Throttler
6
+
7
+ # Initializes a throttler instance with the given rate.
8
+ #
9
+ # @param rate [Number] throttler rate in times per second
6
10
  def initialize(rate)
7
11
  @rate = rate_from_argument(rate)
8
12
  @min_dt = 1.0 / @rate
9
13
  @next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
10
14
  end
11
15
 
16
+ # call-seq:
17
+ # throttler.call { ... }
18
+ # throttler.process { ... }
19
+ #
20
+ # Invokes the throttler with the given block. The throttler will
21
+ # automatically introduce a delay to keep to the maximum specified rate.
22
+ # The throttler instance is passed to the given block.
23
+ #
24
+ # @return [any] given block's return value
12
25
  def call
13
26
  now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
14
27
  delta = @next_time - now
15
28
  Polyphony.backend_sleep(delta) if delta > 0
16
- yield self
29
+ result = yield self
17
30
 
18
31
  while true
19
32
  @next_time += @min_dt
20
33
  break if @next_time > now
21
34
  end
35
+
36
+ result
22
37
  end
23
38
  alias_method :process, :call
24
39
 
25
- def stop
26
- @stop = true
27
- end
28
-
29
40
  private
30
41
 
42
+ # Converts the given argument to a rate. If a hash is given, the throttler's
43
+ # rate is computed from the value of either the `:interval` or `:rate` keys.
44
+ #
45
+ # @param arg [Number, Hash] rate argument
46
+ # @return [Number] rate in times per second
31
47
  def rate_from_argument(arg)
32
48
  return arg if arg.is_a?(Numeric)
33
49
 
@@ -1,17 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- # Implements a common timer for running multiple timeouts
4
+
5
+ # Implements a common timer for running multiple timeouts. This class may be
6
+ # used to reduce the timer granularity in case a large number of timeouts is
7
+ # used concurrently. This class basically provides the same methods as global
8
+ # methods concerned with timeouts, such as `#cancel_after`, `#every` etc.
5
9
  class Timer
10
+
11
+ # Initializes a new timer with the given resolution.
12
+ #
13
+ # @param tag [any] tag to use for the timer's fiber
14
+ # @param resolution: [Number] timer granularity in seconds or fractions thereof
6
15
  def initialize(tag = nil, resolution:)
7
16
  @fiber = spin_loop(tag, interval: resolution) { update }
8
17
  @timeouts = {}
9
18
  end
10
19
 
20
+ # Stops the timer's associated fiber.
21
+ #
22
+ # @return [Polyphony::Timer] self
11
23
  def stop
12
24
  @fiber.stop
25
+ self
13
26
  end
14
27
 
28
+ # Sleeps for the given duration.
29
+ #
30
+ # @param duration [Number] sleep duration in seconds
31
+ # @return [void]
15
32
  def sleep(duration)
16
33
  fiber = Fiber.current
17
34
  @timeouts[fiber] = {
@@ -23,6 +40,12 @@ module Polyphony
23
40
  @timeouts.delete(fiber)
24
41
  end
25
42
 
43
+ # Spins up a fiber that will run the given block after sleeping for the
44
+ # given delay.
45
+ #
46
+ # @param delay [Number] delay in seconds before running the given block
47
+ # @param &block [Proc] block to run
48
+ # @return [Fiber] spun fiber
26
49
  def after(interval, &block)
27
50
  spin do
28
51
  self.sleep interval
@@ -30,6 +53,12 @@ module Polyphony
30
53
  end
31
54
  end
32
55
 
56
+ # Runs the given block in an infinite loop with a regular interval between
57
+ # consecutive iterations.
58
+ #
59
+ # @param interval [Number] interval between consecutive iterations in seconds
60
+ # @param &block [Proc] block to run
61
+ # @return [void]
33
62
  def every(interval)
34
63
  fiber = Fiber.current
35
64
  @timeouts[fiber] = {
@@ -45,6 +74,44 @@ module Polyphony
45
74
  @timeouts.delete(fiber)
46
75
  end
47
76
 
77
+ # call-seq:
78
+ # timer.cancel_after(interval) { ... }
79
+ # timer.cancel_after(interval, with_exception: exception) { ... }
80
+ # timer.cancel_after(interval, with_exception: [klass, message]) { ... }
81
+ # timer.cancel_after(interval) { |timeout| ... }
82
+ # timer.cancel_after(interval, with_exception: exception) { |timeout| ... }
83
+ # timer.cancel_after(interval, with_exception: [klass, message]) { |timeout| ... }
84
+ #
85
+ # Runs the given block after setting up a cancellation timer for
86
+ # cancellation. If the cancellation timer elapses, the execution will be
87
+ # interrupted with an exception defaulting to `Polyphony::Cancel`.
88
+ #
89
+ # This method should be used when a timeout should cause an exception to be
90
+ # propagated down the call stack or up the fiber tree.
91
+ #
92
+ # Example of normal use:
93
+ #
94
+ # def read_from_io_with_timeout(io)
95
+ # timer.cancel_after(10) { io.read }
96
+ # rescue Polyphony::Cancel
97
+ # nil
98
+ # end
99
+ #
100
+ # The timeout period can be reset using `Timer#reset`, as shown in the
101
+ # following example:
102
+ #
103
+ # timer.cancel_after(10) do
104
+ # loop do
105
+ # msg = socket.gets
106
+ # timer.reset
107
+ # handle_msg(msg)
108
+ # end
109
+ # end
110
+ #
111
+ # @param interval [Number] timout in seconds
112
+ # @param with_exception: [Class, Exception] exception or exception class
113
+ # @param &block [Proc] block to execute
114
+ # @return [any] block's return value
48
115
  def cancel_after(interval, with_exception: Polyphony::Cancel)
49
116
  fiber = Fiber.current
50
117
  @timeouts[fiber] = {
@@ -57,6 +124,48 @@ module Polyphony
57
124
  @timeouts.delete(fiber)
58
125
  end
59
126
 
127
+ # call-seq:
128
+ # timer.move_on_after(interval) { ... }
129
+ # timer.move_on_after(interval, with_value: value) { ... }
130
+ # timer.move_on_after(interval) { |canceller| ... }
131
+ # timer.move_on_after(interval, with_value: value) { |canceller| ... }
132
+ #
133
+ # Runs the given block after setting up a cancellation timer for
134
+ # cancellation. If the cancellation timer elapses, the execution will be
135
+ # interrupted with a `Polyphony::MoveOn` exception, which will be rescued,
136
+ # and with cause the operation to return the given value.
137
+ #
138
+ # This method should be used when a timeout is to be handled locally,
139
+ # without generating an exception that is to propagated down the call stack
140
+ # or up the fiber tree.
141
+ #
142
+ # Example of normal use:
143
+ #
144
+ # timer.move_on_after(10) {
145
+ # sleep 60
146
+ # 42
147
+ # } #=> nil
148
+ #
149
+ # timer.move_on_after(10, with_value: :oops) {
150
+ # sleep 60
151
+ # 42
152
+ # } #=> :oops
153
+ #
154
+ # The timeout period can be reset using `Timer#reset`, as shown in the
155
+ # following example:
156
+ #
157
+ # timer.move_on_after(10) do
158
+ # loop do
159
+ # msg = socket.gets
160
+ # timer.reset
161
+ # handle_msg(msg)
162
+ # end
163
+ # end
164
+ #
165
+ # @param interval [Number] timout in seconds
166
+ # @param with_value: [any] return value in case of timeout
167
+ # @param &block [Proc] block to execute
168
+ # @return [any] block's return value
60
169
  def move_on_after(interval, with_value: nil)
61
170
  fiber = Fiber.current
62
171
  @timeouts[fiber] = {
@@ -71,6 +180,9 @@ module Polyphony
71
180
  @timeouts.delete(fiber)
72
181
  end
73
182
 
183
+ # Resets the timeout for the current fiber.
184
+ #
185
+ # @return [void]
74
186
  def reset
75
187
  record = @timeouts[Fiber.current]
76
188
  return unless record
@@ -80,21 +192,33 @@ module Polyphony
80
192
 
81
193
  private
82
194
 
195
+ # Returns the current monotonic clock value.
196
+ #
197
+ # @return [Number] monotonic clock value in seconds
83
198
  def now
84
199
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
85
200
  end
86
201
 
202
+ # Converts a timeout record's exception spec to an exception instance.
203
+ #
204
+ # @param record [Array, Class, Exception, String] exception spec
205
+ # @return [Exception] exception instance
87
206
  def timeout_exception(record)
88
207
  case (exception = record[:exception])
89
208
  when Array
90
209
  exception[0].new(exception[1])
91
210
  when Class
92
211
  exception.new
212
+ when Exception
213
+ exception
93
214
  else
94
215
  RuntimeError.new(exception)
95
216
  end
96
217
  end
97
218
 
219
+ # Runs a timer iteration, invoking any timeouts that are due.
220
+ #
221
+ # @return [void]
98
222
  def update
99
223
  return if @timeouts.empty?
100
224
 
@@ -1,20 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Exeption overrides
3
+ # Extensions to the Exception class
4
4
  class ::Exception
5
5
  class << self
6
+
7
+ # Set to true to disable sanitizing the backtrace (to remove frames occuring
8
+ # in the Polyphony code itself.)
6
9
  attr_accessor :__disable_sanitized_backtrace__
7
10
  end
8
11
 
9
- attr_accessor :source_fiber, :raising_fiber
12
+ # Set to the fiber in which the exception was *originally* raised (in case the
13
+ # exception was not caught.) The exception will propagate up the fiber tree,
14
+ # allowing it to be caught in any of the fiber's ancestors, while the
15
+ # `@source_fiber`` attribute will continue pointing to the original fiber.
16
+ attr_accessor :source_fiber
17
+
18
+ # Set to the fiber from which the exception was raised.
19
+ attr_accessor :raising_fiber
10
20
 
11
21
  alias_method :orig_initialize, :initialize
22
+
23
+ # Initializes the exception with the given arguments.
12
24
  def initialize(*args)
13
25
  @raising_fiber = Fiber.current
14
26
  orig_initialize(*args)
15
27
  end
16
28
 
17
29
  alias_method :orig_backtrace, :backtrace
30
+
31
+ # Returns the backtrace for the exception. If
32
+ # `Exception.__disable_sanitized_backtrace__` is not true, any stack frames
33
+ # occuring in Polyphony's code will be removed from the backtrace.
34
+ #
35
+ # @return [Array<String>] backtrace
18
36
  def backtrace
19
37
  unless @backtrace_called
20
38
  @backtrace_called = true
@@ -24,6 +42,17 @@ class ::Exception
24
42
  sanitized_backtrace
25
43
  end
26
44
 
45
+ # Raises the exception. this method is a simple wrapper to `Kernel.raise`. It
46
+ # is overriden in the `Polyphony::Interjection` exception class.
47
+ def invoke
48
+ Kernel.raise(self)
49
+ end
50
+
51
+ private
52
+
53
+ # Returns a sanitized backtrace for the exception.
54
+ #
55
+ # @return [Array<String>] sanitized backtrace
27
56
  def sanitized_backtrace
28
57
  return sanitize(orig_backtrace) unless @raising_fiber
29
58
 
@@ -33,13 +62,14 @@ class ::Exception
33
62
 
34
63
  POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
35
64
 
65
+ # Sanitizes the backtrace by removing any frames occuring in Polyphony's code
66
+ # base.
67
+ #
68
+ # @param backtrace [Array<String>] unsanitized backtrace
69
+ # @return [Array<String>] sanitized backtrace
36
70
  def sanitize(backtrace)
37
71
  return backtrace if ::Exception.__disable_sanitized_backtrace__
38
72
 
39
73
  backtrace.reject { |l| l[POLYPHONY_DIR] }
40
74
  end
41
-
42
- def invoke
43
- Kernel.raise(self)
44
- end
45
75
  end