| /* |
| * 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) { |
| LOG.error(JGitText.get().shutdownCleanupFailed, e); |
| } |
| 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; |
| } |
| } |