blob: f52025fd6be54583db0b2ed7fc8b5ede30bc6615 [file] [log] [blame]
/*
* Copyright (C) 2023, SAP SE and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.util;
import java.text.MessageFormat;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jgit.internal.JGitText;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A hook registered as a JVM shutdown hook managing a set of objects needing
* cleanup during JVM shutdown. See {@link Runtime#addShutdownHook}.
*/
@SuppressWarnings("ImmutableEnumChecker")
public enum ShutdownHook {
/**
* Singleton
*/
INSTANCE;
/**
* Object that needs to cleanup on JVM shutdown.
*/
public interface Listener {
/**
* Cleanup resources when JVM shuts down, called from JVM shutdown hook.
* <p>
* Implementations should be coded defensively
* <ul>
* <li>they should finish their work quickly
* <li>they should be written to be thread-safe and to avoid deadlocks
* insofar as possible
* <li>they should not rely blindly upon services that may have
* registered their own shutdown hooks and therefore may themselves be
* in the process of shutting down
* <li>attempts to use other thread-based services may lead to
* deadlocks.
* </ul>
* See {@link Runtime#addShutdownHook} for more details.
*/
public void onShutdown();
}
private static final Logger LOG = LoggerFactory
.getLogger(ShutdownHook.class);
private final Set<Listener> listeners = ConcurrentHashMap.newKeySet();
private volatile boolean shutdownInProgress;
private ShutdownHook() {
try {
Runtime.getRuntime().addShutdownHook(new Thread(this::cleanup));
} catch (IllegalStateException e) {
// ignore - the VM is already shutting down
}
}
private void cleanup() {
shutdownInProgress = true;
ExecutorService runner = Executors.newWorkStealingPool();
try {
runner.submit(() -> {
this.doCleanup();
return null;
}).get(30L, TimeUnit.SECONDS);
} catch (RejectedExecutionException | InterruptedException
| ExecutionException | TimeoutException e) {
// message isn't localized since during shutdown there's no
// guarantee which classes are still loaded
LOG.error("Cleanup during JVM shutdown failed", e); //$NON-NLS-1$
}
runner.shutdownNow();
}
private void doCleanup() {
listeners.parallelStream().forEach(this::notify);
}
private void notify(Listener l) {
LOG.debug(JGitText.get().shutdownCleanup, l);
try {
l.onShutdown();
} catch (RuntimeException e) {
LOG.error(MessageFormat.format(
JGitText.get().shutdownCleanupListenerFailed, l), e);
}
}
/**
* Register object that needs cleanup during JVM shutdown if it is not
* already registered. Registration is disabled when JVM shutdown is already
* in progress.
*
* @param l
* the object to call {@link Listener#onShutdown} on when JVM
* shuts down
* @return {@code true} if this object has been registered
*/
public boolean register(Listener l) {
if (shutdownInProgress) {
return listeners.contains(l);
}
LOG.debug("register {} with shutdown hook", l); //$NON-NLS-1$
listeners.add(l);
return true;
}
/**
* Unregister object that no longer needs cleanup during JVM shutdown if it
* is still registered. Unregistration is disabled when JVM shutdown is
* already in progress.
*
* @param l
* the object registered to be notified for cleanup when the JVM
* shuts down
* @return {@code true} if this object is no longer registered
*/
public boolean unregister(Listener l) {
if (shutdownInProgress) {
return !listeners.contains(l);
}
LOG.debug("unregister {} from shutdown hook", l); //$NON-NLS-1$
listeners.remove(l);
return true;
}
/**
* Whether a JVM shutdown is in progress
*
* @return {@code true} if a JVM shutdown is in progress
*/
public boolean isShutdownInProgress() {
return shutdownInProgress;
}
}