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();
}