11package com .bugsnag ;
22
33import java .lang .Thread .UncaughtExceptionHandler ;
4- import java .util .Set ;
5- import java .util .WeakHashMap ;
4+ import java .lang .ref .WeakReference ;
5+ import java .util .Arrays ;
6+ import java .util .Collections ;
7+ import java .util .Objects ;
8+ import java .util .concurrent .atomic .AtomicBoolean ;
9+ import java .util .concurrent .atomic .AtomicReference ;
10+ import java .util .stream .Stream ;
611
712
813class ExceptionHandler implements UncaughtExceptionHandler {
14+ private static volatile ExceptionHandler singletonInstance = null ;
15+
916 private final UncaughtExceptionHandler originalHandler ;
10- private final WeakHashMap <Bugsnag , Boolean > clientMap = new WeakHashMap <Bugsnag , Boolean >();
17+ private final AtomicReference <WeakReference <Bugsnag >[]> weakClients = new AtomicReference <>(null );
18+ private final AtomicBoolean installed = new AtomicBoolean (false );
19+
20+ /**
21+ * Returns all the Bugsnag instances associated with this ExceptionHandler.
22+ *
23+ * @return an immutable iterable of the Bugsnag instances associated with this ExceptionHandler (never null)
24+ */
25+ Iterable <Bugsnag > uncaughtExceptionClients () {
26+ WeakReference <Bugsnag >[] clientRefs = weakClients .get ();
27+ if (clientRefs == null || clientRefs .length == 0 ) {
28+ return Collections .emptySet ();
29+ }
1130
12- Set <Bugsnag > uncaughtExceptionClients () {
13- return clientMap .keySet ();
31+ return () -> Stream .of (clientRefs )
32+ .map (WeakReference ::get )
33+ .filter (Objects ::nonNull )
34+ .iterator ();
1435 }
1536
1637 static void enable (Bugsnag bugsnag ) {
17- UncaughtExceptionHandler currentHandler = Thread .getDefaultUncaughtExceptionHandler ();
38+ ExceptionHandler handler = getGlobalOrCreate ();
39+ handler .add (bugsnag );
1840
19- // Find or create the Bugsnag ExceptionHandler
20- ExceptionHandler bugsnagHandler ;
21- if (currentHandler instanceof ExceptionHandler ) {
22- bugsnagHandler = (ExceptionHandler ) currentHandler ;
23- } else {
24- bugsnagHandler = new ExceptionHandler (currentHandler );
25- Thread .setDefaultUncaughtExceptionHandler (bugsnagHandler );
41+ if (handler .installed .compareAndSet (false , true )) {
42+ Thread .setDefaultUncaughtExceptionHandler (handler );
2643 }
27-
28- // Subscribe this bugsnag to uncaught exceptions
29- bugsnagHandler .clientMap .put (bugsnag , true );
3044 }
3145
3246 static void disable (Bugsnag bugsnag ) {
33- // Find the Bugsnag ExceptionHandler
34- UncaughtExceptionHandler currentHandler = Thread .getDefaultUncaughtExceptionHandler ();
35- if (currentHandler instanceof ExceptionHandler ) {
36- // Unsubscribe this bugsnag from uncaught exceptions
37- ExceptionHandler bugsnagHandler = (ExceptionHandler ) currentHandler ;
38- bugsnagHandler .clientMap .remove (bugsnag );
39-
40- // Remove the Bugsnag ExceptionHandler if no clients are subscribed
41- if (bugsnagHandler .clientMap .size () == 0 ) {
42- Thread .setDefaultUncaughtExceptionHandler (bugsnagHandler .originalHandler );
43- }
47+ ExceptionHandler handler = getGlobalOrNull ();
48+ if (handler == null ) {
49+ return ; // No handler installed, so nothing to disable
50+ }
51+
52+ if (handler .remove (bugsnag )) {
53+ handler .cleanup ();
4454 }
4555 }
4656
@@ -51,10 +61,13 @@ static void disable(Bugsnag bugsnag) {
5161 @ Override
5262 public void uncaughtException (java .lang .Thread thread , Throwable throwable ) {
5363 // Notify any subscribed clients of the uncaught exception
54- for (Bugsnag bugsnag : clientMap . keySet ()) {
55- if (bugsnag .getConfig ().shouldSendUncaughtExceptions ()) {
64+ for (Bugsnag bugsnag : uncaughtExceptionClients ()) {
65+ if (bugsnag .getConfig ().getAutoDetectErrors ()) {
5666 HandledState handledState = HandledState .newInstance (
57- HandledState .SeverityReasonType .REASON_UNHANDLED_EXCEPTION , Severity .ERROR );
67+ HandledState .SeverityReasonType .REASON_UNHANDLED_EXCEPTION ,
68+ Severity .ERROR
69+ );
70+
5871 bugsnag .notify (throwable , handledState , thread );
5972 }
6073 }
@@ -68,4 +81,120 @@ public void uncaughtException(java.lang.Thread thread, Throwable throwable) {
6881 throwable .printStackTrace (System .err );
6982 }
7083 }
84+
85+ @ SuppressWarnings ("unchecked" )
86+ void add (Bugsnag bugsnag ) {
87+ WeakReference <Bugsnag > newRef = new WeakReference <>(bugsnag );
88+
89+ while (true ) {
90+ final WeakReference <Bugsnag >[] currentRefs = weakClients .get ();
91+
92+ if (currentRefs != null ) {
93+ // Create a new array with the new client added
94+ WeakReference <Bugsnag >[] newRefs = new WeakReference [currentRefs .length + 1 ];
95+ int index = 0 ;
96+ for (WeakReference <Bugsnag > ref : currentRefs ) {
97+ // Copy existing non-garbage collected references
98+ // It's not as fast as System.arraycopy, but it avoids needing even more copies of the array
99+ if (ref .get () != null ) {
100+ newRefs [index ++] = ref ;
101+ }
102+ }
103+ newRefs [index ++] = newRef ;
104+
105+ if (index < newRefs .length ) {
106+ // If some references were garbage collected, create a smaller array
107+ newRefs = Arrays .copyOf (newRefs , index );
108+ }
109+
110+ // Attempt to update the reference atomically
111+ if (weakClients .compareAndSet (currentRefs , newRefs )) {
112+ return ; // Successfully added the client
113+ }
114+ } else {
115+ WeakReference <Bugsnag >[] newRefs = new WeakReference [] {
116+ newRef
117+ };
118+
119+ // Attempt to update the reference atomically
120+ if (weakClients .compareAndSet (null , newRefs )) {
121+ return ; // Successfully added the client
122+ }
123+ }
124+ }
125+ }
126+
127+ @ SuppressWarnings ("unchecked" )
128+ boolean remove (Bugsnag bugsnag ) {
129+ while (true ) {
130+ final WeakReference <Bugsnag >[] currentRefs = weakClients .get ();
131+ if (currentRefs == null || currentRefs .length == 0 ) {
132+ return false ;
133+ }
134+
135+ // Create a new array with the specified client removed
136+ WeakReference <Bugsnag >[] newRefs = new WeakReference [currentRefs .length - 1 ];
137+ int index = 0 ;
138+ for (WeakReference <Bugsnag > ref : currentRefs ) {
139+ Bugsnag client = ref .get ();
140+ if (client != null && client != bugsnag ) {
141+ if (index >= newRefs .length ) {
142+ // if we reached this point, then 'bugsnag' wasn't in the list - this will always be
143+ // the last element in the list, and it wasn't the one we are trying to remove so we
144+ // can exit here
145+ return false ;
146+ }
147+ newRefs [index ++] = ref ;
148+ }
149+ }
150+
151+ if (index < newRefs .length ) {
152+ // If some references were garbage collected, create a smaller array
153+ newRefs = Arrays .copyOf (newRefs , index );
154+ }
155+
156+ if (weakClients .compareAndSet (currentRefs , newRefs )) {
157+ // Return true if the array changed (target removed or GC'd refs cleaned)
158+ return index < currentRefs .length ;
159+ }
160+ }
161+ }
162+
163+ private void cleanup () {
164+ WeakReference <Bugsnag >[] refs = weakClients .get ();
165+ if (refs != null && refs .length > 0 ) {
166+ // this instance still has clients
167+ return ;
168+ }
169+
170+ if (isCurrentInstalledHandler () && installed .compareAndSet (true , false )) {
171+ Thread .setDefaultUncaughtExceptionHandler (originalHandler );
172+ }
173+ }
174+
175+ /**
176+ * Return true if this instance is currently the default uncaught exception handler. If this instance is installed
177+ * as a handler, but is not the outermost/current handler this will return false (meaning it is not safe to
178+ * uninstall this handler as it is possibly being delegated to by another crash handler).
179+ *
180+ * @return true if this instance is currently the default uncaught exception handler, false otherwise
181+ */
182+ private boolean isCurrentInstalledHandler () {
183+ return Thread .getDefaultUncaughtExceptionHandler () == this ;
184+ }
185+
186+ static ExceptionHandler getGlobalOrCreate () {
187+ if (singletonInstance == null ) {
188+ synchronized (ExceptionHandler .class ) {
189+ if (singletonInstance == null ) {
190+ singletonInstance = new ExceptionHandler (Thread .getDefaultUncaughtExceptionHandler ());
191+ }
192+ }
193+ }
194+ return singletonInstance ;
195+ }
196+
197+ static ExceptionHandler getGlobalOrNull () {
198+ return singletonInstance ;
199+ }
71200}
0 commit comments