|
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