- * Implemented as a protected method to discourage use of the buffer proxy
- * in collections etc (given by design it wraps a temporary value only).
+ * The provided comparator must strictly match the lexicographical order of
+ * keys in the native LMDB database.
*
- * @param o1 left operand
- * @param o2 right operand
- * @return as per {@link Comparable}
+ * @param flags for the database
+ * @return a comparator that can be used (never null)
*/
- protected abstract int compare(T o1, T o2);
+ protected abstract Comparator
* An instance will create and close its own cursor.
*
- *
- * If iterating over keys stored with {@link DbiFlags#MDB_INTEGERKEY} you must
- * provide a Java comparator when constructing the {@link Dbi} or this class. It
- * is more efficient to use a comparator only with this class, as this avoids
- * LMDB calling back into Java code to perform the integer key comparison.
- *
* @param
- * If a comparator is provided, it must reflect the same ordering as LMDB uses
- * for cursor operations (eg first, next, last, previous etc).
- *
- *
- * If a null comparator is provided, any comparator provided when opening the
- * database is used. If no database comparator was specified, the buffer's
- * default comparator is used. Such buffer comparators reflect LMDB's default
- * lexicographical order.
- *
- * @param txn transaction handle (not null; not committed)
- * @param range range of acceptable keys (not null)
- * @param comparator custom comparator for keys (may be null)
- * @return iterator (never null)
- */
- public CursorIterable
* This method will automatically commit the private transaction before
@@ -303,13 +340,14 @@ public Dbi
- * If a custom comparator is specified, this comparator is called from LMDB
- * any time it needs to compare two keys. The comparator must be used any time
- * any time this database is opened, otherwise database corruption may occur.
- * The custom comparator will also be used whenever a {@link CursorIterable}
- * is created from the returned {@link Dbi}. If a custom comparator is not
- * specified, LMDB's native default lexicographical order is used. The default
- * comparator is typically more efficient (as there is no need for the native
- * library to call back into Java for the comparator result).
+ * A {@link Comparator} may be provided when calling this method. Such
+ * comparator is primarily used by {@link CursorIterable} instances. A
+ * secondary (but uncommon) use of the comparator is to act as a callback from
+ * the native library if
+ * A default comparator will be provided if
* This method (and its overloaded convenience variants) must not be called
@@ -339,17 +381,24 @@ public Dbi
+ * Recent versions of the JDK have a nasty habit of prematurely deciding
+ * objects are unreachable (eg
+ * StackOverflow question
+ * 26642153.
+ *
+ *
+ *
+ * This method is always implemented as a synchronization on {@code ref}.
+ * It is the caller's responsibility to ensure that this synchronization
+ * will not cause deadlock.
+ *
+ * @param ref the reference (null is acceptable but has no effect)
+ * @see Netty PR 8410
+ */
+ @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"})
+ public static void reachabilityFence0(final Object ref) {
+ if (ref != null) {
+ synchronized (ref) {
+ // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java
new file mode 100644
index 00000000..446ea5b5
--- /dev/null
+++ b/src/main/java/org/lmdbjava/TargetName.java
@@ -0,0 +1,157 @@
+/*-
+ * #%L
+ * LmdbJava
+ * %%
+ * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project
+ * %%
+ * Licensed 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.
+ * #L%
+ */
+
+package org.lmdbjava;
+
+import static java.lang.System.getProperty;
+import static java.util.Locale.ENGLISH;
+
+/**
+ * Determines the name of the target LMDB native library.
+ *
+ *
+ * Users will typically use an LMDB native library that is embedded within the
+ * LmdbJava JAR. Embedded libraries are built by a Zig cross-compilation step as
+ * part of the release process. The naming convention reflects the Zig target
+ * name plus a common filename extension. This simplifies support for future Zig
+ * targets (eg with different toolchains etc).
+ *
+ *
+ * Users can set two system properties to override the automatic resolution of
+ * an embedded library. Setting {@link #LMDB_NATIVE_LIB_PROP} will force use of
+ * that external LMDB library. Setting {@link #LMDB_EMBEDDED_LIB_PROP} will
+ * force use of that embedded LMDB library. If both are set, the former property
+ * will take precedence. Most users do not need to set either property.
+ */
+public final class TargetName {
+
+ /**
+ * True if the resolved native filename is an external file (conversely false
+ * indicates the file should be considered a classpath resource).
+ */
+ public static final boolean IS_EXTERNAL;
+
+ /**
+ * Java system property name that can be set to override the embedded library
+ * that will be used. This is likely to be required if automatic resolution
+ * fails but the user still prefers to use an LmdbJava-bundled library. This
+ * path must include the classpath prefix (usually Dbi in the Env.
*
* nativeCb is true. This is
+ * usually avoided due to the overhead of native code calling back into Java.
+ * It is instead highly recommended to set the correct {@link DbiFlags} to
+ * allow the native library to correctly order the intended keys.
+ *
+ * null is passed as the
+ * comparator. If a custom comparator is provided, it must strictly match the
+ * lexicographical order of keys in the native LMDB database.
*
* java.io.tmpdir. Ignored if the LMDB system library is not
* being extracted from the LmdbJava JAR (as would be the case if other
- * system properties defined in Library have been set).
+ * system properties defined in TargetName have been set).
*/
public static final String LMDB_EXTRACT_DIR_PROP = "lmdbjava.extract.dir";
- /**
- * Java system property name that can be set to provide a custom path to a
- * external LMDB system library. If set, the system property
- * DISABLE_EXTRACT_PROP will be overridden.
- */
- public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib";
- /**
- * Indicates whether automatic extraction of the LMDB system library is
- * permitted.
- */
- public static final boolean SHOULD_EXTRACT = !getBoolean(DISABLE_EXTRACT_PROP);
/**
* Indicates the directory where the LMDB system library will be extracted.
*/
@@ -91,35 +68,14 @@ final class Library {
getProperty("java.io.tmpdir"));
static final Lmdb LIB;
static final jnr.ffi.Runtime RUNTIME;
- /**
- * Indicates whether external LMDB system library is provided.
- */
- static final boolean SHOULD_USE_LIB = nonNull(
- getProperty(LMDB_NATIVE_LIB_PROP));
- private static final String LIB_NAME = "lmdb";
static {
final String libToLoad;
- final String arch = getProperty("os.arch");
- final boolean arch64 = "x64".equals(arch) || "amd64".equals(arch)
- || "x86_64".equals(arch);
-
- final String os = getProperty("os.name");
- final boolean linux = os.toLowerCase(ENGLISH).startsWith("linux");
- final boolean osx = os.startsWith("Mac OS X");
- final boolean windows = os.startsWith("Windows");
-
- if (SHOULD_USE_LIB) {
- libToLoad = getProperty(LMDB_NATIVE_LIB_PROP);
- } else if (SHOULD_EXTRACT && arch64 && linux) {
- libToLoad = extract("org/lmdbjava/lmdbjava-native-linux-x86_64.so");
- } else if (SHOULD_EXTRACT && arch64 && osx) {
- libToLoad = extract("org/lmdbjava/lmdbjava-native-osx-x86_64.dylib");
- } else if (SHOULD_EXTRACT && arch64 && windows) {
- libToLoad = extract("org/lmdbjava/lmdbjava-native-windows-x86_64.dll");
+ if (TargetName.IS_EXTERNAL) {
+ libToLoad = TargetName.RESOLVED_FILENAME;
} else {
- libToLoad = LIB_NAME;
+ libToLoad = extract(TargetName.RESOLVED_FILENAME);
}
LIB = create(Lmdb.class).load(libToLoad);
@@ -291,7 +247,7 @@ int mdb_put(@In Pointer txn, @In Pointer dbi, @In Pointer key,
@In Pointer data,
int flags);
- int mdb_reader_check(@In Pointer env, int dead);
+ int mdb_reader_check(@In Pointer env, @Out IntByReference dead);
int mdb_set_compare(@In Pointer txn, @In Pointer dbi, ComparatorCallback cb);
diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java
new file mode 100644
index 00000000..36bf9247
--- /dev/null
+++ b/src/main/java/org/lmdbjava/ReferenceUtil.java
@@ -0,0 +1,67 @@
+/*-
+ * #%L
+ * LmdbJava
+ * %%
+ * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project
+ * %%
+ * Licensed 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.
+ * #L%
+ */
+
+package org.lmdbjava;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Supports creating strong references in manner compatible with Java 8.
+ */
+public final class ReferenceUtil {
+
+ private ReferenceUtil() {
+ }
+
+ /**
+ * Ensures that the object referenced by the given reference remains
+ * strongly reachable, regardless of any prior actions of the program
+ * that might otherwise cause the object to become unreachable. Thus, the
+ * referenced object is not reclaimable by garbage collection at least until
+ * after the invocation of this method.
+ *
+ * java.lang.ref.Reference.reachabilityFence offers a solution to
+ * this problem, but it was only introduced in Java 9. LmdbJava presently
+ * supports Java 8 and therefore this method provides an alternative.
+ *
+ * org/lmdbjava).
+ */
+ public static final String LMDB_EMBEDDED_LIB_PROP = "lmdbjava.embedded.lib";
+ /**
+ * Java system property name that can be set to provide a custom path to an
+ * external LMDB system library.
+ */
+ public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib";
+ /**
+ * Resolved target native filename or fully-qualified classpath location.
+ */
+ public static final String RESOLVED_FILENAME;
+ private static final String ARCH = getProperty("os.arch");
+ private static final String EMBED = getProperty(LMDB_EMBEDDED_LIB_PROP);
+ private static final String EXTERNAL = getProperty(LMDB_NATIVE_LIB_PROP);
+ private static final String OS = getProperty("os.name");
+
+ static {
+ IS_EXTERNAL = isExternal(EXTERNAL);
+ RESOLVED_FILENAME = resolveFilename(EXTERNAL, EMBED, ARCH, OS);
+ }
+
+ private TargetName() {
+ }
+
+ public static String resolveExtension(final String os) {
+ return check(os, "Windows") ? "dll" : "so";
+ }
+
+ static boolean isExternal(final String external) {
+ return external != null && !external.isEmpty();
+ }
+
+ static String resolveFilename(final String external, final String embed,
+ final String arch, final String os) {
+ if (external != null && !external.isEmpty()) {
+ return external;
+ }
+
+ if (embed != null && !embed.isEmpty()) {
+ return embed;
+ }
+
+ final String pkg = TargetName.class.getPackage().getName().replace('.', '/');
+ return pkg + "/" + resolveArch(arch) + "-" + resolveOs(os) + "-"
+ + resolveToolchain(os) + "." + resolveExtension(os);
+ }
+
+ /**
+ * Case insensitively checks whether the passed string starts with any of the
+ * candidate strings.
+ *
+ * @param string the string being checked
+ * @param candidates one or more candidate strings
+ * @return true if the string starts with any of the candidates
+ */
+ private static boolean check(final String string,
+ final String... candidates) {
+ if (string == null) {
+ return false;
+ }
+
+ final String strLower = string.toLowerCase(ENGLISH);
+ for (final String c : candidates) {
+ if (strLower.startsWith(c.toLowerCase(ENGLISH))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String err(final String reason) {
+ return reason + " (please set system property " + LMDB_NATIVE_LIB_PROP
+ + " to the path of an external LMDB native library or property "
+ + LMDB_EMBEDDED_LIB_PROP + " to the name of an LmdbJava embedded"
+ + " library; os.arch='" + ARCH + "' os.name='" + OS + "')";
+ }
+
+ private static String resolveArch(final String arch) {
+ if (check(arch, "aarch64")) {
+ return "aarch64";
+ } else if (check(arch, "x86_64", "amd64")) {
+ return "x86_64";
+ }
+ throw new UnsupportedOperationException(err("Unsupported os.arch"));
+ }
+
+ private static String resolveOs(final String os) {
+ if (check(os, "Linux")) {
+ return "linux";
+ } else if (check(os, "Mac OS")) {
+ return "macos";
+ } else if (check(os, "Windows")) {
+ return "windows";
+ }
+ throw new UnsupportedOperationException(err("Unsupported os.name"));
+ }
+
+ private static String resolveToolchain(final String os) {
+ return check(os, "Mac OS") ? "none" : "gnu";
+ }
+
+}
diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java
index f3071152..b6d28917 100644
--- a/src/main/java/org/lmdbjava/Txn.java
+++ b/src/main/java/org/lmdbjava/Txn.java
@@ -34,8 +34,6 @@
import static org.lmdbjava.Txn.State.RESET;
import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN;
-import java.util.Comparator;
-
import jnr.ffi.Pointer;
/**
@@ -225,19 +223,6 @@ void checkWritesAllowed() {
}
}
- ComparatorInteger rather than the primitive to represent a
* null buffer.
*/
- private static class FakeCursor {
+ private static final class FakeCursor {
private static final int[] KEYS = new int[]{2, 4, 6, 8};
private int position;
diff --git a/src/test/java/org/lmdbjava/TargetNameTest.java b/src/test/java/org/lmdbjava/TargetNameTest.java
new file mode 100644
index 00000000..eec38233
--- /dev/null
+++ b/src/test/java/org/lmdbjava/TargetNameTest.java
@@ -0,0 +1,74 @@
+/*-
+ * #%L
+ * LmdbJava
+ * %%
+ * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project
+ * %%
+ * Licensed 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.
+ * #L%
+ */
+
+package org.lmdbjava;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.lmdbjava.TargetName.isExternal;
+import static org.lmdbjava.TargetName.resolveFilename;
+import static org.lmdbjava.TestUtils.invokePrivateConstructor;
+
+import org.junit.Test;
+
+/**
+ * Test {@link TargetName}.
+ */
+public final class TargetNameTest {
+
+ private static final String NONE = "";
+
+ @Test
+ public void coverPrivateConstructors() {
+ invokePrivateConstructor(TargetName.class);
+ }
+
+ @Test
+ public void customEmbedded() {
+ assertThat(resolveFilename(NONE, "x/y.so", NONE, NONE), is("x/y.so"));
+ assertThat(isExternal(NONE), is(false));
+ }
+
+ @Test
+ public void embeddedNameResolution() {
+ embed("aarch64-linux-gnu.so", "aarch64", "Linux");
+ embed("aarch64-macos-none.so", "aarch64", "Mac OS");
+ embed("x86_64-linux-gnu.so", "x86_64", "Linux");
+ embed("x86_64-macos-none.so", "x86_64", "Mac OS");
+ embed("x86_64-windows-gnu.dll", "x86_64", "Windows");
+ }
+
+ @Test
+ public void externalLibrary() {
+ assertThat(resolveFilename("/l.so", NONE, NONE, NONE), is("/l.so"));
+ assertThat(TargetName.isExternal("/l.so"), is(true));
+ }
+
+ @Test
+ public void externalTakesPriority() {
+ assertThat(resolveFilename("/lm.so", "x/y.so", NONE, NONE), is("/lm.so"));
+ assertThat(isExternal("/lm.so"), is(true));
+ }
+
+ private void embed(final String lib, final String arch, final String os) {
+ assertThat(resolveFilename(NONE, NONE, arch, os), is("org/lmdbjava/" + lib));
+ assertThat(isExternal(NONE), is(false));
+ }
+}
diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java
index c115be0b..775a10a6 100644
--- a/src/test/java/org/lmdbjava/TxnTest.java
+++ b/src/test/java/org/lmdbjava/TxnTest.java
@@ -84,7 +84,7 @@ public void after() {
public void before() throws IOException {
path = tmp.newFile();
env = create()
- .setMapSize(KIBIBYTES.toBytes(100))
+ .setMapSize(KIBIBYTES.toBytes(256))
.setMaxReaders(1)
.setMaxDbs(2)
.open(path, POSIX_MODE, MDB_NOSUBDIR);