airbrake-ruby 4.6.0-java → 4.7.0-java
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/lib/airbrake-ruby.rb +2 -0
- data/lib/airbrake-ruby/async_sender.rb +21 -83
- data/lib/airbrake-ruby/file_cache.rb +6 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +22 -1
- data/lib/airbrake-ruby/performance_breakdown.rb +2 -1
- data/lib/airbrake-ruby/performance_notifier.rb +42 -12
- data/lib/airbrake-ruby/query.rb +2 -1
- data/lib/airbrake-ruby/request.rb +2 -1
- data/lib/airbrake-ruby/thread_pool.rb +128 -0
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/async_sender_spec.rb +32 -115
- data/spec/{file_cache.rb → file_cache_spec.rb} +2 -4
- data/spec/filters/sql_filter_spec.rb +47 -4
- data/spec/filters/thread_filter_spec.rb +2 -2
- data/spec/{notice_notifier_spec → notice_notifier}/options_spec.rb +0 -0
- data/spec/performance_notifier_spec.rb +56 -0
- data/spec/thread_pool_spec.rb +158 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e49bb6ddb05f50704b2fb2bf52b680e1310b9234
|
4
|
+
data.tar.gz: b2fafa4ebceec499b04d23c9a8094bbfb5f59c66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40adfc247b2a12a0bddbcf87ec654f28125954c64380bd31f662f496219496154ef017e61a369918dfff20055667e23453e6b0c6a7ae2fa049792854ca53091a
|
7
|
+
data.tar.gz: 02d13ebd1cc32aab0b9b60b273d26b37f627b13dac701ad81ecaebfd0a03b984d4bc7b17435264d78a90c32d5c5eb973348294aa98de0f4a3664fef16f855506
|
data/lib/airbrake-ruby.rb
CHANGED
@@ -12,6 +12,7 @@ require 'airbrake-ruby/stashable'
|
|
12
12
|
require 'airbrake-ruby/config'
|
13
13
|
require 'airbrake-ruby/config/validator'
|
14
14
|
require 'airbrake-ruby/promise'
|
15
|
+
require 'airbrake-ruby/thread_pool'
|
15
16
|
require 'airbrake-ruby/sync_sender'
|
16
17
|
require 'airbrake-ruby/async_sender'
|
17
18
|
require 'airbrake-ruby/response'
|
@@ -252,6 +253,7 @@ module Airbrake
|
|
252
253
|
# @return [void]
|
253
254
|
def close
|
254
255
|
notice_notifier.close
|
256
|
+
performance_notifier.close
|
255
257
|
end
|
256
258
|
|
257
259
|
# Pings the Airbrake Deploy API endpoint about the occurred deploy.
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module Airbrake
|
2
|
-
# Responsible for sending notices to Airbrake asynchronously.
|
3
|
-
# supports an unlimited number of worker threads and an unlimited queue size
|
4
|
-
# (both values are configurable).
|
2
|
+
# Responsible for sending notices to Airbrake asynchronously.
|
5
3
|
#
|
6
4
|
# @see SyncSender
|
7
5
|
# @api private
|
@@ -15,25 +13,9 @@ module Airbrake
|
|
15
13
|
"and the following notice will not be delivered " \
|
16
14
|
"Error: %<type>s - %<message>s\nBacktrace: %<backtrace>s\n".freeze
|
17
15
|
|
18
|
-
|
19
|
-
# @note This is exposed for eaiser unit testing
|
20
|
-
# @since v4.0.0
|
21
|
-
attr_reader :workers
|
22
|
-
|
23
|
-
# @return [Array<[Airbrake::Notice,Airbrake::Promise]>] the list of unsent
|
24
|
-
# payload
|
25
|
-
# @note This is exposed for eaiser unit testing
|
26
|
-
# @since v4.0.0
|
27
|
-
attr_reader :unsent
|
28
|
-
|
29
|
-
def initialize
|
16
|
+
def initialize(method = :post)
|
30
17
|
@config = Airbrake::Config.instance
|
31
|
-
@
|
32
|
-
@sender = SyncSender.new
|
33
|
-
@closed = false
|
34
|
-
@workers = ThreadGroup.new
|
35
|
-
@mutex = Mutex.new
|
36
|
-
@pid = nil
|
18
|
+
@method = method
|
37
19
|
end
|
38
20
|
|
39
21
|
# Asynchronously sends a notice to Airbrake.
|
@@ -41,83 +23,39 @@ module Airbrake
|
|
41
23
|
# @param [Airbrake::Notice] notice A notice that was generated by the
|
42
24
|
# library
|
43
25
|
# @return [Airbrake::Promise]
|
44
|
-
def send(notice, promise)
|
45
|
-
|
26
|
+
def send(notice, promise, endpoint = @config.endpoint)
|
27
|
+
unless thread_pool << [notice, promise, endpoint]
|
28
|
+
return will_not_deliver(notice, promise)
|
29
|
+
end
|
46
30
|
|
47
|
-
@unsent << [notice, promise]
|
48
31
|
promise
|
49
32
|
end
|
50
33
|
|
51
|
-
# Closes the instance making it a no-op (it shut downs all worker
|
52
|
-
# threads). Before closing, waits on all unsent notices to be sent.
|
53
|
-
#
|
54
34
|
# @return [void]
|
55
|
-
# @raise [Airbrake::Error] when invoked more than one time
|
56
35
|
def close
|
57
|
-
|
58
|
-
raise Airbrake::Error, 'attempted to close already closed sender' if closed?
|
59
|
-
|
60
|
-
unless @unsent.empty?
|
61
|
-
msg = "#{LOG_LABEL} waiting to send #{@unsent.size} unsent notice(s)..."
|
62
|
-
logger.debug(msg + ' (Ctrl-C to abort)')
|
63
|
-
end
|
64
|
-
|
65
|
-
@config.workers.times { @unsent << [:stop, Airbrake::Promise.new] }
|
66
|
-
@closed = true
|
67
|
-
@workers.list.dup
|
68
|
-
end
|
69
|
-
|
70
|
-
threads.each(&:join)
|
71
|
-
logger.debug("#{LOG_LABEL} closed")
|
36
|
+
thread_pool.close
|
72
37
|
end
|
73
38
|
|
74
|
-
# Checks whether the sender is closed and thus usable.
|
75
39
|
# @return [Boolean]
|
76
40
|
def closed?
|
77
|
-
|
41
|
+
thread_pool.closed?
|
78
42
|
end
|
79
43
|
|
80
|
-
#
|
81
|
-
# workers only in two cases: when it was closed or when all workers
|
82
|
-
# crashed. An *active* sender doesn't have any workers only when something
|
83
|
-
# went wrong.
|
84
|
-
#
|
85
|
-
# Workers are expected to crash when you +fork+ the process the workers are
|
86
|
-
# living in. In this case we detect a +fork+ and try to revive them here.
|
87
|
-
#
|
88
|
-
# Another possible scenario that crashes workers is when you close the
|
89
|
-
# instance on +at_exit+, but some other +at_exit+ hook prevents the process
|
90
|
-
# from exiting.
|
91
|
-
#
|
92
|
-
# @return [Boolean] true if an instance wasn't closed, but has no workers
|
93
|
-
# @see https://goo.gl/oydz8h Example of at_exit that prevents exit
|
44
|
+
# @return [Boolean]
|
94
45
|
def has_workers?
|
95
|
-
|
96
|
-
return false if @closed
|
97
|
-
|
98
|
-
if @pid != Process.pid && @workers.list.empty?
|
99
|
-
@pid = Process.pid
|
100
|
-
spawn_workers
|
101
|
-
end
|
102
|
-
|
103
|
-
!@closed && @workers.list.any?
|
104
|
-
end
|
46
|
+
thread_pool.has_workers?
|
105
47
|
end
|
106
48
|
|
107
49
|
private
|
108
50
|
|
109
|
-
def
|
110
|
-
@
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
while (message = @unsent.pop)
|
118
|
-
break if message.first == :stop
|
119
|
-
@sender.send(*message)
|
120
|
-
end
|
51
|
+
def thread_pool
|
52
|
+
@thread_pool ||= begin
|
53
|
+
sender = SyncSender.new(@method)
|
54
|
+
ThreadPool.new(
|
55
|
+
worker_size: @config.workers,
|
56
|
+
queue_size: @config.queue_size,
|
57
|
+
block: proc { |args| sender.send(*args) }
|
58
|
+
)
|
121
59
|
end
|
122
60
|
end
|
123
61
|
|
@@ -128,7 +66,7 @@ module Airbrake
|
|
128
66
|
format(
|
129
67
|
WILL_NOT_DELIVER_MSG,
|
130
68
|
log_label: LOG_LABEL,
|
131
|
-
capacity: @
|
69
|
+
capacity: @config.queue_size,
|
132
70
|
type: error[:type],
|
133
71
|
message: error[:message],
|
134
72
|
backtrace: error[:backtrace].map do |line|
|
@@ -136,7 +74,7 @@ module Airbrake
|
|
136
74
|
end.join("\n")
|
137
75
|
)
|
138
76
|
)
|
139
|
-
promise.reject("AsyncSender has reached its capacity of #{@
|
77
|
+
promise.reject("AsyncSender has reached its capacity of #{@config.queue_size}")
|
140
78
|
end
|
141
79
|
end
|
142
80
|
end
|
@@ -37,6 +37,10 @@ module Airbrake
|
|
37
37
|
# rubocop:enable Metrics/LineLength
|
38
38
|
}.freeze
|
39
39
|
|
40
|
+
# @return [Regexp] the regexp that is applied after the feature regexps
|
41
|
+
# were used
|
42
|
+
POST_FILTER = /(?<=[values|in ]\().+(?=\))/i
|
43
|
+
|
40
44
|
# @return [Hash{Symbol=>Array<Symbol>}] a set of features that corresponds
|
41
45
|
# to a certain dialect
|
42
46
|
DIALECT_FEATURES = {
|
@@ -75,6 +79,16 @@ module Airbrake
|
|
75
79
|
oracle_enhanced: %r{'|/\*|\*/}
|
76
80
|
}.freeze
|
77
81
|
|
82
|
+
# @return [Array<Regexp>] the list of queries to be ignored
|
83
|
+
IGNORED_QUERIES = [
|
84
|
+
/\ACOMMIT/i,
|
85
|
+
/\ABEGIN/i,
|
86
|
+
/\ASET/i,
|
87
|
+
/\ASHOW/i,
|
88
|
+
/\AWITH/i,
|
89
|
+
/FROM pg_attribute/i
|
90
|
+
].freeze
|
91
|
+
|
78
92
|
def initialize(dialect)
|
79
93
|
@dialect =
|
80
94
|
case dialect
|
@@ -95,7 +109,14 @@ module Airbrake
|
|
95
109
|
def call(resource)
|
96
110
|
return unless resource.respond_to?(:query)
|
97
111
|
|
98
|
-
|
112
|
+
query = resource.query
|
113
|
+
if IGNORED_QUERIES.any? { |q| q =~ query }
|
114
|
+
resource.ignore!
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
q = query.gsub(@regexp, FILTERED)
|
119
|
+
q.gsub!(POST_FILTER, FILTERED) if q =~ POST_FILTER
|
99
120
|
q = ERROR_MSG if UNMATCHED_PAIR[@dialect] =~ q
|
100
121
|
resource.query = q
|
101
122
|
end
|
@@ -21,6 +21,7 @@ module Airbrake
|
|
21
21
|
start_time:,
|
22
22
|
end_time: Time.now
|
23
23
|
)
|
24
|
+
@start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
|
24
25
|
super(method, route, response_type, groups, start_time, end_time)
|
25
26
|
end
|
26
27
|
|
@@ -37,7 +38,7 @@ module Airbrake
|
|
37
38
|
'method' => method,
|
38
39
|
'route' => route,
|
39
40
|
'responseType' => response_type,
|
40
|
-
'time' =>
|
41
|
+
'time' => @start_time_utc
|
41
42
|
}.delete_if { |_key, val| val.nil? }
|
42
43
|
end
|
43
44
|
end
|
@@ -11,11 +11,12 @@ module Airbrake
|
|
11
11
|
def initialize
|
12
12
|
@config = Airbrake::Config.instance
|
13
13
|
@flush_period = Airbrake::Config.instance.performance_stats_flush_period
|
14
|
-
@sender =
|
14
|
+
@sender = AsyncSender.new(:put)
|
15
15
|
@payload = {}
|
16
16
|
@schedule_flush = nil
|
17
17
|
@mutex = Mutex.new
|
18
18
|
@filter_chain = FilterChain.new
|
19
|
+
@waiting = false
|
19
20
|
end
|
20
21
|
|
21
22
|
# @param [Hash] resource
|
@@ -33,10 +34,10 @@ module Airbrake
|
|
33
34
|
|
34
35
|
@mutex.synchronize do
|
35
36
|
update_payload(resource)
|
36
|
-
@flush_period > 0 ? schedule_flush
|
37
|
+
@flush_period > 0 ? schedule_flush : send(@payload, promise)
|
37
38
|
end
|
38
39
|
|
39
|
-
promise
|
40
|
+
promise.resolve(:success)
|
40
41
|
end
|
41
42
|
|
42
43
|
# @see Airbrake.add_performance_filter
|
@@ -49,6 +50,14 @@ module Airbrake
|
|
49
50
|
@filter_chain.delete_filter(filter_class)
|
50
51
|
end
|
51
52
|
|
53
|
+
def close
|
54
|
+
@mutex.synchronize do
|
55
|
+
@schedule_flush.kill if @schedule_flush
|
56
|
+
@sender.close
|
57
|
+
logger.debug("#{LOG_LABEL} performance notifier closed")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
52
61
|
private
|
53
62
|
|
54
63
|
def update_payload(resource)
|
@@ -61,18 +70,39 @@ module Airbrake
|
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
64
|
-
def schedule_flush
|
65
|
-
|
66
|
-
sleep(@flush_period)
|
73
|
+
def schedule_flush
|
74
|
+
return if @payload.empty?
|
67
75
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
76
|
+
if @schedule_flush && @schedule_flush.status == 'sleep' && @waiting
|
77
|
+
begin
|
78
|
+
@schedule_flush.run
|
79
|
+
rescue ThreadError => exception
|
80
|
+
logger.error("#{LOG_LABEL}: error occurred while flushing: #{exception}")
|
73
81
|
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@schedule_flush ||= spawn_timer
|
85
|
+
end
|
86
|
+
|
87
|
+
def spawn_timer
|
88
|
+
Thread.new do
|
89
|
+
loop do
|
90
|
+
if @payload.none?
|
91
|
+
@waiting = true
|
92
|
+
Thread.stop
|
93
|
+
@waiting = false
|
94
|
+
end
|
95
|
+
|
96
|
+
sleep(@flush_period)
|
74
97
|
|
75
|
-
|
98
|
+
payload = nil
|
99
|
+
@mutex.synchronize do
|
100
|
+
payload = @payload
|
101
|
+
@payload = {}
|
102
|
+
end
|
103
|
+
|
104
|
+
send(payload, Airbrake::Promise.new)
|
105
|
+
end
|
76
106
|
end
|
77
107
|
end
|
78
108
|
|
data/lib/airbrake-ruby/query.rb
CHANGED
@@ -22,6 +22,7 @@ module Airbrake
|
|
22
22
|
start_time:,
|
23
23
|
end_time: Time.now
|
24
24
|
)
|
25
|
+
@start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
|
25
26
|
super(method, route, query, func, file, line, start_time, end_time)
|
26
27
|
end
|
27
28
|
|
@@ -42,7 +43,7 @@ module Airbrake
|
|
42
43
|
'method' => method,
|
43
44
|
'route' => route,
|
44
45
|
'query' => query,
|
45
|
-
'time' =>
|
46
|
+
'time' => @start_time_utc,
|
46
47
|
'function' => func,
|
47
48
|
'file' => file,
|
48
49
|
'line' => line
|
@@ -17,6 +17,7 @@ module Airbrake
|
|
17
17
|
start_time:,
|
18
18
|
end_time: Time.now
|
19
19
|
)
|
20
|
+
@start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
|
20
21
|
super(method, route, status_code, start_time, end_time)
|
21
22
|
end
|
22
23
|
|
@@ -37,7 +38,7 @@ module Airbrake
|
|
37
38
|
'method' => method,
|
38
39
|
'route' => route,
|
39
40
|
'statusCode' => status_code,
|
40
|
-
'time' =>
|
41
|
+
'time' => @start_time_utc
|
41
42
|
}.delete_if { |_key, val| val.nil? }
|
42
43
|
end
|
43
44
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# ThreadPool implements a simple thread pool that can configure the number of
|
3
|
+
# worker threads and the size of the queue to process.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# # Initialize a new thread pool with 5 workers and a queue size of 100. Set
|
7
|
+
# # the block to be run concurrently.
|
8
|
+
# thread_pool = ThreadPool.new(
|
9
|
+
# worker_size: 5,
|
10
|
+
# queue_size: 100,
|
11
|
+
# block: proc { |message| print "ECHO: #{message}..."}
|
12
|
+
# )
|
13
|
+
#
|
14
|
+
# # Send work.
|
15
|
+
# 10.times { |i| thread_pool << i }
|
16
|
+
# #=> ECHO: 0...ECHO: 1...ECHO: 2...
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @since v4.6.1
|
20
|
+
class ThreadPool
|
21
|
+
include Loggable
|
22
|
+
|
23
|
+
# @return [ThreadGroup] the list of workers
|
24
|
+
# @note This is exposed for eaiser unit testing
|
25
|
+
attr_reader :workers
|
26
|
+
|
27
|
+
def initialize(worker_size:, queue_size:, block:)
|
28
|
+
@worker_size = worker_size
|
29
|
+
@queue_size = queue_size
|
30
|
+
@block = block
|
31
|
+
|
32
|
+
@queue = SizedQueue.new(queue_size)
|
33
|
+
@workers = ThreadGroup.new
|
34
|
+
@mutex = Mutex.new
|
35
|
+
@pid = nil
|
36
|
+
@closed = false
|
37
|
+
|
38
|
+
has_workers?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds a new message to the thread pool. Rejects messages if the queue is at
|
42
|
+
# its capacity.
|
43
|
+
#
|
44
|
+
# @param [Object] message The message that gets passed to the block
|
45
|
+
# @return [Boolean] true if the message was successfully sent to the pool,
|
46
|
+
# false if the queue is full
|
47
|
+
def <<(message)
|
48
|
+
return false if backlog >= @queue_size
|
49
|
+
@queue << message
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Integer] how big the queue is at the moment
|
54
|
+
def backlog
|
55
|
+
@queue.size
|
56
|
+
end
|
57
|
+
|
58
|
+
# Checks if a thread pool has any workers. A thread pool doesn't have any
|
59
|
+
# workers only in two cases: when it was closed or when all workers
|
60
|
+
# crashed. An *active* thread pool doesn't have any workers only when
|
61
|
+
# something went wrong.
|
62
|
+
#
|
63
|
+
# Workers are expected to crash when you +fork+ the process the workers are
|
64
|
+
# living in. In this case we detect a +fork+ and try to revive them here.
|
65
|
+
#
|
66
|
+
# Another possible scenario that crashes workers is when you close the
|
67
|
+
# instance on +at_exit+, but some other +at_exit+ hook prevents the process
|
68
|
+
# from exiting.
|
69
|
+
#
|
70
|
+
# @return [Boolean] true if an instance wasn't closed, but has no workers
|
71
|
+
# @see https://goo.gl/oydz8h Example of at_exit that prevents exit
|
72
|
+
def has_workers?
|
73
|
+
@mutex.synchronize do
|
74
|
+
return false if @closed
|
75
|
+
|
76
|
+
if @pid != Process.pid && @workers.list.empty?
|
77
|
+
@pid = Process.pid
|
78
|
+
spawn_workers
|
79
|
+
end
|
80
|
+
|
81
|
+
!@closed && @workers.list.any?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Closes the thread pool making it a no-op (it shut downs all worker
|
86
|
+
# threads). Before closing, waits on all unprocessed tasks to be processed.
|
87
|
+
#
|
88
|
+
# @return [void]
|
89
|
+
# @raise [Airbrake::Error] when invoked more than one time
|
90
|
+
def close
|
91
|
+
threads = @mutex.synchronize do
|
92
|
+
raise Airbrake::Error, 'this thread pool is closed already' if @closed
|
93
|
+
|
94
|
+
unless @queue.empty?
|
95
|
+
msg = "#{LOG_LABEL} waiting to process #{@queue.size} task(s)..."
|
96
|
+
logger.debug(msg + ' (Ctrl-C to abort)')
|
97
|
+
end
|
98
|
+
|
99
|
+
@worker_size.times { @queue << :stop }
|
100
|
+
@closed = true
|
101
|
+
@workers.list.dup
|
102
|
+
end
|
103
|
+
|
104
|
+
threads.each(&:join)
|
105
|
+
logger.debug("#{LOG_LABEL} thread pool closed")
|
106
|
+
end
|
107
|
+
|
108
|
+
def closed?
|
109
|
+
@closed
|
110
|
+
end
|
111
|
+
|
112
|
+
def spawn_workers
|
113
|
+
@worker_size.times { @workers.add(spawn_worker) }
|
114
|
+
@workers.enclose
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def spawn_worker
|
120
|
+
Thread.new do
|
121
|
+
while (message = @queue.pop)
|
122
|
+
break if message == :stop
|
123
|
+
@block.call(message)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/spec/async_sender_spec.rb
CHANGED
@@ -8,148 +8,65 @@ RSpec.describe Airbrake::AsyncSender do
|
|
8
8
|
Airbrake::Config.instance = Airbrake::Config.new(
|
9
9
|
project_id: '1',
|
10
10
|
workers: 3,
|
11
|
-
queue_size:
|
11
|
+
queue_size: 10
|
12
12
|
)
|
13
|
-
|
14
|
-
allow(Airbrake::Loggable.instance).to receive(:debug)
|
15
|
-
expect(subject).to have_workers
|
16
13
|
end
|
17
14
|
|
18
15
|
describe "#send" do
|
19
|
-
|
20
|
-
|
21
|
-
subject.send(notice, Airbrake::Promise.new)
|
22
|
-
end
|
23
|
-
subject.close
|
24
|
-
|
25
|
-
expect(a_request(:post, endpoint)).to have_been_made.twice
|
26
|
-
end
|
27
|
-
|
28
|
-
context "when the queue is full" do
|
29
|
-
before do
|
30
|
-
allow(subject.unsent).to receive(:size).and_return(queue_size)
|
31
|
-
end
|
32
|
-
|
33
|
-
it "discards payload" do
|
34
|
-
200.times do
|
35
|
-
subject.send(notice, Airbrake::Promise.new)
|
36
|
-
end
|
16
|
+
context "when sender has the capacity to send" do
|
17
|
+
it "sends notices to Airbrake" do
|
18
|
+
2.times { subject.send(notice, Airbrake::Promise.new) }
|
37
19
|
subject.close
|
38
20
|
|
39
|
-
expect(a_request(:post, endpoint)).
|
21
|
+
expect(a_request(:post, endpoint)).to have_been_made.twice
|
40
22
|
end
|
41
23
|
|
42
|
-
it "
|
43
|
-
expect(Airbrake::Loggable.instance).to receive(:error).with(
|
44
|
-
/reached its capacity/
|
45
|
-
).exactly(15).times
|
46
|
-
|
47
|
-
15.times do
|
48
|
-
subject.send(notice, Airbrake::Promise.new)
|
49
|
-
end
|
50
|
-
subject.close
|
51
|
-
end
|
52
|
-
|
53
|
-
it "returns a rejected promise" do
|
24
|
+
it "returns a resolved promise" do
|
54
25
|
promise = Airbrake::Promise.new
|
55
|
-
|
56
|
-
expect(promise.value).to eq(
|
57
|
-
'error' => "AsyncSender has reached its capacity of #{queue_size}"
|
58
|
-
)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe "#close" do
|
64
|
-
context "when there are no unsent notices" do
|
65
|
-
it "joins the spawned thread" do
|
66
|
-
workers = subject.workers.list
|
67
|
-
expect(workers).to all(be_alive)
|
68
|
-
|
26
|
+
subject.send(notice, promise)
|
69
27
|
subject.close
|
70
|
-
|
28
|
+
|
29
|
+
expect(promise).to be_resolved
|
71
30
|
end
|
72
31
|
end
|
73
32
|
|
74
|
-
context "when
|
75
|
-
|
76
|
-
|
77
|
-
|
33
|
+
context "when sender has exceeded the capacity to send" do
|
34
|
+
before do
|
35
|
+
Airbrake::Config.instance = Airbrake::Config.new(
|
36
|
+
project_id: '1',
|
37
|
+
workers: 0,
|
38
|
+
queue_size: 1
|
78
39
|
)
|
79
|
-
expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
|
80
|
-
|
81
|
-
300.times { subject.send(notice, Airbrake::Promise.new) }
|
82
|
-
subject.close
|
83
40
|
end
|
84
41
|
|
85
|
-
it "
|
42
|
+
it "doesn't send the exceeded notices to Airbrake" do
|
43
|
+
15.times { subject.send(notice, Airbrake::Promise.new) }
|
86
44
|
subject.close
|
87
|
-
expect(subject.unsent.size).to be_zero
|
88
|
-
end
|
89
|
-
end
|
90
45
|
|
91
|
-
|
92
|
-
it "doesn't increase the unsent queue size" do
|
93
|
-
begin
|
94
|
-
subject.close
|
95
|
-
rescue Airbrake::Error
|
96
|
-
nil
|
97
|
-
end
|
98
|
-
|
99
|
-
expect(subject.unsent.size).to be_zero
|
46
|
+
expect(a_request(:post, endpoint)).not_to have_been_made
|
100
47
|
end
|
101
48
|
|
102
|
-
it "
|
49
|
+
it "returns a rejected promise" do
|
50
|
+
promise = nil
|
51
|
+
15.times do
|
52
|
+
promise = subject.send(notice, Airbrake::Promise.new)
|
53
|
+
end
|
103
54
|
subject.close
|
104
55
|
|
105
|
-
expect(
|
106
|
-
expect
|
107
|
-
|
56
|
+
expect(promise).to be_rejected
|
57
|
+
expect(promise.value).to eq(
|
58
|
+
'error' => "AsyncSender has reached its capacity of 1"
|
108
59
|
)
|
109
60
|
end
|
110
|
-
end
|
111
61
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
62
|
+
it "logs discarded notice" do
|
63
|
+
expect(Airbrake::Loggable.instance).to receive(:error).with(
|
64
|
+
/reached its capacity/
|
65
|
+
).at_least(:once)
|
119
66
|
|
120
|
-
|
121
|
-
|
122
|
-
subject.workers.list.each do |worker|
|
123
|
-
worker.kill.join
|
67
|
+
15.times { subject.send(notice, Airbrake::Promise.new) }
|
68
|
+
subject.close
|
124
69
|
end
|
125
|
-
expect(subject).not_to have_workers
|
126
|
-
end
|
127
|
-
|
128
|
-
it "returns false when the sender is closed" do
|
129
|
-
subject.close
|
130
|
-
expect(subject).not_to have_workers
|
131
|
-
end
|
132
|
-
|
133
|
-
it "respawns workers on fork()", skip: %w[jruby rbx].include?(RUBY_ENGINE) do
|
134
|
-
pid = fork { expect(subject).to have_workers }
|
135
|
-
Process.wait(pid)
|
136
|
-
subject.close
|
137
|
-
expect(subject).not_to have_workers
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
describe "#spawn_workers" do
|
142
|
-
it "spawns alive threads in an enclosed ThreadGroup" do
|
143
|
-
expect(subject.workers).to be_a(ThreadGroup)
|
144
|
-
expect(subject.workers.list).to all(be_alive)
|
145
|
-
expect(subject.workers).to be_enclosed
|
146
|
-
|
147
|
-
subject.close
|
148
|
-
end
|
149
|
-
|
150
|
-
it "spawns exactly config.workers workers" do
|
151
|
-
expect(subject.workers.list.size).to eq(Airbrake::Config.instance.workers)
|
152
|
-
subject.close
|
153
70
|
end
|
154
71
|
end
|
155
72
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
RSpec.describe Airbrake::FileCache do
|
2
|
-
|
3
|
-
|
4
|
-
expect(described_class).to be_empty
|
5
|
-
end
|
2
|
+
before { described_class.reset }
|
3
|
+
after { described_class.reset }
|
6
4
|
|
7
5
|
describe ".[]=" do
|
8
6
|
context "when cache limit isn't reached" do
|
@@ -10,6 +10,18 @@ RSpec.describe Airbrake::Filters::SqlFilter do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
shared_examples "query blacklisting" do |query, opts|
|
14
|
+
it "ignores '#{query}'" do
|
15
|
+
filter = described_class.new('postgres')
|
16
|
+
q = Airbrake::Query.new(
|
17
|
+
query: query, method: 'GET', route: '/', start_time: Time.now
|
18
|
+
)
|
19
|
+
filter.call(q)
|
20
|
+
|
21
|
+
expect(q.ignored?).to eq(opts[:should_ignore])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
13
25
|
ALL_DIALECTS = %i[mysql postgres sqlite cassandra oracle].freeze
|
14
26
|
|
15
27
|
# rubocop:disable Metrics/LineLength
|
@@ -60,7 +72,11 @@ RSpec.describe Airbrake::Filters::SqlFilter do
|
|
60
72
|
dialects: %i[postgres]
|
61
73
|
}, {
|
62
74
|
input: "INSERT INTO `X` values(\"test\",0, 1 , 2, 'test')",
|
63
|
-
output: "INSERT INTO `X` values(
|
75
|
+
output: "INSERT INTO `X` values(?)",
|
76
|
+
dialects: %i[mysql]
|
77
|
+
}, {
|
78
|
+
input: "INSERT INTO `X` values(\"test\",0, 1 , 2, 'test')",
|
79
|
+
output: "INSERT INTO `X` values(?)",
|
64
80
|
dialects: %i[mysql]
|
65
81
|
}, {
|
66
82
|
input: "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value='nothing'",
|
@@ -68,7 +84,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
|
|
68
84
|
dialects: ALL_DIALECTS
|
69
85
|
}, {
|
70
86
|
input: "INSERT INTO X VALUES(1, 23456, 123.456, 99+100)",
|
71
|
-
output: "INSERT INTO X VALUES(
|
87
|
+
output: "INSERT INTO X VALUES(?)",
|
72
88
|
dialects: ALL_DIALECTS
|
73
89
|
}, {
|
74
90
|
input: "SELECT * FROM table WHERE name=\"foo\" AND value=\"don't\"",
|
@@ -117,7 +133,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
|
|
117
133
|
dialects: ALL_DIALECTS
|
118
134
|
}, {
|
119
135
|
input: "INSERT INTO X values('', 'a''b c',0, 1 , 'd''e f''s h')",
|
120
|
-
output: "INSERT INTO X values(
|
136
|
+
output: "INSERT INTO X values(?)",
|
121
137
|
dialects: ALL_DIALECTS
|
122
138
|
}, {
|
123
139
|
input: "SELECT * FROM t WHERE -- '\n bar='baz' -- '",
|
@@ -153,7 +169,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
|
|
153
169
|
dialects: %i[postgres]
|
154
170
|
}, {
|
155
171
|
input: "INSERT INTO \"foo\" (\"bar\", \"baz\", \"qux\") VALUES ($1, $2, $3) RETURNING \"id\"",
|
156
|
-
output: "INSERT INTO \"foo\" (
|
172
|
+
output: "INSERT INTO \"foo\" (?) RETURNING \"id\"",
|
157
173
|
dialects: %i[postgres]
|
158
174
|
}, {
|
159
175
|
input: "select * from foo where bar = 'some\\tthing' and baz = 10",
|
@@ -216,4 +232,31 @@ RSpec.describe Airbrake::Filters::SqlFilter do
|
|
216
232
|
include_examples 'query filtering', test
|
217
233
|
end
|
218
234
|
# rubocop:enable Metrics/LineLength
|
235
|
+
|
236
|
+
[
|
237
|
+
'COMMIT',
|
238
|
+
'commit',
|
239
|
+
'BEGIN',
|
240
|
+
'begin',
|
241
|
+
'SET time zone ?',
|
242
|
+
'set time zone ?',
|
243
|
+
'SHOW max_identifier_length',
|
244
|
+
'show max_identifier_length',
|
245
|
+
|
246
|
+
'WITH pk_constraint AS ( SELECT conrelid, unnest(conkey) AS connum ' \
|
247
|
+
'FROM pg_constraint WHERE contype = ? AND conrelid = ?::regclass ), ' \
|
248
|
+
'cons AS ( SELECT conrelid, connum, row_number() OVER() AS rownum FROM ' \
|
249
|
+
'pk_constraint ) SELECT attr.attname FROM pg_attribute attr INNER JOIN ' \
|
250
|
+
'cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum ' \
|
251
|
+
'ORDER BY cons.rownum'
|
252
|
+
|
253
|
+
].each do |query|
|
254
|
+
include_examples 'query blacklisting', query, should_ignore: true
|
255
|
+
end
|
256
|
+
|
257
|
+
[
|
258
|
+
'UPDATE "users" SET "last_sign_in_at" = ? WHERE "users"."id" = ?'
|
259
|
+
].each do |query|
|
260
|
+
include_examples 'query blacklisting', query, should_ignore: false
|
261
|
+
end
|
219
262
|
end
|
@@ -245,12 +245,12 @@ RSpec.describe Airbrake::Filters::ThreadFilter do
|
|
245
245
|
|
246
246
|
it "appends thread inspect (self)" do
|
247
247
|
subject.call(notice)
|
248
|
-
expect(notice[:params][:thread][:self]).to match(/\A#<Thread
|
248
|
+
expect(notice[:params][:thread][:self]).to match(/\A#<Thread:.+>\z/)
|
249
249
|
end
|
250
250
|
|
251
251
|
it "appends thread group" do
|
252
252
|
subject.call(notice)
|
253
|
-
expect(notice[:params][:thread][:group][0]).to match(/\A#<Thread
|
253
|
+
expect(notice[:params][:thread][:group][0]).to match(/\A#<Thread:.+>\z/)
|
254
254
|
end
|
255
255
|
|
256
256
|
it "appends priority" do
|
File without changes
|
@@ -31,6 +31,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
31
31
|
end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
|
32
32
|
)
|
33
33
|
)
|
34
|
+
subject.close
|
34
35
|
|
35
36
|
expect(
|
36
37
|
a_request(:put, queries).with(body: %r|
|
@@ -60,6 +61,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
60
61
|
end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
|
61
62
|
)
|
62
63
|
)
|
64
|
+
subject.close
|
63
65
|
|
64
66
|
expect(
|
65
67
|
a_request(:put, routes).with(body: %r|
|
@@ -87,6 +89,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
87
89
|
groups: { db: 131, view: 421 }
|
88
90
|
)
|
89
91
|
)
|
92
|
+
subject.close
|
90
93
|
|
91
94
|
expect(
|
92
95
|
a_request(:put, breakdowns).with(body: %r|
|
@@ -126,6 +129,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
126
129
|
start_time: Time.new(2018, 1, 1, 0, 0, 20, 0)
|
127
130
|
)
|
128
131
|
)
|
132
|
+
subject.close
|
133
|
+
|
129
134
|
expect(
|
130
135
|
a_request(:put, routes).with(body: /"time":"2018-01-01T00:00:00\+00:00"/)
|
131
136
|
).to have_been_made
|
@@ -148,6 +153,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
148
153
|
start_time: Time.new(2018, 1, 1, 0, 0, 50, 0)
|
149
154
|
)
|
150
155
|
)
|
156
|
+
subject.close
|
157
|
+
|
151
158
|
expect(
|
152
159
|
a_request(:put, routes).with(body: /"count":2/)
|
153
160
|
).to have_been_made
|
@@ -172,6 +179,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
172
179
|
end_time: Time.new(2018, 1, 1, 0, 1, 55, 0)
|
173
180
|
)
|
174
181
|
)
|
182
|
+
subject.close
|
183
|
+
|
175
184
|
expect(
|
176
185
|
a_request(:put, routes).with(
|
177
186
|
body: %r|\A
|
@@ -206,6 +215,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
206
215
|
end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
|
207
216
|
)
|
208
217
|
)
|
218
|
+
subject.close
|
219
|
+
|
209
220
|
expect(
|
210
221
|
a_request(:put, routes).with(
|
211
222
|
body: %r|\A
|
@@ -242,6 +253,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
242
253
|
groups: { db: 55, view: 11 }
|
243
254
|
)
|
244
255
|
)
|
256
|
+
subject.close
|
245
257
|
|
246
258
|
expect(
|
247
259
|
a_request(:put, breakdowns).with(body: %r|
|
@@ -281,6 +293,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
281
293
|
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
|
282
294
|
)
|
283
295
|
)
|
296
|
+
subject.close
|
297
|
+
|
284
298
|
expect(promise).to be_an(Airbrake::Promise)
|
285
299
|
expect(promise.value).to eq('' => nil)
|
286
300
|
end
|
@@ -292,6 +306,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
292
306
|
expect(Airbrake::Config.instance).to receive(:check_performance_options)
|
293
307
|
.with(request).and_return(Airbrake::Promise.new)
|
294
308
|
subject.notify(request)
|
309
|
+
subject.close
|
295
310
|
end
|
296
311
|
|
297
312
|
it "sends environment when it's specified" do
|
@@ -305,6 +320,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
305
320
|
start_time: Time.new
|
306
321
|
)
|
307
322
|
)
|
323
|
+
subject.close
|
324
|
+
|
308
325
|
expect(
|
309
326
|
a_request(:put, routes).with(
|
310
327
|
body: /\A{"routes":\[.+\],"environment":"test"}\z/x
|
@@ -369,6 +386,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
369
386
|
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
|
370
387
|
)
|
371
388
|
)
|
389
|
+
subject.close
|
390
|
+
|
372
391
|
expect(a_request(:put, routes)).not_to have_been_made
|
373
392
|
end
|
374
393
|
|
@@ -381,6 +400,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
381
400
|
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
|
382
401
|
)
|
383
402
|
)
|
403
|
+
subject.close
|
404
|
+
|
384
405
|
expect(a_request(:put, queries)).not_to have_been_made
|
385
406
|
end
|
386
407
|
end
|
@@ -401,6 +422,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
401
422
|
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
|
402
423
|
)
|
403
424
|
)
|
425
|
+
subject.close
|
426
|
+
|
404
427
|
expect(
|
405
428
|
a_request(:put, queries).with(
|
406
429
|
body: /\A{"queries":\[{"method":"POST","route":"\[Filtered\]"/
|
@@ -410,6 +433,37 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
410
433
|
end
|
411
434
|
end
|
412
435
|
|
436
|
+
describe "#close" do
|
437
|
+
before do
|
438
|
+
Airbrake::Config.instance.merge(performance_stats_flush_period: 0.1)
|
439
|
+
end
|
440
|
+
|
441
|
+
after do
|
442
|
+
Airbrake::Config.instance.merge(performance_stats_flush_period: 0)
|
443
|
+
end
|
444
|
+
|
445
|
+
it "kills the background thread" do
|
446
|
+
expect_any_instance_of(Thread).to receive(:kill).and_call_original
|
447
|
+
subject.notify(
|
448
|
+
Airbrake::Query.new(
|
449
|
+
method: 'POST',
|
450
|
+
route: '/foo',
|
451
|
+
query: 'SELECT * FROM things',
|
452
|
+
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
|
453
|
+
)
|
454
|
+
)
|
455
|
+
subject.close
|
456
|
+
end
|
457
|
+
|
458
|
+
it "logs the exit message" do
|
459
|
+
allow(Airbrake::Loggable.instance).to receive(:debug)
|
460
|
+
expect(Airbrake::Loggable.instance).to receive(:debug).with(
|
461
|
+
/performance notifier closed/
|
462
|
+
)
|
463
|
+
subject.close
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
413
467
|
describe "#delete_filter" do
|
414
468
|
let(:filter) do
|
415
469
|
Class.new do
|
@@ -429,6 +483,8 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
429
483
|
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
|
430
484
|
)
|
431
485
|
)
|
486
|
+
subject.close
|
487
|
+
|
432
488
|
expect(a_request(:put, routes)).to have_been_made
|
433
489
|
end
|
434
490
|
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
RSpec.describe Airbrake::ThreadPool do
|
2
|
+
let(:tasks) { [] }
|
3
|
+
let(:worker_size) { 1 }
|
4
|
+
let(:queue_size) { 2 }
|
5
|
+
|
6
|
+
subject do
|
7
|
+
described_class.new(
|
8
|
+
worker_size: worker_size,
|
9
|
+
queue_size: queue_size,
|
10
|
+
block: proc { |message| tasks << message }
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#<<" do
|
15
|
+
it "returns true" do
|
16
|
+
retval = subject << 1
|
17
|
+
subject.close
|
18
|
+
expect(retval).to eq(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "performs work in background" do
|
22
|
+
subject << 2
|
23
|
+
subject << 1
|
24
|
+
subject.close
|
25
|
+
|
26
|
+
expect(tasks).to eq([2, 1])
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when the queue is full" do
|
30
|
+
before do
|
31
|
+
allow(subject).to receive(:backlog).and_return(queue_size)
|
32
|
+
end
|
33
|
+
|
34
|
+
subject do
|
35
|
+
described_class.new(
|
36
|
+
worker_size: 1,
|
37
|
+
queue_size: 1,
|
38
|
+
block: proc { |message| tasks << message }
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns false" do
|
43
|
+
retval = subject << 1
|
44
|
+
subject.close
|
45
|
+
expect(retval).to eq(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "discards tasks" do
|
49
|
+
200.times { subject << 1 }
|
50
|
+
subject.close
|
51
|
+
|
52
|
+
expect(tasks.size).to be_zero
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#backlog" do
|
58
|
+
let(:worker_size) { 0 }
|
59
|
+
|
60
|
+
it "returns the size of the queue" do
|
61
|
+
subject << 1
|
62
|
+
expect(subject.backlog).to eq(1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#has_workers?" do
|
67
|
+
it "returns false when the thread pool is not closed, but has 0 workers" do
|
68
|
+
subject.workers.list.each do |worker|
|
69
|
+
worker.kill.join
|
70
|
+
end
|
71
|
+
expect(subject).not_to have_workers
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns false when the thread pool is closed" do
|
75
|
+
subject.close
|
76
|
+
expect(subject).not_to have_workers
|
77
|
+
end
|
78
|
+
|
79
|
+
it "respawns workers on fork()", skip: %w[jruby].include?(RUBY_ENGINE) do
|
80
|
+
pid = fork { expect(subject).to have_workers }
|
81
|
+
Process.wait(pid)
|
82
|
+
subject.close
|
83
|
+
expect(subject).not_to have_workers
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#close" do
|
88
|
+
context "when there's no work to do" do
|
89
|
+
it "joins the spawned thread" do
|
90
|
+
workers = subject.workers.list
|
91
|
+
expect(workers).to all(be_alive)
|
92
|
+
|
93
|
+
subject.close
|
94
|
+
expect(workers).to all(be_stop)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when there's some work to do" do
|
99
|
+
it "logs how many tasks are left to process" do
|
100
|
+
thread_pool = described_class.new(
|
101
|
+
worker_size: 0, queue_size: 2, block: proc {}
|
102
|
+
)
|
103
|
+
|
104
|
+
expect(Airbrake::Loggable.instance).to receive(:debug).with(
|
105
|
+
/waiting to process \d+ task\(s\)/
|
106
|
+
)
|
107
|
+
expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
|
108
|
+
|
109
|
+
2.times { thread_pool << 1 }
|
110
|
+
thread_pool.close
|
111
|
+
end
|
112
|
+
|
113
|
+
it "waits until the queue gets empty" do
|
114
|
+
thread_pool = described_class.new(
|
115
|
+
worker_size: 1, queue_size: 2, block: proc {}
|
116
|
+
)
|
117
|
+
|
118
|
+
10.times { subject << 1 }
|
119
|
+
thread_pool.close
|
120
|
+
expect(thread_pool.backlog).to be_zero
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "when it was already closed" do
|
125
|
+
it "doesn't increase the queue size" do
|
126
|
+
begin
|
127
|
+
subject.close
|
128
|
+
rescue Airbrake::Error
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
expect(subject.backlog).to be_zero
|
133
|
+
end
|
134
|
+
|
135
|
+
it "raises error" do
|
136
|
+
subject.close
|
137
|
+
expect { subject.close }.to raise_error(
|
138
|
+
Airbrake::Error, 'this thread pool is closed already'
|
139
|
+
)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "#spawn_workers" do
|
145
|
+
it "spawns alive threads in an enclosed ThreadGroup" do
|
146
|
+
expect(subject.workers).to be_a(ThreadGroup)
|
147
|
+
expect(subject.workers.list).to all(be_alive)
|
148
|
+
expect(subject.workers).to be_enclosed
|
149
|
+
|
150
|
+
subject.close
|
151
|
+
end
|
152
|
+
|
153
|
+
it "spawns exactly `workers_size` workers" do
|
154
|
+
expect(subject.workers.list.size).to eq(worker_size)
|
155
|
+
subject.close
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airbrake-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.7.0
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Airbrake Technologies, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rbtree-jruby
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- lib/airbrake-ruby/stat.rb
|
81
81
|
- lib/airbrake-ruby/sync_sender.rb
|
82
82
|
- lib/airbrake-ruby/tdigest.rb
|
83
|
+
- lib/airbrake-ruby/thread_pool.rb
|
83
84
|
- lib/airbrake-ruby/time_truncate.rb
|
84
85
|
- lib/airbrake-ruby/timed_trace.rb
|
85
86
|
- lib/airbrake-ruby/truncator.rb
|
@@ -92,7 +93,7 @@ files:
|
|
92
93
|
- spec/config/validator_spec.rb
|
93
94
|
- spec/config_spec.rb
|
94
95
|
- spec/deploy_notifier_spec.rb
|
95
|
-
- spec/
|
96
|
+
- spec/file_cache_spec.rb
|
96
97
|
- spec/filter_chain_spec.rb
|
97
98
|
- spec/filters/context_filter_spec.rb
|
98
99
|
- spec/filters/dependency_filter_spec.rb
|
@@ -118,8 +119,8 @@ files:
|
|
118
119
|
- spec/inspectable_spec.rb
|
119
120
|
- spec/monotonic_time_spec.rb
|
120
121
|
- spec/nested_exception_spec.rb
|
122
|
+
- spec/notice_notifier/options_spec.rb
|
121
123
|
- spec/notice_notifier_spec.rb
|
122
|
-
- spec/notice_notifier_spec/options_spec.rb
|
123
124
|
- spec/notice_spec.rb
|
124
125
|
- spec/performance_breakdown_spec.rb
|
125
126
|
- spec/performance_notifier_spec.rb
|
@@ -132,6 +133,7 @@ files:
|
|
132
133
|
- spec/stat_spec.rb
|
133
134
|
- spec/sync_sender_spec.rb
|
134
135
|
- spec/tdigest_spec.rb
|
136
|
+
- spec/thread_pool_spec.rb
|
135
137
|
- spec/time_truncate_spec.rb
|
136
138
|
- spec/timed_trace_spec.rb
|
137
139
|
- spec/truncator_spec.rb
|
@@ -187,6 +189,7 @@ test_files:
|
|
187
189
|
- spec/notice_notifier_spec.rb
|
188
190
|
- spec/time_truncate_spec.rb
|
189
191
|
- spec/promise_spec.rb
|
192
|
+
- spec/thread_pool_spec.rb
|
190
193
|
- spec/config/validator_spec.rb
|
191
194
|
- spec/sync_sender_spec.rb
|
192
195
|
- spec/ignorable_spec.rb
|
@@ -195,10 +198,11 @@ test_files:
|
|
195
198
|
- spec/airbrake_spec.rb
|
196
199
|
- spec/nested_exception_spec.rb
|
197
200
|
- spec/timed_trace_spec.rb
|
201
|
+
- spec/file_cache_spec.rb
|
198
202
|
- spec/request_spec.rb
|
203
|
+
- spec/notice_notifier/options_spec.rb
|
199
204
|
- spec/filter_chain_spec.rb
|
200
205
|
- spec/response_spec.rb
|
201
|
-
- spec/file_cache.rb
|
202
206
|
- spec/code_hunk_spec.rb
|
203
207
|
- spec/fixtures/notroot.txt
|
204
208
|
- spec/fixtures/project_root/long_line.txt
|
@@ -210,4 +214,3 @@ test_files:
|
|
210
214
|
- spec/inspectable_spec.rb
|
211
215
|
- spec/stashable_spec.rb
|
212
216
|
- spec/query_spec.rb
|
213
|
-
- spec/notice_notifier_spec/options_spec.rb
|