aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/internal/util
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/internal/util')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java127
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java152
3 files changed, 400 insertions, 0 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java
new file mode 100644
index 0000000000..29ed7564d3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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 org.eclipse.jgit.internal.JGitText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class that is registered as an OSGi service via the manifest. If JGit runs
+ * in OSGi, OSGi will instantiate a singleton as soon as the bundle is activated
+ * since this class is an immediate OSGi component with no dependencies. OSGi
+ * will then call its {@link #start()} method. If JGit is not running in OSGi,
+ * {@link #getInstance()} will lazily create an instance.
+ * <p>
+ * An OSGi-created {@link CleanupService} will run the registered cleanup when
+ * the {@code org.eclipse.jgit} bundle is deactivated. A lazily created instance
+ * will register the cleanup as a JVM shutdown hook.
+ * </p>
+ */
+public final class CleanupService {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(CleanupService.class);
+
+ private static final Object LOCK = new Object();
+
+ private static CleanupService INSTANCE;
+
+ private final boolean isOsgi;
+
+ private JGitText jgitText;
+
+ private Runnable cleanup;
+
+ /**
+ * Public component constructor for OSGi DS. Do <em>not</em> call this
+ * explicitly! (Unfortunately this constructor must be public because of
+ * OSGi requirements.)
+ */
+ public CleanupService() {
+ this.isOsgi = true;
+ setInstance(this);
+ }
+
+ private CleanupService(boolean isOsgi) {
+ this.isOsgi = isOsgi;
+ }
+
+ private static void setInstance(CleanupService service) {
+ synchronized (LOCK) {
+ INSTANCE = service;
+ }
+ }
+
+ /**
+ * Obtains the singleton instance of the {@link CleanupService} that knows
+ * whether or not it is running on OSGi.
+ *
+ * @return the {@link CleanupService} singleton instance
+ */
+ public static CleanupService getInstance() {
+ synchronized (LOCK) {
+ if (INSTANCE == null) {
+ INSTANCE = new CleanupService(false);
+ }
+ return INSTANCE;
+ }
+ }
+
+ void start() {
+ // Nothing to do
+ }
+
+ void register(Runnable cleanUp) {
+ if (isOsgi) {
+ cleanup = cleanUp;
+ } else {
+ // Ensure the JGitText class is loaded. Depending on the framework
+ // JGit runs in, it may not be possible anymore to load classes when
+ // the hook runs. For instance when run in a maven plug-in: the
+ // Plexus class world that loaded JGit may already have been
+ // disposed by the time the JVM shutdown hook runs when the whole
+ // maven build terminates.
+ jgitText = JGitText.get();
+ assert jgitText != null;
+ try {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ cleanUp.run();
+ // Don't catch exceptions; let the JVM do the problem
+ // reporting.
+ } finally {
+ jgitText = null;
+ }
+ }));
+ } catch (IllegalStateException e) {
+ // Ignore -- the JVM is already shutting down.
+ }
+ }
+ }
+
+ void shutDown() {
+ if (isOsgi && cleanup != null) {
+ Runnable r = cleanup;
+ cleanup = null;
+ try {
+ r.run();
+ } catch (RuntimeException e) {
+ LOG.error(JGitText.get().shutdownCleanupFailed, e);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
new file mode 100644
index 0000000000..270b760562
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.lang.ref.SoftReference;
+import java.util.Optional;
+
+/**
+ * Interface representing a reference to a potentially mutable optional object.
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ * @since 6.7
+ */
+public interface Optionally<T> {
+ /**
+ * A permanently empty Optionally
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ */
+ public class Empty<T> implements Optionally<T> {
+ @Override
+ public void clear() {
+ // empty
+ }
+
+ @Override
+ public Optional<T> getOptional() {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * A permanent(hard) reference to an object
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ */
+ public class Hard<T> implements Optionally<T> {
+ /**
+ * The mutable optional object
+ */
+ protected Optional<T> optional;
+
+ /**
+ * @param element
+ * the mutable optional object
+ */
+ public Hard(T element) {
+ optional = Optional.ofNullable(element);
+ }
+
+ @Override
+ public void clear() {
+ optional = Optional.empty();
+ }
+
+ @Override
+ public Optional<T> getOptional() {
+ return optional;
+ }
+ }
+
+ /**
+ * A SoftReference Optionally
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ */
+ public class Soft<T> extends SoftReference<T> implements Optionally<T> {
+ /**
+ * @param t
+ * the mutable optional object
+ */
+ public Soft(T t) {
+ super(t);
+ }
+
+ @Override
+ public Optional<T> getOptional() {
+ return Optional.ofNullable(get());
+ }
+ }
+
+ /**
+ * The empty Optionally
+ */
+ public static final Optionally<?> EMPTY = new Empty<>();
+
+ /**
+ * Get empty Optionally
+ *
+ * @param <T>
+ * type of the empty Optionally
+ * @return the empty Optionally
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Optionally<T> empty() {
+ return (Optionally<T>) EMPTY;
+ }
+
+ /**
+ * Clear the object
+ */
+ void clear();
+
+ /**
+ * Get an Optional representing the current state of the object
+ *
+ * @return the mutable optional object
+ */
+ Optional<T> getOptional();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java
new file mode 100644
index 0000000000..f6b4723489
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java
@@ -0,0 +1,152 @@
+/*
+ * 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.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The singleton {@link ShutdownHook} provides a means to register
+ * {@link Listener}s that are run when JGit is uninstalled, either
+ * <ul>
+ * <li>in an OSGi framework when this bundle is deactivated, or</li>
+ * <li>otherwise, when the JVM as a whole shuts down.</li>
+ * </ul>
+ */
+@SuppressWarnings("ImmutableEnumChecker")
+public enum ShutdownHook {
+ /**
+ * Singleton
+ */
+ INSTANCE;
+
+ /**
+ * Object that needs to cleanup on shutdown.
+ */
+ public interface Listener {
+ /**
+ * Cleanup resources when JGit is shut down.
+ * <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 final AtomicBoolean shutdownInProgress = new AtomicBoolean();
+
+ private ShutdownHook() {
+ CleanupService.getInstance().register(this::cleanup);
+ }
+
+ private void cleanup() {
+ if (!shutdownInProgress.getAndSet(true)) {
+ ExecutorService runner = Executors.newWorkStealingPool();
+ try {
+ runner.submit(() -> {
+ this.doCleanup();
+ return null;
+ }).get(30L, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException
+ | TimeoutException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } finally {
+ 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 JGit shutdown if it is not
+ * already registered. Registration is disabled when JGit shutdown is
+ * already in progress.
+ *
+ * @param l
+ * the object to call {@link Listener#onShutdown} on when JGit
+ * shuts down
+ * @return {@code true} if this object has been registered
+ */
+ public boolean register(Listener l) {
+ if (shutdownInProgress.get()) {
+ 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 JGit shutdown if it
+ * is still registered. Unregistration is disabled when JGit 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.get()) {
+ return !listeners.contains(l);
+ }
+ LOG.debug("unregister {} from shutdown hook", l); //$NON-NLS-1$
+ listeners.remove(l);
+ return true;
+ }
+
+ /**
+ * Whether a JGit shutdown is in progress
+ *
+ * @return {@code true} if a JGit shutdown is in progress
+ */
+ public boolean isShutdownInProgress() {
+ return shutdownInProgress.get();
+ }
+}