honeybadger 2.7.2 → 3.0.0.beta1

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -3
  3. data/README.md +8 -15
  4. data/lib/honeybadger.rb +9 -232
  5. data/lib/honeybadger/agent.rb +292 -134
  6. data/lib/honeybadger/backend.rb +6 -6
  7. data/lib/honeybadger/backend/base.rb +11 -0
  8. data/lib/honeybadger/backend/server.rb +2 -14
  9. data/lib/honeybadger/cli.rb +0 -2
  10. data/lib/honeybadger/cli/deploy.rb +42 -0
  11. data/lib/honeybadger/cli/exec.rb +138 -0
  12. data/lib/honeybadger/cli/heroku.rb +1 -22
  13. data/lib/honeybadger/cli/install.rb +74 -0
  14. data/lib/honeybadger/cli/main.rb +138 -153
  15. data/lib/honeybadger/cli/notify.rb +66 -0
  16. data/lib/honeybadger/cli/test.rb +266 -0
  17. data/lib/honeybadger/config.rb +178 -162
  18. data/lib/honeybadger/config/defaults.rb +5 -5
  19. data/lib/honeybadger/config/env.rb +8 -6
  20. data/lib/honeybadger/config/ruby.rb +100 -0
  21. data/lib/honeybadger/config/yaml.rb +18 -19
  22. data/lib/honeybadger/const.rb +3 -16
  23. data/lib/honeybadger/context_manager.rb +50 -0
  24. data/lib/honeybadger/init/rails.rb +9 -21
  25. data/lib/honeybadger/init/rake.rb +2 -0
  26. data/lib/honeybadger/init/ruby.rb +9 -0
  27. data/lib/honeybadger/init/sinatra.rb +13 -6
  28. data/lib/honeybadger/notice.rb +29 -14
  29. data/lib/honeybadger/plugins/delayed_job/plugin.rb +4 -5
  30. data/lib/honeybadger/plugins/passenger.rb +1 -2
  31. data/lib/honeybadger/plugins/rails.rb +0 -28
  32. data/lib/honeybadger/plugins/resque.rb +2 -5
  33. data/lib/honeybadger/plugins/shoryuken.rb +2 -2
  34. data/lib/honeybadger/plugins/sidekiq.rb +2 -2
  35. data/lib/honeybadger/plugins/sucker_punch.rb +1 -0
  36. data/lib/honeybadger/plugins/thor.rb +2 -2
  37. data/lib/honeybadger/plugins/warden.rb +1 -0
  38. data/lib/honeybadger/rack/error_notifier.rb +11 -9
  39. data/lib/honeybadger/rack/user_feedback.rb +6 -4
  40. data/lib/honeybadger/rack/user_informer.rb +6 -4
  41. data/lib/honeybadger/ruby.rb +2 -0
  42. data/lib/honeybadger/singleton.rb +26 -0
  43. data/lib/honeybadger/util/http.rb +12 -0
  44. data/lib/honeybadger/util/request_hash.rb +71 -0
  45. data/lib/honeybadger/util/sanitizer.rb +101 -64
  46. data/lib/honeybadger/version.rb +1 -1
  47. data/lib/honeybadger/worker.rb +246 -0
  48. metadata +17 -13
  49. data/lib/honeybadger/agent/batch.rb +0 -50
  50. data/lib/honeybadger/agent/null_worker.rb +0 -26
  51. data/lib/honeybadger/agent/worker.rb +0 -243
  52. data/lib/honeybadger/cli/helpers.rb +0 -160
  53. data/lib/honeybadger/config/callbacks.rb +0 -70
  54. data/lib/honeybadger/plugins/unicorn.rb +0 -27
  55. data/lib/honeybadger/rack/metrics_reporter.rb +0 -16
  56. data/lib/honeybadger/rack/request_hash.rb +0 -55
@@ -1,4 +1,4 @@
1
1
  module Honeybadger
2
2
  # Public: The current String Honeybadger version.
3
- VERSION = '2.7.2'.freeze
3
+ VERSION = '3.0.0.beta1'.freeze
4
4
  end
@@ -0,0 +1,246 @@
1
+ require 'forwardable'
2
+ require 'net/http'
3
+
4
+ require 'honeybadger/logging'
5
+
6
+ module Honeybadger
7
+ # Internal: A concurrent queue to notify the backend.
8
+ class Worker
9
+ extend Forwardable
10
+
11
+ include Honeybadger::Logging::Helper
12
+
13
+ # Internal: Sub-class thread so we have a named thread (useful for debugging in Thread.list).
14
+ class Thread < ::Thread; end
15
+
16
+ # Internal: A queue which enforces a maximum size.
17
+ class Queue < ::Queue
18
+ attr_reader :max_size
19
+
20
+ def initialize(max_size)
21
+ @max_size = max_size
22
+ super()
23
+ end
24
+
25
+ def push(msg)
26
+ super unless size == max_size
27
+ end
28
+ end
29
+
30
+ SHUTDOWN = :__hb_worker_shutdown!
31
+
32
+ def initialize(config)
33
+ @config = config
34
+ @throttles = []
35
+ @mutex = Mutex.new
36
+ @marker = ConditionVariable.new
37
+ @queue = Queue.new(config.max_queue_size)
38
+ @shutdown = false
39
+ @start_at = nil
40
+ end
41
+
42
+ def push(msg)
43
+ return false unless start
44
+ queue.push(msg)
45
+ end
46
+
47
+ def send_now(msg)
48
+ handle_response(msg, notify_backend(msg))
49
+ end
50
+
51
+ # Internal: Shutdown the worker after sending remaining data.
52
+ #
53
+ # Returns true.
54
+ def shutdown
55
+ d { 'shutting down worker' }
56
+
57
+ mutex.synchronize do
58
+ @shutdown = true
59
+ @pid = nil
60
+ queue.push(SHUTDOWN)
61
+ end
62
+
63
+ return true unless thread
64
+
65
+ r = true
66
+ unless Thread.current.eql?(thread)
67
+ begin
68
+ r = !!thread.join
69
+ ensure
70
+ shutdown! unless r
71
+ end
72
+ end
73
+
74
+ r
75
+ end
76
+
77
+ def shutdown!
78
+ mutex.synchronize do
79
+ @shutdown = true
80
+ @pid = nil
81
+ queue.clear
82
+ end
83
+
84
+ d { 'killing worker thread' }
85
+
86
+ if thread
87
+ Thread.kill(thread)
88
+ thread.join # Allow ensure blocks to execute.
89
+ end
90
+
91
+ true
92
+ end
93
+
94
+ # Internal: Blocks until queue is processed up to this point in time.
95
+ #
96
+ # Returns nothing.
97
+ def flush
98
+ mutex.synchronize do
99
+ if thread && thread.alive?
100
+ queue.push(marker)
101
+ marker.wait(mutex)
102
+ end
103
+ end
104
+ end
105
+
106
+ def start
107
+ return false unless can_start?
108
+
109
+ mutex.synchronize do
110
+ @shutdown = false
111
+ @start_at = nil
112
+
113
+ return true if thread && thread.alive?
114
+
115
+ @pid = Process.pid
116
+ @thread = Thread.new { run }
117
+ end
118
+
119
+ true
120
+ end
121
+
122
+ private
123
+
124
+ attr_reader :config, :queue, :pid, :mutex, :marker,
125
+ :thread, :throttles
126
+
127
+ def_delegator :config, :backend
128
+
129
+ def can_start?
130
+ mutex.synchronize do
131
+ return true unless @shutdown
132
+ return false unless @start_at
133
+ Time.now.to_i >= @start_at
134
+ end
135
+ end
136
+
137
+ def suspend(interval)
138
+ mutex.synchronize { @start_at = Time.now.to_i + interval }
139
+
140
+ # Must be performed last since this may kill the current thread.
141
+ shutdown!
142
+ end
143
+
144
+ def run
145
+ begin
146
+ d { 'worker started' }
147
+ loop do
148
+ case msg = queue.pop
149
+ when SHUTDOWN then break
150
+ when ConditionVariable then signal_marker(msg)
151
+ else work(msg)
152
+ end
153
+ end
154
+ ensure
155
+ d { 'stopping worker' }
156
+ end
157
+ rescue Exception => e
158
+ error {
159
+ msg = "error in worker thread (shutting down) class=%s message=%s\n\t%s"
160
+ sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
161
+ }
162
+ ensure
163
+ release_marker
164
+ end
165
+
166
+ def work(msg)
167
+ send_now(msg)
168
+ sleep(throttle_interval)
169
+ rescue StandardError => e
170
+ error {
171
+ msg = "Error in worker thread class=%s message=%s\n\t%s"
172
+ sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
173
+ }
174
+ sleep(1)
175
+ end
176
+
177
+ def throttle_interval
178
+ return 0 unless throttles[0]
179
+ mutex.synchronize do
180
+ throttles.reduce(1) {|a,e| a*e }
181
+ end
182
+ end
183
+
184
+ def notify_backend(payload)
185
+ debug { sprintf('worker notifying backend id=%s', payload.id) }
186
+ backend.notify(:notices, payload)
187
+ end
188
+
189
+ def add_throttle(t)
190
+ mutex.synchronize do
191
+ throttles.push(t)
192
+ end
193
+ end
194
+
195
+ def del_throttle
196
+ mutex.synchronize do
197
+ throttles.shift
198
+ end
199
+ end
200
+
201
+ def handle_response(msg, response)
202
+ debug { sprintf('worker response code=%s message=%s', response.code, response.message.to_s.dump) }
203
+
204
+ case response.code
205
+ when 429, 503
206
+ warn { sprintf('Error report failed: project is sending too many errors. id=%s code=%s throttle=1.25 interval=%s', msg.id, throttle_interval, response.code) }
207
+ add_throttle(1.25)
208
+ when 402
209
+ warn { sprintf('Error report failed: payment is required. id=%s code=%s', msg.id, response.code) }
210
+ suspend(3600)
211
+ when 403
212
+ warn { sprintf('Error report failed: API key is invalid. id=%s code=%s', msg.id, response.code) }
213
+ suspend(3600)
214
+ when 201
215
+ if throttle = del_throttle
216
+ info { sprintf('Success ⚡ https://app.honeybadger.io/notice/%s id=%s code=%s throttle=%s interval=%s', msg.id, msg.id, response.code, throttle_interval, response.code) }
217
+ else
218
+ info { sprintf('Success ⚡ https://app.honeybadger.io/notice/%s id=%s code=%s', msg.id, msg.id, response.code) }
219
+ end
220
+ when :error
221
+ warn { sprintf('Error report failed: an unknown error occurred. code=%s error=%s', response.code, response.message.to_s.dump) }
222
+ else
223
+ warn { sprintf('Error report failed: unknown response from server. code=%s', response.code) }
224
+ end
225
+ end
226
+
227
+ # Internal: Release the marker. Important to perform during cleanup when
228
+ # shutting down, otherwise it could end up waiting indefinitely.
229
+ #
230
+ # Returns nothing.
231
+ def release_marker
232
+ signal_marker(marker)
233
+ end
234
+
235
+ # Internal: Signal a marker.
236
+ #
237
+ # marker - The ConditionVariable marker to signal.
238
+ #
239
+ # Returns nothing.
240
+ def signal_marker(marker)
241
+ mutex.synchronize do
242
+ marker.signal
243
+ end
244
+ end
245
+ end
246
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeybadger
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.2
4
+ version: 3.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Honeybadger Industries LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-13 00:00:00.000000000 Z
11
+ date: 2016-12-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Make managing application errors a more pleasant experience.
14
14
  email:
@@ -25,9 +25,6 @@ files:
25
25
  - bin/honeybadger
26
26
  - lib/honeybadger.rb
27
27
  - lib/honeybadger/agent.rb
28
- - lib/honeybadger/agent/batch.rb
29
- - lib/honeybadger/agent/null_worker.rb
30
- - lib/honeybadger/agent/worker.rb
31
28
  - lib/honeybadger/backend.rb
32
29
  - lib/honeybadger/backend/base.rb
33
30
  - lib/honeybadger/backend/debug.rb
@@ -36,17 +33,23 @@ files:
36
33
  - lib/honeybadger/backend/test.rb
37
34
  - lib/honeybadger/backtrace.rb
38
35
  - lib/honeybadger/cli.rb
39
- - lib/honeybadger/cli/helpers.rb
36
+ - lib/honeybadger/cli/deploy.rb
37
+ - lib/honeybadger/cli/exec.rb
40
38
  - lib/honeybadger/cli/heroku.rb
39
+ - lib/honeybadger/cli/install.rb
41
40
  - lib/honeybadger/cli/main.rb
41
+ - lib/honeybadger/cli/notify.rb
42
+ - lib/honeybadger/cli/test.rb
42
43
  - lib/honeybadger/config.rb
43
- - lib/honeybadger/config/callbacks.rb
44
44
  - lib/honeybadger/config/defaults.rb
45
45
  - lib/honeybadger/config/env.rb
46
+ - lib/honeybadger/config/ruby.rb
46
47
  - lib/honeybadger/config/yaml.rb
47
48
  - lib/honeybadger/const.rb
49
+ - lib/honeybadger/context_manager.rb
48
50
  - lib/honeybadger/init/rails.rb
49
51
  - lib/honeybadger/init/rake.rb
52
+ - lib/honeybadger/init/ruby.rb
50
53
  - lib/honeybadger/init/sinatra.rb
51
54
  - lib/honeybadger/logging.rb
52
55
  - lib/honeybadger/notice.rb
@@ -61,20 +64,21 @@ files:
61
64
  - lib/honeybadger/plugins/sidekiq.rb
62
65
  - lib/honeybadger/plugins/sucker_punch.rb
63
66
  - lib/honeybadger/plugins/thor.rb
64
- - lib/honeybadger/plugins/unicorn.rb
65
67
  - lib/honeybadger/plugins/warden.rb
66
68
  - lib/honeybadger/rack/error_notifier.rb
67
- - lib/honeybadger/rack/metrics_reporter.rb
68
- - lib/honeybadger/rack/request_hash.rb
69
69
  - lib/honeybadger/rack/user_feedback.rb
70
70
  - lib/honeybadger/rack/user_informer.rb
71
+ - lib/honeybadger/ruby.rb
72
+ - lib/honeybadger/singleton.rb
71
73
  - lib/honeybadger/tasks.rb
72
74
  - lib/honeybadger/templates/feedback_form.erb
73
75
  - lib/honeybadger/util/http.rb
76
+ - lib/honeybadger/util/request_hash.rb
74
77
  - lib/honeybadger/util/request_payload.rb
75
78
  - lib/honeybadger/util/sanitizer.rb
76
79
  - lib/honeybadger/util/stats.rb
77
80
  - lib/honeybadger/version.rb
81
+ - lib/honeybadger/worker.rb
78
82
  - resources/ca-bundle.crt
79
83
  - vendor/capistrano-honeybadger/lib/capistrano/honeybadger.rb
80
84
  - vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap
@@ -128,12 +132,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
132
  requirements:
129
133
  - - ">="
130
134
  - !ruby/object:Gem::Version
131
- version: 1.9.3
135
+ version: 2.1.0
132
136
  required_rubygems_version: !ruby/object:Gem::Requirement
133
137
  requirements:
134
- - - ">="
138
+ - - ">"
135
139
  - !ruby/object:Gem::Version
136
- version: '0'
140
+ version: 1.3.1
137
141
  requirements: []
138
142
  rubyforge_project:
139
143
  rubygems_version: 2.5.1
@@ -1,50 +0,0 @@
1
- require 'securerandom'
2
-
3
- module Honeybadger
4
- class Agent
5
- class Batch
6
- def initialize(config, name, opts = {})
7
- @id = SecureRandom.uuid
8
- @config = config
9
- @name = name
10
- @max = opts.fetch(:max, 100)
11
- @interval = opts.fetch(:interval, 60)
12
- @future = opts.fetch(:now, now()) + interval
13
- @values = opts.fetch(:collection, Array.new)
14
- @mutex = Mutex.new
15
- end
16
-
17
- attr_reader :id
18
-
19
- def push(val)
20
- mutex.synchronize { values.push(val) }
21
- end
22
-
23
- def empty?
24
- mutex.synchronize { values.empty? }
25
- end
26
-
27
- def size
28
- mutex.synchronize { values.size }
29
- end
30
-
31
- def flush?
32
- size >= max || now >= future
33
- end
34
-
35
- def as_json(*args)
36
- mutex.synchronize do
37
- { name => values.map(&:to_h), :environment => config[:env], :hostname => config[:hostname] }
38
- end
39
- end
40
-
41
- private
42
-
43
- attr_reader :config, :name, :max, :interval, :values, :future, :mutex
44
-
45
- def now
46
- Time.now.to_i
47
- end
48
- end
49
- end
50
- end
@@ -1,26 +0,0 @@
1
- module Honeybadger
2
- class Agent
3
- # Internal: A default worker which does nothing.
4
- class NullWorker
5
- def push(obj)
6
- true
7
- end
8
-
9
- def shutdown
10
- true
11
- end
12
-
13
- def shutdown!
14
- true
15
- end
16
-
17
- def flush
18
- true
19
- end
20
-
21
- def start
22
- true
23
- end
24
- end
25
- end
26
- end
@@ -1,243 +0,0 @@
1
- require 'forwardable'
2
- require 'net/http'
3
-
4
- require 'honeybadger/logging'
5
- require 'honeybadger/agent/null_worker'
6
-
7
- module Honeybadger
8
- class Agent
9
- # Internal: A concurrent queue to notify the backend.
10
- class Worker
11
- extend Forwardable
12
-
13
- include Honeybadger::Logging::Helper
14
-
15
- # Internal: Sub-class thread so we have a named thread (useful for debugging in Thread.list).
16
- class Thread < ::Thread; end
17
-
18
- # Internal: A queue which enforces a maximum size.
19
- class Queue < ::Queue
20
- attr_reader :max_size
21
-
22
- def initialize(max_size)
23
- @max_size = max_size
24
- super()
25
- end
26
-
27
- def push(obj)
28
- super unless size == max_size
29
- end
30
- end
31
-
32
- SHUTDOWN = :__hb_worker_shutdown!
33
-
34
- def initialize(config, feature)
35
- @config = config
36
- @feature = feature
37
- @backend = config.backend
38
- @throttles = []
39
- @mutex = Mutex.new
40
- @marker = ConditionVariable.new
41
- @queue = Queue.new(config.max_queue_size)
42
- @shutdown = false
43
- @start_at = nil
44
- end
45
-
46
- def push(obj)
47
- return false unless start
48
- queue.push(obj)
49
- end
50
-
51
- # Internal: Shutdown the worker after sending remaining data.
52
- #
53
- # Returns true.
54
- def shutdown
55
- d { sprintf('shutting down worker feature=%s', feature) }
56
-
57
- mutex.synchronize do
58
- @shutdown = true
59
- @pid = nil
60
- queue.push(SHUTDOWN)
61
- end
62
-
63
- return true unless thread
64
-
65
- r = true
66
- unless Thread.current.eql?(thread)
67
- begin
68
- r = !!thread.join
69
- ensure
70
- shutdown! unless r
71
- end
72
- end
73
-
74
- r
75
- end
76
-
77
- def shutdown!
78
- mutex.synchronize do
79
- @shutdown = true
80
- @pid = nil
81
- queue.clear
82
- end
83
-
84
- d { sprintf('killing worker thread feature=%s', feature) }
85
-
86
- if thread
87
- Thread.kill(thread)
88
- thread.join # Allow ensure blocks to execute.
89
- end
90
-
91
- true
92
- end
93
-
94
- # Internal: Blocks until queue is processed up to this point in time.
95
- #
96
- # Returns nothing.
97
- def flush
98
- mutex.synchronize do
99
- if thread && thread.alive?
100
- queue.push(marker)
101
- marker.wait(mutex)
102
- end
103
- end
104
- end
105
-
106
- def start
107
- return false unless can_start?
108
-
109
- mutex.synchronize do
110
- @shutdown = false
111
- @start_at = nil
112
-
113
- return true if thread && thread.alive?
114
-
115
- @pid = Process.pid
116
- @thread = Thread.new { run }
117
- end
118
-
119
- true
120
- end
121
-
122
- private
123
-
124
- attr_reader :config, :backend, :feature, :queue, :pid, :mutex, :marker,
125
- :thread, :throttles
126
-
127
- def can_start?
128
- mutex.synchronize do
129
- return true unless @shutdown
130
- return false unless @start_at
131
- Time.now.to_i >= @start_at
132
- end
133
- end
134
-
135
- def suspend(interval)
136
- mutex.synchronize { @start_at = Time.now.to_i + interval }
137
-
138
- # Must be performed last since this may kill the current thread.
139
- shutdown!
140
- end
141
-
142
- def run
143
- begin
144
- d { sprintf('worker started feature=%s', feature) }
145
- loop do
146
- case msg = queue.pop
147
- when SHUTDOWN then break
148
- when ConditionVariable then signal_marker(msg)
149
- else process(msg)
150
- end
151
- end
152
- ensure
153
- d { sprintf('stopping worker feature=%s', feature) }
154
- end
155
- rescue Exception => e
156
- error {
157
- msg = "error in worker thread (shutting down) feature=%s class=%s message=%s\n\t%s"
158
- sprintf(msg, feature, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
159
- }
160
- ensure
161
- release_marker
162
- end
163
-
164
- def process(msg)
165
- handle_response(notify_backend(msg))
166
- sleep(throttle_interval)
167
- rescue StandardError => e
168
- error {
169
- msg = "error in worker thread feature=%s class=%s message=%s\n\t%s"
170
- sprintf(msg, feature, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
171
- }
172
- sleep(1)
173
- end
174
-
175
- def throttle_interval
176
- return 0 unless throttles[0]
177
- mutex.synchronize do
178
- throttles.reduce(1) {|a,e| a*e }
179
- end
180
- end
181
-
182
- def notify_backend(payload)
183
- debug { sprintf('worker notifying backend feature=%s id=%s', feature, payload.id) }
184
- backend.notify(feature, payload)
185
- end
186
-
187
- def add_throttle(t)
188
- mutex.synchronize do
189
- throttles.push(t)
190
- end
191
- end
192
-
193
- def del_throttle
194
- mutex.synchronize do
195
- throttles.shift
196
- end
197
- end
198
-
199
- def handle_response(response)
200
- debug { sprintf('worker response feature=%s code=%s message=%s', feature, response.code, response.message.to_s.dump) }
201
-
202
- case response.code
203
- when 429, 503
204
- add_throttle(1.25)
205
- debug { sprintf('worker applying throttle=1.25 interval=%s feature=%s code=%s', throttle_interval, feature, response.code) }
206
- when 402
207
- warn { sprintf('data will not be reported (payment required) feature=%s code=%s', feature, response.code) }
208
- suspend(3600)
209
- when 403
210
- warn { sprintf('data will not be reported feature=%s code=%s error=%s', feature, response.code, response.error.to_s.dump) }
211
- suspend(3600)
212
- when 201
213
- if throttle = del_throttle
214
- debug { sprintf('worker removing throttle=%s interval=%s feature=%s code=%s', throttle, throttle_interval, feature, response.code) }
215
- end
216
- when :error
217
- # Error logged by backend.
218
- else
219
- warn { sprintf('worker unknown response feature=%s code=%s', feature, response.code) }
220
- end
221
- end
222
-
223
- # Internal: Release the marker. Important to perform during cleanup when
224
- # shutting down, otherwise it could end up waiting indefinitely.
225
- #
226
- # Returns nothing.
227
- def release_marker
228
- signal_marker(marker)
229
- end
230
-
231
- # Internal: Signal a marker.
232
- #
233
- # marker - The ConditionVariable marker to signal.
234
- #
235
- # Returns nothing.
236
- def signal_marker(marker)
237
- mutex.synchronize do
238
- marker.signal
239
- end
240
- end
241
- end
242
- end
243
- end