|
33 | 33 | import java.lang.reflect.Method; |
34 | 34 | import java.net.URL; |
35 | 35 | import java.net.URLClassLoader; |
| 36 | +import java.util.ArrayList; |
36 | 37 | import java.util.Arrays; |
37 | 38 | import java.util.Collection; |
38 | 39 | import java.util.Collections; |
39 | 40 | import java.util.List; |
| 41 | +import java.util.Map; |
| 42 | +import java.util.concurrent.ConcurrentHashMap; |
40 | 43 |
|
41 | 44 | import org.scijava.event.ContextCreatedEvent; |
42 | 45 | import org.scijava.event.ContextDisposingEvent; |
@@ -73,6 +76,14 @@ public class Context implements Disposable, AutoCloseable { |
73 | 76 | */ |
74 | 77 | public static final String STRICT_PROPERTY = "scijava.context.strict"; |
75 | 78 |
|
| 79 | + /** Set of currently active (not disposed) application contexts. */ |
| 80 | + private static final Map<Context, Boolean> CONTEXTS = |
| 81 | + new ConcurrentHashMap<>(); // NB: ConcurrentHashMap disallows nulls. |
| 82 | + |
| 83 | + // -- Static fields -- |
| 84 | + |
| 85 | + private static Thread shutdownThread = null; |
| 86 | + |
76 | 87 | // -- Fields -- |
77 | 88 |
|
78 | 89 | /** Index of the application context's services. */ |
@@ -293,7 +304,20 @@ public Context(final Collection<Class<? extends Service>> serviceClasses, |
293 | 304 | } |
294 | 305 |
|
295 | 306 | // If JVM shuts down with context still active, clean up after ourselves. |
296 | | - Runtime.getRuntime().addShutdownHook(new Thread(() -> doDispose(false))); |
| 307 | + if (shutdownThread == null) { |
| 308 | + synchronized (Context.class) { |
| 309 | + if (shutdownThread == null) { |
| 310 | + shutdownThread = new Thread(() -> { |
| 311 | + final List<Context> contexts = new ArrayList<>(CONTEXTS.keySet()); |
| 312 | + for (final Context context : contexts) { |
| 313 | + context.doDispose(false); |
| 314 | + } |
| 315 | + }); |
| 316 | + Runtime.getRuntime().addShutdownHook(shutdownThread); |
| 317 | + } |
| 318 | + } |
| 319 | + } |
| 320 | + CONTEXTS.put(this, true); |
297 | 321 |
|
298 | 322 | // Publish an event to indicate that context initialization is complete. |
299 | 323 | final EventService eventService = getService(EventService.class); |
@@ -432,7 +456,6 @@ public boolean isInjectable(final Class<?> type) { |
432 | 456 |
|
433 | 457 | @Override |
434 | 458 | public void dispose() { |
435 | | - if (disposed) return; |
436 | 459 | doDispose(true); |
437 | 460 | } |
438 | 461 |
|
@@ -589,6 +612,7 @@ private String createMissingServiceMessage( |
589 | 612 | private synchronized void doDispose(final boolean announce) { |
590 | 613 | if (disposed) return; |
591 | 614 | disposed = true; |
| 615 | + CONTEXTS.remove(this); |
592 | 616 | if (announce) { |
593 | 617 | final EventService eventService = getService(EventService.class); |
594 | 618 | if (eventService != null) eventService.publish(new ContextDisposingEvent()); |
|
0 commit comments