This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 416551cdc75295e253cfcb1445618d8bb80fc9dc Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Dec 5 16:49:32 2024 +0100 Add a public `Configuration.shutdown()` method. --- .../factory/ConcurrentAuthorityFactory.java | 4 +-- .../main/org/apache/sis/setup/Configuration.java | 28 +++++++++++++++- .../main/org/apache/sis/system/Shutdown.java | 38 +++++++++++----------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java index 3e675a5408..ec51343a7d 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java @@ -1669,7 +1669,7 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa /** String representation used by {@link CacheRecord}. */ @Override public String toString() { - final StringBuilder buffer = new StringBuilder(); + final var buffer = new StringBuilder(); if (type instanceof Class<?>) { buffer.append("Code[“").append(code); if (buffer.length() > 15) { // Arbitrary limit in string length. @@ -2090,7 +2090,7 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa */ private static <DAO extends GeodeticAuthorityFactory> List<DAO> clear(final Deque<DataAccessRef<DAO>> availableDAOs) { assert Thread.holdsLock(availableDAOs); - final List<DAO> factories = new ArrayList<>(availableDAOs.size()); + final var factories = new ArrayList<DAO>(availableDAOs.size()); DataAccessRef<DAO> dao; while ((dao = availableDAOs.pollFirst()) != null) { factories.add(dao.factory); diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/Configuration.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/Configuration.java index e777d74735..a0f0fa7618 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/Configuration.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/setup/Configuration.java @@ -23,6 +23,9 @@ import java.util.function.Supplier; import java.util.concurrent.TimeUnit; import javax.sql.DataSource; import java.sql.SQLException; +import org.apache.sis.system.Shutdown; +import org.apache.sis.system.SystemListener; +import org.apache.sis.util.logging.Logging; import org.apache.sis.util.privy.MetadataServices; @@ -54,7 +57,7 @@ import org.apache.sis.util.privy.MetadataServices; * </ul> * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.5 * @since 1.0 */ public final class Configuration { @@ -146,4 +149,27 @@ public final class Configuration { public void setDatabase(final Supplier<DataSource> source) { MetadataServices.getInstance().setDataSource(Objects.requireNonNull(source)); } + + /** + * Shutdowns the Apache SIS library. + * This method close database connections and stops daemon threads. + * <strong>The Apache SIS library shall not be used anymore after this method call.</strong> + * Any use of Apache SIS after this method call may cause undermined behavior. + * In particular, it may cause memory leaks. + * + * <h4>When to use</h4> + * This method should generally <strong>not</strong> be invoked, because Apache SIS registers itself + * a {@linkplain Runtime#addShutdownHook(Thread) shutdown hook} to the Java Virtual Machine. + * This method may be useful in environments that do not allow the use of shutdown hooks, + * or when waiting for the <abbr>JVM</abbr> shutdown is overly conservative. + * + * @since 1.5 + */ + public void shutdown() { + try { + Shutdown.stop(Configuration.class); + } catch (Exception e) { + Logging.unexpectedException(SystemListener.LOGGER, Configuration.class, "stop", e); + } + } } diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Shutdown.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Shutdown.java index d926758b75..6015812e60 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Shutdown.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/system/Shutdown.java @@ -16,8 +16,8 @@ */ package org.apache.sis.system; -import java.util.List; -import java.util.ArrayList; +import java.util.Deque; +import java.util.ArrayDeque; import java.util.Objects; import java.util.concurrent.Callable; import org.apache.sis.util.logging.Logging; @@ -44,7 +44,7 @@ public final class Shutdown extends Thread { /** * The resources to dispose. Most recently added resources are last. */ - private static final List<Callable<?>> resources = new ArrayList<>(); + private static final Deque<Callable<?>> resources = new ArrayDeque<>(); /** * Creates the thread to be executed at shutdown time. @@ -83,7 +83,7 @@ public final class Shutdown extends Thread { /** * Invoked if the Apache SIS library is executed from an environment that provide its own shutdown hook. - * Example of such environments are OSG and servlet containers. In such case, the shutdown hook will not + * Example of such environments are OSGi and servlet containers. In such case, the shutdown hook will not * be registered to the JVM {@link Runtime}. * * @param env a description of the container. Should contain version information if possible. @@ -108,10 +108,13 @@ public final class Shutdown extends Thread { public static void register(final Callable<?> resource) { synchronized (resources) { assert !resources.contains(resource); - resources.add(resource); - if (hook == null && container == null) { + resources.addLast(resource); + if (hook == null && container == null) try { hook = new Shutdown(); Runtime.getRuntime().addShutdownHook(hook); + } catch (SecurityException e) { + hook = null; + Logging.recoverableException(SystemListener.LOGGER, Shutdown.class, "register", e); } } } @@ -121,26 +124,22 @@ public final class Shutdown extends Thread { */ private static void removeShutdownHook() { assert Thread.holdsLock(resources); - if (hook != null) { + if (hook != null) try { Runtime.getRuntime().removeShutdownHook(hook); hook = null; + } catch (SecurityException e) { + Logging.recoverableException(SystemListener.LOGGER, Shutdown.class, "removeShutdownHook", e); } } /** * Unregisters a code from execution at JVM shutdown time. - * This method uses identity comparison (it does not use {@link Object#equals(Object)}). * * @param resource the resource disposal to cancel execution. */ public static void unregister(final Callable<?> resource) { synchronized (resources) { - for (int i = resources.size(); --i>=0;) { // Check most recently added resources first. - if (resources.get(i) == resource) { - resources.remove(i); - break; - } - } + resources.removeLastOccurrence(resource); } } @@ -148,8 +147,8 @@ public final class Shutdown extends Thread { * Unregisters the supervisor MBean, executes the disposal tasks and shutdowns the {@code org.apache.sis.util} threads. * This method may be invoked at JVM shutdown, or if a container like OSGi is unloaded the SIS library. * - * @param caller the class invoking this method, to be used only for logging purpose, or {@code null} - * if the logging system is not available anymore (i.e. the JVM itself is shutting down). + * @param caller the class invoking this method (used for logging purpose), or {@code null} if this method + * is invoked at JVM shutdown time (in which case the logging system is not available anymore). * @throws Exception if an error occurred during unregistration of the supervisor MBean * or during a resource disposal. */ @@ -157,6 +156,7 @@ public final class Shutdown extends Thread { synchronized (resources) { container = "Shutdown"; if (caller != null) { + // Remove only if JVM shutdown is not already in progress. removeShutdownHook(); } } @@ -176,9 +176,9 @@ public final class Shutdown extends Thread { * invoke Shutdown.[un]register(Disposable), but we nevertheless make the loop robust to this case. */ synchronized (resources) { - int i; - while ((i = resources.size()) != 0) try { // In case run() modifies the resources list. - resources.remove(i - 1).call(); // Dispose most recently added resources first. + Callable<?> r; + while ((r = resources.pollLast()) != null) try { + r.call(); } catch (Exception e) { if (exception != null) { e.addSuppressed(exception);