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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new f2101a867d Rework the way that native libraries are loaded for 
avoiding to destroy the same library many times when the library was loaded 
from a filename. This is needed for avoiding a JVM crash if the 
`GDALStoreProvider(Path)` constructor was invoked many times with the same path.
f2101a867d is described below

commit f2101a867d6c68b151c982b10892c6546088200f
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Feb 20 01:06:37 2026 +0100

    Rework the way that native libraries are loaded for avoiding to destroy the 
same library many times when the library was loaded from a filename.
    This is needed for avoiding a JVM crash if the `GDALStoreProvider(Path)` 
constructor was invoked many times with the same path.
---
 .../sis/util/collection/WeakValueHashMap.java      |   6 +-
 .../main/org/apache/sis/storage/gdal/GDAL.java     | 147 +++------------
 .../apache/sis/storage/gdal/GDALStoreProvider.java | 126 ++++++++++---
 .../org/apache/sis/storage/gdal/package-info.java  |   2 +-
 .../apache/sis/storage/panama/LibraryLoader.java   | 192 +++++++++----------
 .../sis/storage/panama/LibraryReference.java       | 207 +++++++++++++++++++++
 .../apache/sis/storage/panama/LibraryStatus.java   |  51 ++++-
 .../apache/sis/storage/panama/NativeFunctions.java |  91 ++++-----
 8 files changed, 528 insertions(+), 294 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
index cf5c57da28..bd1878a120 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/WeakValueHashMap.java
@@ -45,8 +45,8 @@ import static org.apache.sis.util.collection.WeakEntry.*;
  * as soon as the garbage collector determines that they are no longer in use. 
If caching
  * service are wanted, or if concurrency are wanted, consider using {@link 
Cache} instead.</p>
  *
- * <p>This class is convenient for avoiding the creation of duplicated 
elements, as in the
- * example below:</p>
+ * <p>This class is convenient for avoiding the creation of duplicated 
elements,
+ * as in the example below:</p>
  *
  * {@snippet lang="java" :
  *     K key = ...
@@ -61,7 +61,7 @@ import static org.apache.sis.util.collection.WeakEntry.*;
  *     }
  *
  * In the above example, the calculation of a new value needs to be fast 
because it is performed inside a synchronized
- * statement blocking all other access to the map. This is okay if that 
particular {@code WeakValueHashMap} instance
+ * statement blocking all other accesses to the map. This is okay if that 
particular {@code WeakValueHashMap} instance
  * is not expected to be used in a highly concurrent environment.
  *
  * <p>{@code WeakValueHashMap} works with array keys as one would expect. For 
example, arrays of {@code int[]} are
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
index 7a29722ce4..fdd2a7601e 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java
@@ -16,31 +16,25 @@
  */
 package org.apache.sis.storage.gdal;
 
-import java.nio.file.Path;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Optional;
 import java.util.NoSuchElementException;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
+import java.util.logging.Logger;
 import java.lang.foreign.Arena;
 import java.lang.foreign.ValueLayout;
-import java.lang.foreign.SymbolLookup;
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.FunctionDescriptor;
 import java.lang.invoke.MethodHandle;
-import org.apache.sis.util.internal.shared.Constants;
-import org.apache.sis.util.logging.Logging;
-import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.panama.LibraryLoader;
 import org.apache.sis.storage.panama.LibraryStatus;
 import org.apache.sis.storage.panama.NativeFunctions;
-import org.apache.sis.storage.panama.Resources;
 
 
 /**
  * Handlers to <abbr>GDAL</abbr> native functions needed by this package.
- * The <abbr>GDAL</abbr> library can be unloaded by invoking {@link #run()}.
+ * The <abbr>GDAL</abbr> library may be unloaded with the {@link 
GDALStoreProvider}
+ * will be garbage collected.
  *
  * @author  Quentin Bialota (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -48,21 +42,6 @@ import org.apache.sis.storage.panama.Resources;
  * @see <a href="https://gdal.org/en/latest/api/raster_c_api.html";>GDAL Raster 
C API</a>
  */
 final class GDAL extends NativeFunctions {
-    /**
-     * The global instance, created when first needed.
-     * This field shall be read and updated in a synchronized block.
-     * It may be reset to {@code null} if <abbr>GDAL</abbr> reported a fatal 
error.
-     *
-     * @see #global(boolean)
-     */
-    private static GDAL global;
-
-    /**
-     * Whether an error occurred during initialization of {@link #global}.
-     * Shall be read and updated in the same synchronization block as {@link 
#global}.
-     */
-    private static LibraryStatus globalStatus;
-
     /**
      * <abbr>GDAL</abbr> {@code const char 
*GDALGetDriverLongName(GDALDriverH)}.
      * Returns the long name of a driver. For the GeoTIFF driver, this is 
"GeoTIFF"
@@ -291,12 +270,12 @@ final class GDAL extends NativeFunctions {
     private OGR ogr;
 
     /**
-     * Creates the handles for all <abbr>GDAL</abbr> functions which will be 
needed.
+     * Creates the handles for most <abbr>GDAL</abbr> functions which will be 
needed.
      *
      * @param  loader  the object used for loading the library.
      * @throws NoSuchElementException if a <abbr>GDAL</abbr> function has not 
been found in the library.
      */
-    private GDAL(final LibraryLoader<GDAL> loader) {
+    private GDAL(final LibraryLoader<GDAL, GDALStoreProvider> loader) {
         super(loader);
 
         // A few frequently-used function signatures.
@@ -418,86 +397,17 @@ final class GDAL extends NativeFunctions {
 
         // Initialize GDAL after we found all functions.
         if (!invoke("GDALAllRegister")) {
-            log(GDAL.class, "<init>", Resources.forLocale(null)
-                    .createLogRecord(Level.WARNING, 
Resources.Keys.CannotInitialize_1, Constants.GDAL));
+            GDALStoreProvider.log(LibraryStatus.CANNOT_INITIALIZE, true, null);
         }
     }
 
     /**
      * Returns the helper class for loading the <abbr>GDAL</abbr> library.
-     * If {@code def} is true, then this method tries to load the library
-     * now and stores the result in {@link #global} and {@link #globalStatus}.
      *
-     * @param  now  whether this method is invoked for the default (global) 
library.
-     *         In such case, the caller must be synchronized and {@link 
#global} must be initially null.
      * @return the library loader for <abbr>GDAL</abbr>.
      */
-    private static LibraryLoader<GDAL> load(final boolean now) {
-        final var loader = new LibraryLoader<>(GDAL::new);
-        if (now) {
-            try {
-                global = loader.global("gdal");
-            } finally {
-                globalStatus = loader.status();
-            }
-            if (global != null) {
-                if (GDALStoreProvider.LOGGER.isLoggable(Level.CONFIG)) {
-                    global.version("--version").ifPresent((version) -> {
-                        log(GDAL.class, "<init>", new LogRecord(Level.CONFIG, 
version));
-                    });
-                }
-            }
-        }
-        return loader;
-    }
-
-    /**
-     * Loads the <abbr>GDAL</abbr> library from the given file.
-     * Callers should register the returned instance in a {@link 
java.lang.ref.Cleaner}.
-     *
-     * @param  library  the library to load.
-     * @return handles to native functions needed by this module.
-     * @throws IllegalArgumentException if the GDAL library has not been found.
-     * @throws NoSuchElementException if a <abbr>GDAL</abbr> function has not 
been found in the library.
-     * @throws IllegalCallerException if this Apache SIS module is not 
authorized to call native methods.
-     */
-    static GDAL load(final Path library) {
-        return load(false).load(library);
-    }
-
-    /**
-     * Returns an instance using the <abbr>GDAL</abbr> library loaded from the 
default library path.
-     * The handles are valid for the Java Virtual Machine lifetime, i.e. it 
uses the global arena.
-     * If this method has already been invoked, this method returns the 
previously created instance.
-     *
-     * <p>If the <abbr>GDAL</abbr> library is not found, the current default 
is {@link SymbolLookup#loaderLookup()}
-     * for allowing users to invoke {@link System#loadLibrary(String)} as a 
fallback. This policy may be revised in
-     * any future version of Apache <abbr>SIS</abbr>.</p>
-     *
-     * @return handles to native functions needed by this module.
-     * @throws DataStoreException if the native library has not been found or 
if SIS is not allowed to call
-     *         native functions, and {@code onError} is null.
-     */
-    static synchronized GDAL global() throws DataStoreException {
-        if (globalStatus == null) {
-            load(true).validate(Constants.GDAL);
-        }
-        globalStatus.report(Constants.GDAL, null);
-        return global;
-    }
-
-    /**
-     * Same as {@link #global}, but logs a warning instead of throwing an 
exception in case of error.
-     *
-     * @param  classe  the class which is invoking this method (for logging 
purpose).
-     * @param  method  the name of the method which is invoking this method 
(for logging purpose).
-     * @return handles to native functions needed by this module, or empty if 
not available.
-     */
-    static synchronized Optional<GDAL> tryGlobal(final Class<?> classe, final 
String method) {
-        if (globalStatus == null) {
-            load(true).getError(Constants.GDAL).ifPresent((record) -> 
log(classe, method, record));
-        }
-        return Optional.ofNullable(global);
+    static LibraryLoader<GDAL, GDALStoreProvider> loader() {
+        return new LibraryLoader<>(GDAL::new, GDALStoreProvider::new, 
GDALStoreProvider.class);
     }
 
     /**
@@ -505,7 +415,7 @@ final class GDAL extends NativeFunctions {
      * The handler is set by a call to {@code CPLErrorHandler 
CPLSetErrorHandler(CPLErrorHandler)} where
      * {@code CPLErrorHandler} is {@code void (*CPLErrorHandler)(CPLErr, 
CPLErrorNum, const char*)}.
      *
-     * <p><b>The error handler is valid only during the lifetime of the 
{@linkplain #arena() arena}.</b>
+     * <p><b>The error handler is valid only during the lifetime of the 
{@linkplain #libraryArena}.</b>
      * The error handle shall be uninstalled before the arena is closed.</p>
      *
      * @param  target  the function to set as an error handler, or {@link 
MemorySegment#NULL} for the GDAL default.
@@ -523,7 +433,7 @@ final class GDAL extends NativeFunctions {
         }
         if (target == null) {
             target = linker.upcallStub(ErrorHandler.getMethod(),
-                    FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT, 
ValueLayout.JAVA_INT, ValueLayout.ADDRESS), arena());
+                    FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT, 
ValueLayout.JAVA_INT, ValueLayout.ADDRESS), libraryArena);
         }
         final MethodHandle handle = linker.downcallHandle(setter,
                 FunctionDescriptor.of(ValueLayout.ADDRESS, 
ValueLayout.ADDRESS));
@@ -534,17 +444,6 @@ final class GDAL extends NativeFunctions {
         }
     }
 
-    /**
-     * Logs the given record as if was produced by the {@link 
GDALStoreProvider}, which is the public class.
-     *
-     * @param  classe  the class which is invoking this method (for logging 
purpose).
-     * @param  method  the name of the method which is invoking this method 
(for logging purpose).
-     * @param  record  the error to log.
-     */
-    private static void log(final Class<?> classe, final String method, final 
LogRecord record) {
-        Logging.completeAndLog(GDALStoreProvider.LOGGER, classe, method, 
record);
-    }
-
     /**
      * Returns the <abbr>GDAL</abbr> version. The request can be:
      *
@@ -618,18 +517,22 @@ final class GDAL extends NativeFunctions {
     }
 
     /**
-     * Unloads the <abbr>GDAL</abbr> library. If the arena is global,
-     * then this method should not be invoked before <abbr>JVM</abbr> shutdown.
-     * Otherwise, this method is invoked when {@link GDALStoreProvider} is 
garbage-collected.
+     * Closes the <abbr>GDAL</abbr> library. This method is invoked 
automatically when the last
+     * {@link NativeFunctions} instance using the native library has been 
garbage-collected.
      */
     @Override
-    public void run() {
-        try {
-            // Clear the error handler because the arena will be closed.
-            setErrorHandler(MemorySegment.NULL);
-            invoke("GDALDestroy");
-        } finally {
-            super.run();
-        }
+    protected void destroy() {
+        // Clear the error handler because `libraryArena` will be closed.
+        setErrorHandler(MemorySegment.NULL);
+        invoke("GDALDestroy");
+    }
+
+    /**
+     * Returns the logger where to report warnings.
+     * This is used if an exception occurred during the execution of {@link 
#destroy()}.
+     */
+    @Override
+    protected Logger getLogger() {
+        return GDALStoreProvider.LOGGER;
     }
 }
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java
index 0974d6f407..b86f176e66 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDALStoreProvider.java
@@ -18,11 +18,11 @@ package org.apache.sis.storage.gdal;
 
 import java.util.List;
 import java.util.Optional;
-import java.util.NoSuchElementException;
 import java.util.logging.Logger;
 import java.util.function.Function;
 import java.time.LocalDate;
 import java.nio.file.Path;
+import java.io.IOException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.ParameterValueGroup;
@@ -42,7 +42,6 @@ import org.apache.sis.io.stream.InternalOptionKey;
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.setup.OptionKey;
-import org.apache.sis.system.Cleaners;
 import org.apache.sis.util.Version;
 import org.apache.sis.util.internal.shared.Constants;
 import org.apache.sis.util.collection.TreeTable;
@@ -60,7 +59,7 @@ import org.apache.sis.util.collection.TreeTable;
  *
  * @author  Quentin Bialota (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.7
  * @since   1.5
  */
 @StoreMetadata(formatName    = Constants.GDAL,
@@ -106,35 +105,110 @@ public class GDALStoreProvider extends DataStoreProvider 
{
     }
 
     /**
-     * Handles to <abbr>GDAL</abbr> native functions, or {@code null} for 
global.
-     * Can also be reset to {@code null} if <abbr>GDAL</abbr> reported a fatal 
error.
+     * Handles to <abbr>GDAL</abbr> native functions, or {@code null} if none.
+     * May also be reset to {@code null} if <abbr>GDAL</abbr> reports a fatal 
error.
      */
     private GDAL nativeFunctions;
 
     /**
-     * The status of {@link #nativeFunctions}, or {@code null} if the global 
set of functions is used.
+     * The status of {@link #nativeFunctions}.
      */
     private LibraryStatus status;
 
+    /**
+     * The unique instance using the global arena, created when first needed.
+     * It is important to have only one {@link GDAL} instance, otherwise 
duplicated
+     * calls to {@code GDALDestroy} may crash the <abbr>JVM</abbr>.
+     */
+    private static GDALStoreProvider GLOBAL;
+
     /**
      * Creates a new provider which will load the <abbr>GDAL</abbr> library 
from the default library path.
+     *
+     * <p>This constructor is public for compatibility with usage of 
<abbr>SIS</abbr> on the class-path.
+     * It may become protected in a future version of Apache <abbr>SIS</abbr>, 
if the use of module-path
+     * become mandatory.</p>
+     *
+     * @throws DataStoreException if the <abbr>GDAL</abbr> library has not 
been found
+     *         or if this Apache SIS module is not authorized to call native 
methods.
+     *
+     * @see #provider()
+     */
+    public GDALStoreProvider() throws DataStoreException {
+        final GDALStoreProvider global = provider();
+        nativeFunctions = global.nativeFunctions;
+        status = global.status;
+    }
+
+    /**
+     * Invoked when we failed to load the <abbr>GDAL</abbr> library.
+     *
+     * @param  status  reason why <abbr>GDAL</abbr> could not be loaded.
+     */
+    private GDALStoreProvider(final LibraryStatus status) {
+        this.status = status;
+    }
+
+    /**
+     * Creates a provider which uses the given handlers to native functions.
+     *
+     * @param  nativeFunctions  handlers of <abbr>GDAL</abbr> functions.
+     */
+    GDALStoreProvider(final GDAL nativeFunctions) {
+        this.nativeFunctions = nativeFunctions;
+        status = LibraryStatus.LOADED;
+    }
+
+    /**
+     * Returns a provider which will load the <abbr>GDAL</abbr> library from 
the default library path.
+     * If the <abbr>GDAL</abbr> library is not found on the library path, then 
this method searches in
+     * the libraries loaded with {@link System#loadLibrary(String)}.
+     *
+     * @return the default data store provider using the <abbr>GDAL</abbr> 
library.
+     * @throws DataStoreException if the <abbr>GDAL</abbr> library has not 
been found
+     *         or if this Apache SIS module is not authorized to call native 
methods.
+     *
+     * @since 1.7
      */
-    public GDALStoreProvider() {
+    public static synchronized GDALStoreProvider provider() throws 
DataStoreException {
+        if (GLOBAL == null) {
+            final var loader = GDAL.loader();
+            try {
+                GLOBAL = loader.global("gdal");
+            } catch (Throwable e) {
+                GLOBAL = new GDALStoreProvider(loader.status());
+                log(GLOBAL.status, false, e);
+            }
+        }
+        GLOBAL.status.throwIfFailed(Constants.GDAL);     // Should never 
return if `nativeFunctions` is null.
+        return GLOBAL;
     }
 
     /**
      * Creates a new provider which will load the <abbr>GDAL</abbr> library 
from the specified file.
-     * The library will be unloaded when this provider will be 
garbage-collected.
+     * This method may return an existing provider previously created for the 
same path.
+     * When the returned provider will be garbage collected, the following 
cleanup actions will be performed:
+     *
+     * <ul>
+     *   <li>The {@code GDALDestroy} native function will be invoked if this 
{@code GDALStoreProvider}
+     *       instance was the last one using the specified library.</li>
+     *   <li>Then the library will be unloaded. Note that the <abbr>OS</abbr> 
may keep the library
+     *       in memory if it is still used by another {@code 
GDALStoreProvider} instance.</li>
+     * </ul>
      *
      * @param  library  path to the library to load.
-     * @throws IllegalArgumentException if the GDAL library has not been found.
-     * @throws NoSuchElementException if a <abbr>GDAL</abbr> function has not 
been found in the library.
-     * @throws IllegalCallerException if this Apache SIS module is not 
authorized to call native methods.
+     * @return data store provider using the <abbr>GDAL</abbr> library at the 
given type.
+     * @throws DataStoreException if the <abbr>GDAL</abbr> library has not 
been found
+     *         or if this Apache SIS module is not authorized to call native 
methods.
+     *
+     * @since 1.7
      */
-    @SuppressWarnings("this-escape")
-    public GDALStoreProvider(final Path library) {
-        Cleaners.SHARED.register(this, nativeFunctions = GDAL.load(library));
-        status = LibraryStatus.LOADED;
+    public static GDALStoreProvider provider(final Path library) throws 
DataStoreException {
+        try {
+            return GDAL.loader().load(library);
+        } catch (IOException | RuntimeException e) {
+            throw new DataStoreException(e);
+        }
     }
 
     /**
@@ -144,25 +218,19 @@ public class GDALStoreProvider extends DataStoreProvider {
      * @throws DataStoreException if the native library is not available.
      */
     final synchronized GDAL GDAL() throws DataStoreException {
-        if (status == null) {
-            return GDAL.global();       // Fetch each time (no cache) because 
may have changed outside this class.
-        }
-        status.report(Constants.GDAL, null);        // Should never return if 
`nativeFunctions` is null.
+        status.throwIfFailed(Constants.GDAL);       // Should never return if 
`nativeFunctions` is null.
         return nativeFunctions;
     }
 
     /**
      * Tries to load <abbr>GDAL</abbr> if not already done, without throwing 
an exception in case of error.
-     * Instead, the error is logged and {@code null} is returned. This is used 
for probing.
+     * Instead, the error is logged and an empty value is returned. This is 
used for probing.
      *
      * @param  classe  the class which is invoking this method (for logging 
purpose).
      * @param  method  the name of the method which is invoking this method 
(for logging purpose).
      * @return the set of native functions, or {@code null} if not available.
      */
     final synchronized Optional<GDAL> tryGDAL(final Class<?> classe, final 
String method) {
-        if (status == null) {
-            return GDAL.tryGlobal(classe, method);
-        }
         return Optional.ofNullable(nativeFunctions);
     }
 
@@ -317,6 +385,18 @@ public class GDALStoreProvider extends DataStoreProvider {
         return open(connector);
     }
 
+    /**
+     * Logs a record at the given level if the native library is not available.
+     * This method pretends that the warning come from a {@code provider()} 
method.
+     *
+     * @param  status    the <abbr>GDAL</abbr> status.
+     * @param  warning   whether to use the warning level. Otherwise, uses the 
configuration level.
+     * @param  cause     the cause of the error, or {@code null} if none.
+     */
+    static void log(final LibraryStatus status, final boolean warning, final 
Throwable e) {
+        status.log(LOGGER, warning, GDALStoreProvider.class, Constants.GDAL, 
e);
+    }
+
     /**
      * Returns the logger used by <abbr>GDAL</abbr> stores.
      */
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
index b5f6f0df47..65b41c9252 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/package-info.java
@@ -47,7 +47,7 @@
  * @author  Quentin Bialota (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.6
+ * @version 1.7
  * @since   1.5
  */
 package org.apache.sis.storage.gdal;
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
index 423e1b93d4..493bf24df7 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryLoader.java
@@ -16,36 +16,38 @@
  */
 package org.apache.sis.storage.panama;
 
+import java.io.IOException;
 import java.nio.file.Path;
-import java.util.Optional;
+import java.nio.file.Files;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.NoSuchElementException;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import java.util.function.Function;
 import java.lang.foreign.Arena;
 import java.lang.foreign.SymbolLookup;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.system.Shutdown;
+import org.apache.sis.storage.DataStoreProvider;
 
 
 /**
  * A helper class for loading a native library.
- * This object exists only for the duration of {@link NativeFunctions} 
construction.
+ * This object exists only for the duration of {@link NativeFunctions} 
construction
+ * followed by the construction of the service ({@link DataStoreProvider}) 
that uses
+ * the native functions.
  *
  * @param <F> the class of {@code NativeFunctions} to construct using this 
loader.
+ * @param <S> type of service which will use the native functions.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class LibraryLoader<F extends NativeFunctions> {
+public final class LibraryLoader<F extends NativeFunctions, S extends 
DataStoreProvider> {
     /**
      * Value to assign to {@link NativeFunctions#libraryName}.
      */
     String filename;
 
     /**
-     * Value to assign to {@link NativeFunctions#arena}.
+     * Value to assign to {@link NativeFunctions#libraryArena}.
      */
-    Arena arena;
+    Arena libraryArena;
 
     /**
      * Value to assign to {@link NativeFunctions#symbols}.
@@ -57,7 +59,19 @@ public final class LibraryLoader<F extends NativeFunctions> {
      * This is reset to {@code null} after usage as a way to ensure
      * that each {@code LibraryLoader} instance is used only once.
      */
-    private Function<LibraryLoader<F>, F> creator;
+    private Function<LibraryLoader<F,S>, F> functionsCreator;
+
+    /**
+     * The constructor to call for building a service using the native 
functions.
+     * This is reset to {@code null} after usage as a way to ensure
+     * that each {@code LibraryLoader} instance is used only once.
+     */
+    private Function<F, S> serviceCreator;
+
+    /**
+     * Exact class (not a parent class or interface) of the service to return.
+     */
+    final Class<S> serviceType;
 
     /**
      * Whether the library has been found.
@@ -67,46 +81,76 @@ public final class LibraryLoader<F extends NativeFunctions> 
{
     private LibraryStatus status;
 
     /**
-     * Cause of failure to load the library, or {@code null} if none.
+     * Creates a new instance.
+     *
+     * @param  functionsCreator  a callback for creating a {@code 
NativeFunctions} if needed.
+     * @param  serviceCreator    a callback for creating the service from the 
{@code NativeFunctions}.
+     * @param  serviceType       exact class (not a parent class or interface) 
of the service to return.
      */
-    private RuntimeException error;
+    public LibraryLoader(final Function<LibraryLoader<F,S>, F> 
functionsCreator,
+                         final Function<F, S> serviceCreator,
+                         final Class<S> serviceType)
+    {
+        this.functionsCreator = functionsCreator;
+        this.serviceCreator   = serviceCreator;
+        this.serviceType      = serviceType;
+    }
 
     /**
-     * Creates a new instance.
+     * Loads the native library and creates the handlers for native functions.
+     * This method can be invoked at most once.
+     *
+     * @return handler of native functions.
+     */
+    final F createNativeFunctions() {
+        Function<LibraryLoader<F,S>, F> c = functionsCreator;
+        functionsCreator = null;     // For making sure to not invoke twice.
+        return c.apply(this);
+    }
+
+    /**
+     * Creates the service which will use the given native functions.
+     * This method is invoked by {@link 
LibraryReference#getOrCreateService(Object, LibraryLoader)}
+     * only and should not be invoked in other circumstances, because the 
caller must ensure that
+     * the returned instance is registered for automatic call to {@link 
NativeFunctions#destroy()}
+     * when the service is garbage-collected or at <abbr>JVM</abbr> shutdown 
time.
      *
-     * @param  creator  the constructor to call for building a {@code 
NativeFunctions}.
+     * @param  handlers  handlers created by {@link #createNativeFunctions()}.
+     * @return service using the given native functions.
      */
-    public LibraryLoader(final Function<LibraryLoader<F>, F> creator) {
-        this.creator = creator;
+    final S createService(final F handlers) {
+        Function<F,S> c = serviceCreator;
+        serviceCreator = null;      // For making sure to not invoke twice.
+        return c.apply(handlers);
     }
 
     /**
-     * Loads the native library from the given file.
-     * Callers should register the returned instance in a {@link 
java.lang.ref.Cleaner}.
+     * Loads the native library from the given file, then builds the service 
using that library.
      *
      * @param  library  the library to load.
-     * @return handles to native functions needed by the data store.
-     * @throws IllegalArgumentException if the native library has not been 
found.
+     * @return service using the native functions in the given library.
+     * @throws IOException if an error occurred while getting a unique 
identifier of the library.
+     * @throws IllegalArgumentException if path does not point to a valid 
native library.
      * @throws IllegalCallerException if this Apache SIS module is not 
authorized to call native methods.
      */
     @SuppressWarnings("restricted")
-    public F load(final Path library) {
-        final var c = creator;
-        creator  = null;
-        filename = library.getFileName().toString();
-        status   = LibraryStatus.LIBRARY_NOT_FOUND;         // In case an 
exception occurs before completion.
-        arena    = Arena.ofShared();
-        final F instance;
+    public S load(final Path library) throws IOException {
+        final Object libraryKey;
+        libraryKey   = Files.readAttributes(library, 
BasicFileAttributes.class).fileKey();
+        filename     = library.getFileName().toString();
+        status       = LibraryStatus.LIBRARY_NOT_FOUND;     // In case an 
exception occurs before completion.
+        libraryArena = Arena.ofShared();
+        final S service;
         try {
-            symbols  = SymbolLookup.libraryLookup(library, arena);
-            status   = LibraryStatus.FUNCTION_NOT_FOUND;    // Update the 
status to report if we cannot complete.
-            instance = c.apply(this);
-            status   = LibraryStatus.LOADED;
+            symbols = SymbolLookup.libraryLookup(library, libraryArena);
+            status  = LibraryStatus.FUNCTION_NOT_FOUND;    // Update the 
status to report if we cannot complete.
+            service = LibraryReference.getOrCreateService(libraryKey, this, 
false);
+            status  = LibraryStatus.LOADED;
         } catch (Throwable e) {
-            arena.close();
+            libraryArena.close();
             throw e;
         }
-        return instance;
+        return service;
     }
 
     /**
@@ -118,58 +162,44 @@ public final class LibraryLoader<F extends 
NativeFunctions> {
      * <p><em>It is caller's responsibility to ensure that this method is 
invoked at most once per library.</em>
      * Invoking this method many times for the same library may cause 
unpredictable behavior.</p>
      *
-     * <h4>Error handling</h4>
-     * Contrarily to {@link #load(Path)}, this method does not throw 
exception. Callers should invoke
-     * {@link #validate()} or {@link #getError(String)} after this method call,
-     * depending on whether an error should be fatal or not.
-     *
      * @param  library   base filename (without the {@code "lib"} prefix and 
{@code ".so"} or
      *                   {@code ".dll"} suffix) of the native library to load.
-     * @return handles to native functions needed by the data store.
+     * @return service using the native functions in the given library.
+     * @throws IllegalCallerException if this Apache SIS module is not 
authorized to call native methods.
      */
     @SuppressWarnings("restricted")
-    public F global(final String library) {
-        final var c = creator;
-        creator     = null;
-        filename    = System.mapLibraryName(library);
-        status      = LibraryStatus.LIBRARY_NOT_FOUND;      // Default value 
if an exception occurs below.
-        F instance  = null;
-create: try {
+    public S global(final String library) {
+        filename     = System.mapLibraryName(library);
+        status       = LibraryStatus.LIBRARY_NOT_FOUND;     // Default value 
if an exception occurs below.
+        libraryArena = Arena.global();
+        S service = null;
+        try {
+            RuntimeException error = null;
             try {
-                symbols = SymbolLookup.libraryLookup(filename, Arena.global());
+                symbols = SymbolLookup.libraryLookup(filename, libraryArena);
             } catch (IllegalArgumentException e) {
                 error    = e;
                 filename = "system";
                 symbols  = SymbolLookup.loaderLookup();     // In case user 
called `System.loadLibrary(…)`.
             }
             try {
-                instance = c.apply(this);
-                status   = LibraryStatus.LOADED;
+                service = LibraryReference.getOrCreateService(serviceType, 
this, true);
+                status  = LibraryStatus.LOADED;
             } catch (RuntimeException e) {
+                if (e instanceof NoSuchElementException) {
+                    status = LibraryStatus.FUNCTION_NOT_FOUND;
+                }
                 if (error != null) {
                     error.addSuppressed(e);
-                } else {
-                    error = e;
+                    throw error;
                 }
-                break create;
+                throw e;
             }
-            /*
-             * Note: registering a shutdown hook cause a reference to be kept 
for the JVM lifetime.
-             * But such reference should exist anyway because this 
`global(String)` method should
-             * be invoked for initialization of a static variable. 
Furthermore, subclass may need
-             * to do a last native method class for flushing some cache.
-             *
-             * TODO: unregister if the library had a fatal error and should 
not be used anymore.
-             */
-            Shutdown.register(instance);
         } catch (IllegalCallerException e) {
-            error  = e;
             status = LibraryStatus.UNAUTHORIZED;
-        } catch (NoSuchElementException e) {
-            error  = e;
-            status = LibraryStatus.FUNCTION_NOT_FOUND;
+            throw e;
         }
-        return instance;
+        return service;
     }
 
     /**
@@ -178,9 +208,9 @@ create: try {
      * that the loading failed:
      *
      * {@snippet lang="java" :
-     *     LibraryLoader<?> loader = ...;
+     *     LibraryLoader<?,?> loader = ...;
      *     try {
-     *         loader.global("foo");
+     *         service = loader.global("foo");
      *     } finally {
      *         globalStatus = loader.status();
      *     }
@@ -191,30 +221,4 @@ create: try {
     public final LibraryStatus status() {
         return status;
     }
-
-    /**
-     * Throws an exception if the loading of the native library failed.
-     *
-     * @param  library  name of the library, used if an error message needs to 
be produced.
-     * @throws DataStoreException if the native library has not been found
-     *         or if SIS is not allowed to call native functions.
-     */
-    public void validate(String library) throws DataStoreException {
-        status.report(library, error);
-    }
-
-    /**
-     * Returns the error as a log message.
-     *
-     * @param  name  the library name.
-     * @return a log message describing the error, or {@code null} if the 
operation was successful.
-     */
-    public Optional<LogRecord> getError(final String name) {
-        if (error == null) {
-            return Optional.empty();
-        }
-        LogRecord record = 
Resources.forLocale(null).createLogRecord(Level.CONFIG, 
Resources.Keys.CannotInitialize_1, name);
-        record.setThrown(error);
-        return Optional.of(record);
-    }
 }
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryReference.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryReference.java
new file mode 100644
index 0000000000..e7d2aede8c
--- /dev/null
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryReference.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.panama;
+
+import java.lang.ref.WeakReference;
+import org.apache.sis.util.Disposable;
+import org.apache.sis.storage.DataStoreProvider;
+import org.apache.sis.system.ReferenceQueueConsumer;
+import org.apache.sis.system.Shutdown;
+
+
+/**
+ * Weak reference to a service which depends on a native library.
+ * Each native library is identified by the unique identifier of the file 
which has been loaded.
+ * Exactly one service ({@link DataStoreProvider}) is associated to each 
native library.
+ *
+ * <h2>Restriction</h2>
+ * The current implementation does not allow two services to share the same 
native library.
+ * This is for avoiding conflict if a {@link NativeFunctions#destroy()} is 
executed when a
+ * service is garbage-collected while another service is still using the same 
native library.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class LibraryReference extends WeakReference<DataStoreProvider> 
implements Disposable {
+    /**
+     * Most recently loaded library, or {@code null} if none.
+     * This is the starting point of a linked list ordered from
+     * most recently loaded library to less recently loaded library.
+     */
+    private static LibraryReference LAST;
+
+    /**
+     * Whether a shutdown hook has already been registered.
+     */
+    private static boolean hasShutdownHook;
+
+    /**
+     * Nodes which was created before this node, or {@code null} if none.
+     * This is used for managing the linked list.
+     *
+     * <p>Implementation note: while a linked list does not scale well 
compared to a hash map,
+     * we expect very few elements in the list, typically only 1 or 2 
elements.</p>
+     */
+    private LibraryReference previous;
+
+    /**
+     * The native functions used by the service.
+     * Its {@link NativeFunctions#destroy()} method will be invoked when the 
service is garbage-collected.
+     */
+    private final NativeFunctions functions;
+
+    /**
+     * An object that uniquely identifies the native library which has been 
loaded.
+     * This is usually a unique identifier of the file from which the library 
was loaded.
+     */
+    private final Object libraryKey;
+
+    /**
+     * Creates a new reference to a native library wrapped by the given 
service.
+     *
+     * @param service     the service which uses a native library.
+     * @param functions   the native functions used by the service.
+     * @param libraryKey  an object that uniquely identifies the native 
library which has been loaded.
+     */
+    private LibraryReference(final DataStoreProvider service, final 
NativeFunctions functions, final Object libraryKey) {
+        super(service, ReferenceQueueConsumer.QUEUE);
+        this.functions  = functions;
+        this.libraryKey = libraryKey;
+        previous = LAST;
+    }
+
+    /**
+     * Returns a service for the native library identified by the given key.
+     * If a service already exists for the given key, it must be an instance
+     * of the exact class (not a subclass) specified by {@code serviceType}.
+     *
+     * <p>If {@code global} is {@code true}, then the native functions will be 
released at <abbr>JVM</abbr>
+     * shutdown time instead of when the service is garbage-collected. Callers 
should use this mode only when
+     * a strong reference to the service will be kept for the <abbr>JVM</abbr> 
lifetime.</p>
+     *
+     * <h4>Error handling</h4>
+     * If an exception is thrown, then the caller is responsible for closing 
{@link LibraryLoader#libraryArena}
+     * if this is a shared arena. The caller will do nothing (not close the 
arena) if this is the global arena.
+     *
+     * @param  <F>         type of the classes providing native functions.
+     * @param  <S>         compile-time value of the {@code serviceType} 
argument.
+     * @param  libraryKey  an object that uniquely identifies the native 
library which has been loaded.
+     * @param  loader      information about the library to load.
+     * @param  global      whether the native library uses the global arena 
instead of a shared arena.
+     * @return an existing or newly created service for the native library 
identified by the given key.
+     * @throws IllegalStateException if the native library identified by the 
given key is already in use.
+     */
+    static synchronized <F extends NativeFunctions, S extends 
DataStoreProvider>
+            S getOrCreateService(final Object libraryKey, final 
LibraryLoader<F,S> loader, final boolean global)
+    {
+        for (LibraryReference library = LAST; library != null; library = 
library.previous) {
+            if (libraryKey.equals(library.libraryKey)) {
+                @SuppressWarnings("unchecked")
+                final S service = (S) library.get();
+                if (service != null) {
+                    if (service.getClass() != loader.serviceType) {
+                        throw new IllegalStateException("Native library 
already in use.");
+                    }
+                    loader.libraryArena.close();    // Because we will use the 
arena of an existing instance.
+                    return service;
+                }
+                break;
+            }
+        }
+        final F functions = loader.createNativeFunctions();
+        final S service;
+        try {
+            service = loader.createService(functions);
+            /*
+             * Note: registering a shutdown hook cause a reference to be kept 
for the JVM lifetime.
+             * But such reference should exist anyway because this 
`global(String)` method should
+             * be invoked for initialization of a static variable. 
Furthermore, subclass may need
+             * to do a last native method class for flushing some cache.
+             *
+             * TODO: unregister if the library had a fatal error and should 
not be used anymore.
+             */
+            if (global) {
+                Shutdown.register(functions);
+            } else {
+                if (!hasShutdownHook) {
+                    Shutdown.register(LibraryReference::disposeAll);
+                    hasShutdownHook = true;
+                }
+                LAST = new LibraryReference(service, functions, libraryKey);
+            }
+        } catch (Throwable e) {
+            try {
+                functions.destroy();
+            } catch (Throwable s) {
+                e.addSuppressed(s);
+            }
+            // `functions.libraryArena` shall be closed (if not global) by the 
caller.
+            throw e;
+        }
+        return service;
+    }
+
+    /**
+     * Invoked when the service is garbage collected. This method invokes 
{@link NativeFunctions#destroy()}
+     * for releasing the native resources. If an exception occurs, it is 
logged.
+     */
+    @Override
+    public void dispose() {
+        synchronized (LibraryReference.class) {
+            LibraryReference library = LAST;
+            if (library == this) {
+                LAST = previous;
+            }
+            while (library != null) {
+                if (library == this) {
+                    try (functions.libraryArena) {
+                        functions.call();    // Inside the synchronized block 
for blocking a reload to happen in same time.
+                    }
+                    break;
+                }
+                if (library.previous == this) {
+                    library.previous = previous;
+                    previous = null;
+                }
+                library = library.previous;
+            }
+            /*
+             * If the loop completes, we did not found this node in the linked 
list.
+             * This is normal if `disposeAll()` has been executed before 
`dispose()`.
+             * We shall not invoke `destroy()` a second time.
+             */
+        }
+    }
+
+    /**
+     * Disposes all nodes in reverse order (most recently added library is 
disposed first).
+     * This method should be invoked only in the shutdown hook, when all 
non-daemon threads
+     * are terminated. No thread should be using the native library at this 
time.
+     *
+     * @return ignored (declared only for matching the method signature 
expected by lambda function).
+     */
+    private static synchronized Object disposeAll() {
+        LibraryReference library = LAST;
+        LAST = null;
+        while (library != null) {
+            try (library.functions.libraryArena) {
+                library.functions.call();
+            }
+            library = library.previous;
+        }
+        return null;
+    }
+}
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryStatus.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryStatus.java
index 57b4c0952a..3503919664 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryStatus.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/LibraryStatus.java
@@ -16,6 +16,10 @@
  */
 package org.apache.sis.storage.panama;
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.LogRecord;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreClosedException;
 
@@ -42,10 +46,15 @@ public enum LibraryStatus {
     LIBRARY_NOT_FOUND(Resources.Keys.LibraryNotFound_1),
 
     /**
-     * The native library was found, but not symbol that we searched.
+     * The native library was found, but one of the required symbols was not 
found.
      */
     FUNCTION_NOT_FOUND(Resources.Keys.FunctionNotFound_1),
 
+    /**
+     * The native library cannot be initialized.
+     */
+    CANNOT_INITIALIZE(Resources.Keys.CannotInitialize_1),
+
     /**
      * <abbr>SIS</abbr> is not authorized to perform native function calls.
      */
@@ -71,19 +80,43 @@ public enum LibraryStatus {
     /**
      * Throws an exception if the native library is not available.
      *
-     * @param  library  the library name, of formatting the error message.
-     * @param  cause    the cause of the error, or {@code null} if none.
-     * @throws DataStoreException if this enumeration value is not {@link 
#LOADED} or if the given cause is not null.
+     * @param  library  the library name, for formatting the error message.
+     * @throws DataStoreException if this enumeration value is not {@link 
#LOADED}.
      */
-    public void report(String library, Exception cause) throws 
DataStoreException {
-        if (message != 0 || cause != null) {
+    public final void throwIfFailed(final String library) throws 
DataStoreException {
+        if (message != 0) {
             // Note: `NativeAccessNotAllowed` will ignore the `library` 
argument.
             String text = Resources.format(message, library);
-            if (cause != null) {
-                throw new DataStoreException(text, cause);
-            } else {
+            if (this == UNLOADED) {
                 throw new DataStoreClosedException(text);
+            } else {
+                throw new DataStoreException(text);
+            }
+        }
+    }
+
+    /**
+     * Logs a record at the given level if the native library is not available.
+     * This method pretends that the warning come from a {@code provider()} 
method.
+     *
+     * @param  logger    the logger where to send a record.
+     * @param  warning   whether to use the warning level. Otherwise, uses the 
configuration level.
+     * @param  provider  the class to report as the source of the warning.
+     * @param  library   the library name, for formatting the error message.
+     * @param  cause     the cause of the error, or {@code null} if none.
+     */
+    public final void log(Logger logger, boolean warning, Class<?> provider, 
String library, Throwable cause) {
+        if (message != 0 || cause != null) {
+            final Level level = warning ? Level.WARNING : Level.CONFIG;
+            final LogRecord record;
+            if (message == 0) {
+                // Should never happen, but defined for safety.
+                record = new LogRecord(level, cause.toString());
+            } else {
+                record = Resources.forLocale(null).createLogRecord(level, 
message, library);
             }
+            record.setThrown(cause);
+            Logging.completeAndLog(logger, provider, "provider", record);
         }
     }
 }
diff --git 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/NativeFunctions.java
 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/NativeFunctions.java
index 35bd86ab54..af5170b99e 100644
--- 
a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/NativeFunctions.java
+++ 
b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/panama/NativeFunctions.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.Optional;
 import java.util.OptionalInt;
+import java.util.logging.Logger;
 import java.util.concurrent.Callable;
 import java.lang.foreign.Arena;
 import java.lang.foreign.Linker;
@@ -30,29 +31,27 @@ import java.lang.foreign.ValueLayout;
 import java.lang.invoke.MethodHandle;
 import java.lang.reflect.UndeclaredThrowableException;
 import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.storage.DataStoreException;
 
 
 /**
  * Base class for sets of method handles to native functions.
- * The native library can be unloaded by invoking {@link #run()}.
- * This instance can be registered in a shutdown hook or a {@link 
java.lang.ref.Cleaner}.
+ * The native library can be unloaded by invoking {@link #call()}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public abstract class NativeFunctions implements Runnable, Callable<Object> {
+public abstract class NativeFunctions implements Callable<Object> {
     /**
      * Name of the library which has been loaded, for information purposes.
      */
     public final String libraryName;
 
     /**
-     * The arena used for loading the library, or {@code null} for the global 
arena.
-     * This is the arena to close for unloading the library.
-     *
-     * @see #arena()
+     * The arena used for loading the library.
+     * May be a shared or a global arena.
      */
-    private final Arena arena;
+    protected final Arena libraryArena;
 
     /**
      * The lookup for retrieving the address of a symbol in the native library.
@@ -70,18 +69,11 @@ public abstract class NativeFunctions implements Runnable, 
Callable<Object> {
      *
      * @param  loader  the object used for loading the library.
      */
-    protected NativeFunctions(final LibraryLoader<?> loader) {
-        libraryName = loader.filename;
-        arena       = loader.arena;
-        symbols     = loader.symbols;
-        linker      = Linker.nativeLinker();
-    }
-
-    /**
-     * Returns the arena used for loading the library.
-     */
-    protected final Arena arena() {
-        return (arena != null) ? arena : Arena.global();
+    protected NativeFunctions(final LibraryLoader<?,?> loader) {
+        libraryName  = loader.filename;
+        libraryArena = loader.libraryArena;
+        symbols      = loader.symbols;
+        linker       = Linker.nativeLinker();
     }
 
     /**
@@ -169,28 +161,6 @@ public abstract class NativeFunctions implements Runnable, 
Callable<Object> {
         return true;
     }
 
-    /**
-     * Unloads the native library. If the arena is global,
-     * then this method should not be invoked before <abbr>JVM</abbr> shutdown.
-     */
-    @Override
-    public void run() {
-        if (arena != null) {
-            arena.close();
-        }
-    }
-
-    /**
-     * Synonymous of {@link #run()}, used in shutdown hook.
-     *
-     * @return {@code null}.
-     */
-    @Override
-    public final Object call() {
-        run();
-        return null;
-    }
-
     /**
      * Returns whether the given result is null.
      *
@@ -230,4 +200,41 @@ public abstract class NativeFunctions implements Runnable, 
Callable<Object> {
             default: return new UndeclaredThrowableException(exception);
         }
     }
+
+    /**
+     * Disposes the native library when this {@code NativeFunctions} has been 
garbage-collected or during
+     * the <abbr>JVM</abbr> shutdown. This method is public as an 
implementation side-effect and should
+     * not be invoked directly in application code.
+     *
+     * <p>This method is invoked in contexts where we cannot propagate 
exceptions to the caller.
+     * Instead, exceptions are logged.</p>
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public final Object call() {
+        // Note: `libraryArena` may be the global arena, which cannot be 
closed.
+        try {
+            destroy();
+        } catch (Throwable e) {
+            Logging.unexpectedException(getLogger(), getClass(), "destroy", e);
+        }
+        return null;
+    }
+
+    /**
+     * Releases any resources used by the native library. This method is 
invoked automatically when
+     * the last {@code NativeFunctions} instance using the native library has 
been garbage-collected,
+     * or during the <abbr>JVM</abbr> shutdown.
+     */
+    protected void destroy() {
+    }
+
+    /**
+     * Returns the logger where to report warnings.
+     * This is used if an exception occurred during the execution of {@link 
#destroy()}.
+     *
+     * @return the logger for warnings.
+     */
+    protected abstract Logger getLogger();
 }

Reply via email to