Skip to content

Commit

Permalink
Only register one single shutdown hook
Browse files Browse the repository at this point in the history
Registering one shutdown hook Thread object per Context ever created
eventually causes the JVM to start throwing around OutOfMemoryErrors.
  • Loading branch information
ctrueden committed Oct 19, 2023
1 parent 5e8fb31 commit 41f3136
Showing 1 changed file with 26 additions and 2 deletions.
28 changes: 26 additions & 2 deletions src/main/java/org/scijava/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.scijava.event.ContextCreatedEvent;
import org.scijava.event.ContextDisposingEvent;
Expand Down Expand Up @@ -73,6 +76,14 @@ public class Context implements Disposable, AutoCloseable {
*/
public static final String STRICT_PROPERTY = "scijava.context.strict";

/** Set of currently active (not disposed) application contexts. */
private static final Map<Context, Boolean> CONTEXTS =
new ConcurrentHashMap<>(); // NB: ConcurrentHashMap disallows nulls.

// -- Static fields --

private static Thread shutdownThread = null;

// -- Fields --

/** Index of the application context's services. */
Expand Down Expand Up @@ -293,7 +304,20 @@ public Context(final Collection<Class<? extends Service>> serviceClasses,
}

// If JVM shuts down with context still active, clean up after ourselves.
Runtime.getRuntime().addShutdownHook(new Thread(() -> doDispose(false)));
if (shutdownThread == null) {
synchronized (Context.class) {
if (shutdownThread == null) {
shutdownThread = new Thread(() -> {
final List<Context> contexts = new ArrayList<>(CONTEXTS.keySet());
for (final Context context : contexts) {
context.doDispose(false);
}
});
Runtime.getRuntime().addShutdownHook(shutdownThread);
}
}
}
CONTEXTS.put(this, true);

// Publish an event to indicate that context initialization is complete.
final EventService eventService = getService(EventService.class);
Expand Down Expand Up @@ -432,7 +456,6 @@ public boolean isInjectable(final Class<?> type) {

@Override
public void dispose() {
if (disposed) return;
doDispose(true);
}

Expand Down Expand Up @@ -589,6 +612,7 @@ private String createMissingServiceMessage(
private synchronized void doDispose(final boolean announce) {
if (disposed) return;
disposed = true;
CONTEXTS.remove(this);
if (announce) {
final EventService eventService = getService(EventService.class);
if (eventService != null) eventService.publish(new ContextDisposingEvent());
Expand Down

0 comments on commit 41f3136

Please sign in to comment.