enhanced_errors 3.0.3 → 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,12 +4,12 @@ require 'set'
4
4
  require 'json'
5
5
  require 'monitor'
6
6
 
7
- require_relative 'enhanced/colors'
8
- require_relative 'enhanced/exception'
7
+ module Enhanced; end
9
8
 
10
- IGNORED_EXCEPTIONS = %w[SystemExit NoMemoryError SignalException Interrupt ScriptError LoadError
11
- NotImplementedError SyntaxError RSpec::Expectations::ExpectationNotMetError
12
- RSpec::Matchers::BuiltIn::RaiseError SystemStackError Psych::BadAlias]
9
+ # Exceptions we could handle but overlook for other reasons. These class constants are not always loaded
10
+ # and generally are only be available when `required`, so we detect them by strings.
11
+ IGNORED_EXCEPTIONS = %w[RSpec::Expectations::ExpectationNotMetError RSpec::Matchers::BuiltIn::RaiseError
12
+ JSON::ParserError Zlib::Error OpenSSL::SSL::SSLError Psych::Exception]
13
13
 
14
14
  class EnhancedErrors
15
15
  extend ::Enhanced
@@ -45,13 +45,8 @@ class EnhancedErrors
45
45
  ].freeze
46
46
 
47
47
  RAILS_SKIP_LIST = [
48
- :@new_record,
49
- :@attributes,
50
48
  :@association_cache,
51
- :@readonly,
52
- :@previously_new_record,
53
49
  :@_routes,
54
- :@routes,
55
50
  :@app,
56
51
  :@arel_table,
57
52
  :@assertion_instance,
@@ -127,7 +122,7 @@ class EnhancedErrors
127
122
  mutex.synchronize { @max_capture_length || DEFAULT_MAX_CAPTURE_LENGTH }
128
123
  end
129
124
 
130
- def max_capture_length=(val)
125
+ def max_capture_length=(value)
131
126
  mutex.synchronize { @max_capture_length = value }
132
127
  end
133
128
 
@@ -140,7 +135,6 @@ class EnhancedErrors
140
135
  end
141
136
  end
142
137
 
143
-
144
138
  def reset!
145
139
  mutex.synchronize do
146
140
  @rspec_tracepoint&.disable
@@ -155,7 +149,7 @@ class EnhancedErrors
155
149
 
156
150
  def skip_list
157
151
  mutex.synchronize do
158
- @skip_list ||= DEFAULT_SKIP_LIST
152
+ @skip_list ||= DEFAULT_SKIP_LIST.to_set
159
153
  end
160
154
  end
161
155
 
@@ -187,12 +181,13 @@ class EnhancedErrors
187
181
 
188
182
  def add_to_skip_list(*vars)
189
183
  mutex.synchronize do
190
- @skip_list.concat(vars)
184
+ @skip_list.add(*vars)
191
185
  end
192
186
  end
193
187
 
194
188
  def enhance_exceptions!(enabled: true, debug: false, capture_events: nil, override_messages: false, **options, &block)
195
189
  mutex.synchronize do
190
+ ensure_extensions_are_required
196
191
  @exception_trace&.disable
197
192
  @exception_trace = nil
198
193
 
@@ -223,10 +218,11 @@ class EnhancedErrors
223
218
 
224
219
  events = @capture_events ? @capture_events.to_a : default_capture_events
225
220
  @exception_trace = TracePoint.new(*events) do |tp|
221
+ return unless exception_is_handleable?(tp.raised_exception)
226
222
  handle_tracepoint_event(tp)
227
223
  end
228
224
 
229
- @exception_trace.enable if @enabled
225
+ @exception_trace&.enable if @enabled
230
226
  end
231
227
  end
232
228
 
@@ -235,7 +231,8 @@ class EnhancedErrors
235
231
  end
236
232
 
237
233
  def start_minitest_binding_capture
238
- EnhancedExceptionContext.clear_all
234
+ ensure_extensions_are_required
235
+ Enhanced::ExceptionContext.clear_all
239
236
  @enabled = true if @enabled.nil?
240
237
  return unless @enabled
241
238
  mutex.synchronize do
@@ -243,7 +240,7 @@ class EnhancedErrors
243
240
  next unless tp.method_id.to_s.start_with?('test_') && is_a_minitest?(tp.defined_class)
244
241
  @minitest_test_binding = tp.binding
245
242
  end
246
- @minitest_trace.enable
243
+ @minitest_trace&.enable
247
244
  end
248
245
  end
249
246
 
@@ -269,7 +266,8 @@ class EnhancedErrors
269
266
  end
270
267
 
271
268
  def start_rspec_binding_capture
272
- EnhancedExceptionContext.clear_all
269
+ ensure_extensions_are_required
270
+ Enhanced::ExceptionContext.clear_all
273
271
  @enabled = true if @enabled.nil?
274
272
  return unless @enabled
275
273
 
@@ -277,21 +275,29 @@ class EnhancedErrors
277
275
  @rspec_example_binding = nil
278
276
  @capture_next_binding = false
279
277
  @rspec_tracepoint&.disable
280
-
281
278
  @rspec_tracepoint = TracePoint.new(:raise) do |tp|
282
- class_name = tp.raised_exception.class.name
283
- case class_name
284
- when 'RSpec::Expectations::ExpectationNotMetError'
285
- start_rspec_binding_trap
286
- else
287
- handle_tracepoint_event(tp)
288
- end
279
+ return unless exception_is_handleable?(tp.raised_exception)
280
+ class_name = tp.raised_exception.class.name
281
+ case class_name
282
+ when 'RSpec::Expectations::ExpectationNotMetError'
283
+ start_rspec_binding_trap
284
+ else
285
+ handle_tracepoint_event(tp)
289
286
  end
290
287
  end
291
- @rspec_tracepoint.enable
288
+ end
289
+ @rspec_tracepoint&.enable
292
290
  end
293
291
 
294
- # grab the next rspec binding that goes by, and then stop the expensive listening trace
292
+ # Behavior: Grabs the next rspec spec binding that goes by, and stops the more-expensive b_return trace.
293
+ # This part of RSpec has been stable, since 2015, so although this is kluge-y, it is stable.
294
+ # The optimization does a 2-3x on spec speed vs. opening up the Tracepoint. With it,
295
+ # things are pretty close in speed to plain rspec.
296
+ # Should the behavior change this can be updated by using a trace to print out items
297
+ # and their local variables then, find the exception or call that goes by right
298
+ # before the spec blocks with the variables, and use that to narrow-down the costly part of
299
+ # the probe to just this point in time. The good news is that
300
+ # this part is test-time only, and this optimization and kluge only applies to RSpec.
295
301
  def start_rspec_binding_trap
296
302
  @rspec_binding_trap = TracePoint.new(:b_return) do |tp|
297
303
  # kluge-y hack and will be a pain to maintain
@@ -302,13 +308,12 @@ class EnhancedErrors
302
308
  next unless @capture_next_binding
303
309
  @capture_next_binding = false
304
310
  @rspec_example_binding = tp.binding
305
- @rspec_binding_trap.disable
311
+ @rspec_binding_trap&.disable
306
312
  @rspec_binding_trap = nil
307
313
  end
308
- @rspec_binding_trap.enable
314
+ @rspec_binding_trap&.enable
309
315
  end
310
316
 
311
-
312
317
  def stop_rspec_binding_capture
313
318
  mutex.synchronize do
314
319
  @rspec_tracepoint&.disable
@@ -327,6 +332,8 @@ class EnhancedErrors
327
332
 
328
333
  locals = b.local_variables.map { |var| [var, safe_local_variable_get(b, var)] }.to_h
329
334
  receiver = b.receiver
335
+ return unless safe_to_inspect?(receiver)
336
+
330
337
  instance_vars = receiver.instance_variables
331
338
  instances = instance_vars.map { |var| [var, safe_instance_variable_get(receiver, var)] }.to_h
332
339
 
@@ -355,7 +362,7 @@ class EnhancedErrors
355
362
  globals: {}
356
363
  },
357
364
  exception: 'NoException',
358
- capture_event: 'RSpecContext'
365
+ capture_event: 'test_context'
359
366
  }
360
367
 
361
368
  default_on_capture(binding_info)
@@ -437,11 +444,7 @@ class EnhancedErrors
437
444
  env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
438
445
  @output_format = case env
439
446
  when 'development', 'test'
440
- if running_in_ci?
441
- :plaintext
442
- else
443
- :terminal
444
- end
447
+ running_in_ci? ? :plaintext : :terminal
445
448
  when 'production'
446
449
  :json
447
450
  else
@@ -453,28 +456,32 @@ class EnhancedErrors
453
456
  def running_in_ci?
454
457
  mutex.synchronize do
455
458
  return @running_in_ci if defined?(@running_in_ci)
456
-
457
459
  @running_in_ci = CI_ENV_VARS.any? { |_, value| value.to_s.downcase == 'true' }
458
460
  end
459
461
  end
460
462
 
461
463
  def apply_skip_list(binding_info)
462
- mutex.synchronize do
463
- variables = binding_info[:variables]
464
- variables[:instances]&.reject! { |var, _| skip_list.include?(var) || (var.to_s.start_with?('@_') && !@debug) }
465
- variables[:locals]&.reject! { |var, _| skip_list.include?(var) }
466
- if @debug
467
- variables[:globals]&.reject! { |var, _| skip_list.include?(var) }
468
- end
464
+ binding_info[:variables][:instances]&.reject! { |var, _| skip_list.include?(var) || (var.to_s[0, 2] == '@_' && !@debug) }
465
+ binding_info[:variables][:locals]&.reject! { |var, _| skip_list.include?(var) }
466
+ if @debug
467
+ binding_info[:variables][:globals]&.reject! { |var, _| skip_list.include?(var) }
469
468
  end
470
469
  binding_info
471
470
  end
472
471
 
473
472
  def validate_binding_format(binding_info)
474
- unless binding_info.keys.include?(:capture_event) && binding_info[:variables].is_a?(Hash)
475
- return nil
473
+ binding_info.keys.include?(:capture_event) && binding_info[:variables].is_a?(Hash)
474
+ end
475
+
476
+ # Here, we are detecting BasicObject, which is surprisingly annoying.
477
+ # We also, importantly, need to detect descendants, as they will also present with a
478
+ # lack of :respond_to? and any other useful method for us.
479
+ def safe_to_inspect?(obj)
480
+ begin
481
+ obj.class
482
+ rescue NoMethodError
483
+ return false
476
484
  end
477
- binding_info
478
485
  end
479
486
 
480
487
  def binding_info_string(binding_info)
@@ -524,7 +531,6 @@ class EnhancedErrors
524
531
 
525
532
  private
526
533
 
527
-
528
534
  def handle_tracepoint_event(tp)
529
535
  # Check enabled outside the synchronized block for speed, but still safe due to re-check inside.
530
536
  return unless enabled
@@ -533,9 +539,13 @@ class EnhancedErrors
533
539
  Thread.current[:enhanced_errors_processing] = true
534
540
  exception = tp.raised_exception
535
541
 
536
- capture_me = mutex.synchronize do
537
- !exception.frozen? && (@eligible_for_capture || method(:default_eligible_for_capture)).call(exception)
538
- end
542
+ return if exception.frozen?
543
+
544
+ capture_me = if @eligible_for_capture
545
+ @eligible_for_capture.call(exception)
546
+ else
547
+ default_eligible_for_capture(exception)
548
+ end
539
549
 
540
550
  unless capture_me
541
551
  Thread.current[:enhanced_errors_processing] = false
@@ -544,32 +554,43 @@ class EnhancedErrors
544
554
 
545
555
  binding_context = tp.binding
546
556
  method_name = tp.method_id
557
+ receiver = binding_context.receiver
558
+
559
+ locals = {}
560
+
561
+ binding_context.local_variables.each do |var|
562
+ locals[var] = safe_local_variable_get(binding_context, var)
563
+ end
564
+
547
565
  method_and_args = {
548
566
  object_name: determine_object_name(tp, method_name),
549
- args: extract_arguments(tp, method_name)
567
+ args: extract_arguments(tp, method_name, locals)
550
568
  }
551
569
 
552
- locals = binding_context.local_variables.map { |var|
553
- [var, safe_local_variable_get(binding_context, var)]
554
- }.to_h
570
+ instances = {}
555
571
 
556
- instance_vars = binding_context.receiver.instance_variables
557
- instances = instance_vars.map { |var|
558
- [var, safe_instance_variable_get(binding_context.receiver, var)]
559
- }.to_h
572
+ begin
573
+ if safe_to_inspect?(receiver)
574
+ receiver.instance_variables.each { |var|
575
+ instances[var] = safe_instance_variable_get(receiver, var)
576
+ }
577
+ end
578
+ rescue => e
579
+ puts "#{e.class.name} #{e.backtrace}"
580
+ end
560
581
 
561
582
  lets = {}
562
583
 
563
584
  globals = {}
564
585
  mutex.synchronize do
565
586
  if @debug
566
- globals = (global_variables - @original_global_variables.to_a).map { |var|
567
- [var, get_global_variable_value(var)]
568
- }.to_h
587
+ globals = (global_variables - @original_global_variables.to_a).each do |var|
588
+ globals[var] = get_global_variable_value(var)
589
+ end
569
590
  end
570
591
  end
571
592
 
572
- capture_event = safe_to_s(tp.event)
593
+ capture_event = tp.event.to_s
573
594
  location = "#{safe_to_s(tp.path)}:#{safe_to_s(tp.lineno)}"
574
595
  binding_info = {
575
596
  source: location,
@@ -583,17 +604,16 @@ class EnhancedErrors
583
604
  lets: lets,
584
605
  globals: globals
585
606
  },
586
- exception: safe_to_s(exception.class.name),
607
+ exception: exception.class.name,
587
608
  capture_event: capture_event
588
609
  }
589
610
 
590
611
  binding_info = default_on_capture(binding_info)
591
- on_capture_hook_local = mutex.synchronize { @on_capture_hook }
592
612
 
593
- if on_capture_hook_local
613
+ if on_capture_hook
594
614
  begin
595
615
  Thread.current[:on_capture] = true
596
- binding_info = on_capture_hook_local.call(binding_info)
616
+ binding_info = on_capture_hook.call(binding_info)
597
617
  rescue
598
618
  binding_info = nil
599
619
  ensure
@@ -601,20 +621,16 @@ class EnhancedErrors
601
621
  end
602
622
  end
603
623
 
604
- if binding_info
605
- binding_info = validate_binding_format(binding_info)
606
- if binding_info && exception.binding_infos.length >= MAX_BINDING_INFOS
607
- exception.binding_infos.delete_at(MAX_BINDING_INFOS / 2.round)
608
- end
609
- if binding_info
610
- exception.binding_infos << binding_info
611
- mutex.synchronize do
612
- override_exception_message(exception, exception.binding_infos) if @override_messages
613
- end
614
- end
624
+ return unless binding_info && validate_binding_format(binding_info)
625
+ if exception.binding_infos.length >= MAX_BINDING_INFOS
626
+ exception.binding_infos.delete_at(MAX_BINDING_INFOS / 2.round)
615
627
  end
616
- rescue
617
- # Avoid raising exceptions here
628
+ exception.binding_infos << binding_info
629
+ mutex.synchronize do
630
+ override_exception_message(exception, exception.binding_infos) if @override_messages
631
+ end
632
+ rescue => e
633
+ puts "Error: #{e&.class&.name} #{e&.backtrace}"
618
634
  ensure
619
635
  Thread.current[:enhanced_errors_processing] = false
620
636
  end
@@ -674,17 +690,15 @@ class EnhancedErrors
674
690
  capture_events.is_a?(Array) && capture_events.all? { |ev| [:raise, :rescue].include?(ev) }
675
691
  end
676
692
 
677
- def extract_arguments(tp, method_name)
693
+ def extract_arguments(tp, method_name, local_vars_hash)
678
694
  return '' unless method_name
679
695
  begin
680
- bind = tp.binding
681
696
  unbound_method = tp.defined_class.instance_method(method_name)
682
697
  method_obj = unbound_method.bind(tp.self)
683
698
  parameters = method_obj.parameters
684
- locals = bind.local_variables
685
699
 
686
700
  parameters.map do |(_, name)|
687
- value = locals.include?(name) ? safe_local_variable_get(bind, name) : nil
701
+ value = local_vars_hash[name]
688
702
  "#{name}=#{safe_inspect(value)}"
689
703
  rescue => e
690
704
  "#{name}=[Error getting argument: #{e.message}]"
@@ -696,17 +710,20 @@ class EnhancedErrors
696
710
 
697
711
  def determine_object_name(tp, method_name = '')
698
712
  begin
699
- self_class = Object.instance_method(:class).bind(tp.self).call
700
- singleton_class = Object.instance_method(:singleton_class).bind(tp.self).call
701
-
702
- if self_class && tp.defined_class == singleton_class
703
- object_identifier = safe_to_s(tp.self)
704
- method_suffix = method_name && !method_name.empty? ? ".#{method_name}" : ""
705
- "#{object_identifier}#{method_suffix}"
713
+ # Check if we're dealing with a singleton method
714
+ if (
715
+ class << tp.self
716
+ self;
717
+ end) == tp.defined_class
718
+ # Singleton method call
719
+ object_str = safe_to_s(tp.self)
720
+ method_suffix = method_name.to_s.empty? ? '' : ".#{method_name}"
721
+ "#{object_str}#{method_suffix}"
706
722
  else
707
- object_class_name = safe_to_s(self_class.name || 'UnknownClass')
708
- method_suffix = method_name && !method_name.empty? ? "##{method_name}" : ""
709
- "#{object_class_name}#{method_suffix}"
723
+ # Instance method call
724
+ klass_name = safe_to_s(tp.self.class.name || 'UnknownClass')
725
+ method_suffix = method_name.to_s.empty? ? '' : "##{method_name}"
726
+ "#{klass_name}#{method_suffix}"
710
727
  end
711
728
  rescue
712
729
  '[ErrorGettingName]'
@@ -798,10 +815,42 @@ class EnhancedErrors
798
815
  apply_skip_list(binding_info)
799
816
  end
800
817
 
818
+ # By default, we have filtering for safety, but past that, we capture everything by default
819
+ # at the moment.
801
820
  def default_eligible_for_capture(exception)
802
- ignored = ignored_exception?(exception)
803
- rspec = exception.class.name.start_with?('RSpec::Matchers')
804
- !ignored && !rspec
821
+ true
822
+ end
823
+
824
+ def exception_is_handleable?(exception)
825
+ case exception
826
+ when SystemExit, SignalException, SystemStackError, NoMemoryError
827
+ # Non-actionable: Ignore these exceptions
828
+ false
829
+ when SyntaxError, LoadError, ScriptError
830
+ # Non-actionable: Structural issues so there's no useful runtime context
831
+ false
832
+ else
833
+ # Ignore internal fatal errors
834
+ exception.class.to_s != 'fatal'
835
+ end
836
+ end
837
+
838
+ # This prevents loading it for say, production, if you don't want to,
839
+ # and keeps things cleaner. It allows a path to put this behind a feature-flag
840
+ # or env variable, and dynamically enable some capture instrumentation only
841
+ # when a Heisenbug is being hunted.
842
+ def ensure_extensions_are_required
843
+ mutex.synchronize do
844
+ return if @loaded_required_extensions
845
+ require_relative 'enhanced/colors'
846
+ require_relative 'enhanced/exception'
847
+ @loaded_required_extensions = true
848
+ end
805
849
  end
850
+
806
851
  end
807
852
  end
853
+
854
+ module Enhanced
855
+
856
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enhanced_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.3
4
+ version: 3.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Beland
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-18 00:00:00.000000000 Z
10
+ date: 2024-12-25 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: awesome_print
@@ -69,24 +68,22 @@ dependencies:
69
68
  description: 'EnhancedErrors will automatically enhance your errors with messages
70
69
  containing variable values from the moment they were raised, using no extra dependencies,
71
70
  and only Ruby''s built-in TracePoint. '
72
- email:
73
71
  executables: []
74
72
  extensions: []
75
73
  extra_rdoc_files: []
76
74
  files:
77
- - ".yardoc/checksums"
78
- - ".yardoc/complete"
79
- - ".yardoc/object_types"
80
- - ".yardoc/objects/root.dat"
81
- - ".yardoc/proxy_types"
82
75
  - LICENSE
83
76
  - README.md
84
77
  - benchmark/benchmark.rb
85
78
  - benchmark/memory_bench.rb
79
+ - benchmark/result.txt
86
80
  - benchmark/stackprofile.rb
87
81
  - doc/Context.html
88
82
  - doc/Enhanced.html
89
83
  - doc/Enhanced/Colors.html
84
+ - doc/Enhanced/Context.html
85
+ - doc/Enhanced/ExceptionBindingInfos.html
86
+ - doc/Enhanced/ExceptionContext.html
90
87
  - doc/Enhanced/Integrations.html
91
88
  - doc/Enhanced/Integrations/RSpecErrorFailureMessage.html
92
89
  - doc/EnhancedErrors.html
@@ -117,8 +114,8 @@ files:
117
114
  - examples/demo_rspec.rb
118
115
  - lib/enhanced/colors.rb
119
116
  - lib/enhanced/context.rb
120
- - lib/enhanced/enhanced_exception_context.rb
121
117
  - lib/enhanced/exception.rb
118
+ - lib/enhanced/exception_context.rb
122
119
  - lib/enhanced/minitest_patch.rb
123
120
  - lib/enhanced_errors.rb
124
121
  homepage: https://github.com/ericbeland/enhanced_errors
@@ -126,7 +123,6 @@ licenses: []
126
123
  metadata:
127
124
  homepage_uri: https://github.com/ericbeland/enhanced_errors
128
125
  source_code_uri: https://github.com/ericbeland/enhanced_errors
129
- post_install_message:
130
126
  rdoc_options: []
131
127
  require_paths:
132
128
  - lib
@@ -141,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
137
  - !ruby/object:Gem::Version
142
138
  version: '0'
143
139
  requirements: []
144
- rubygems_version: 3.5.22
145
- signing_key:
140
+ rubygems_version: 3.6.2
146
141
  specification_version: 4
147
142
  summary: Automatically enhance your errors with messages containing variable values
148
143
  from the moment they were raised.
data/.yardoc/checksums DELETED
@@ -1,6 +0,0 @@
1
- lib/enhanced/colors.rb ed3b11d00ff9ceed089d4a65f0be5b3fca64bbe6
2
- lib/enhanced_errors.rb f79331fea888262a0447d2fff4e5067fdf1418a9
3
- lib/enhanced/context.rb 24ca2d1f4ee2ff48dd83c913ad1f1e7c1aa367c4
4
- lib/enhanced/exception.rb 5572411e9e32bbe9ed01b98787e1a53a4ab61408
5
- lib/enhanced/minitest_patch.rb 3e7fb88ddc37a1f966877735a43ae206ee396bbc
6
- lib/enhanced/enhanced_exception_context.rb 23423dbdb33b7961a0b8a297e052ebb2fbe1c6ac
data/.yardoc/complete DELETED
File without changes
data/.yardoc/object_types DELETED
Binary file
Binary file
data/.yardoc/proxy_types DELETED
Binary file
@@ -1,47 +0,0 @@
1
- require 'weakref'
2
-
3
- require_relative 'context'
4
-
5
- require 'weakref'
6
- require 'monitor'
7
-
8
- module EnhancedExceptionContext
9
- extend self
10
-
11
- REGISTRY = {}
12
- MUTEX = Monitor.new
13
-
14
- def store_context(exception, context)
15
- MUTEX.synchronize do
16
- REGISTRY[exception.object_id] = { weak_exc: WeakRef.new(exception), context: context }
17
- end
18
- end
19
-
20
- def context_for(exception)
21
- MUTEX.synchronize do
22
- entry = REGISTRY[exception.object_id]
23
- return nil unless entry
24
-
25
- begin
26
- _ = entry[:weak_exc].__getobj__ # ensure exception is still alive
27
- entry[:context]
28
- rescue RefError
29
- # Exception no longer alive, clean up
30
- REGISTRY.delete(exception.object_id)
31
- nil
32
- end
33
- end
34
- end
35
-
36
- def clear_context(exception)
37
- MUTEX.synchronize do
38
- REGISTRY.delete(exception.object_id)
39
- end
40
- end
41
-
42
- def clear_all
43
- MUTEX.synchronize do
44
- REGISTRY.clear
45
- end
46
- end
47
- end