txn, final T key, final int size,
checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv()
.pointerVal(), flags));
txn.kv().valOut(); // marked as in,out in LMDB C docs
+ ReferenceUtil.reachabilityFence0(key);
return txn.val();
}
diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java
new file mode 100644
index 00000000..0002a509
--- /dev/null
+++ b/src/main/java/org/lmdbjava/ReferenceUtil.java
@@ -0,0 +1,55 @@
+/*-
+ * #%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;
+
+public final class 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.
+ *
+ * Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable.
+ * see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8
+ * The Java 9 method Reference.reachabilityFence offers a solution to this problem.
+ *
+ *
This method is always implemented as a synchronization on {@code ref}, not as
+ * {@code Reference.reachabilityFence} for consistency across platforms and to allow building on JDK 6-8.
+ * It is the caller's responsibility to ensure that this synchronization will not cause deadlock.
+ *
+ * @param ref the reference. If {@code null}, this method has no effect.
+ * @see https://github.com/netty/netty/pull/8410
+ */
+ @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"})
+ public static void reachabilityFence0(Object ref) {
+ if (ref != null) {
+ synchronized (ref) {
+ // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521
+ }
+ }
+ }
+
+ private ReferenceUtil() {}
+}
diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java
new file mode 100644
index 00000000..aa8a8856
--- /dev/null
+++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java
@@ -0,0 +1,111 @@
+/*-
+ * #%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;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static java.nio.ByteBuffer.allocateDirect;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+import static org.lmdbjava.DbiFlags.MDB_CREATE;
+import static org.lmdbjava.Env.create;
+
+@SuppressFBWarnings({"DM_GC", "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"})
+@SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly")
+public class GarbageCollectionTest {
+
+ private static final String DB_NAME = "my DB";
+ private static final String KEY_PREFIX = "Uncorruptedkey";
+ private static final String VAL_PREFIX = "Uncorruptedval";
+
+ @Rule
+ public final TemporaryFolder tmp = new TemporaryFolder();
+ private void putBuffer(Dbi db, Txn txn, int i) {
+ ByteBuffer key = allocateDirect(24);
+ ByteBuffer val = allocateDirect(24);
+ key.put((KEY_PREFIX+i).getBytes(UTF_8)).flip();
+ val.put((VAL_PREFIX+i).getBytes(UTF_8)).flip();
+ db.put(txn, key, val);
+ }
+ @Test
+ public void buffersNotGarbageCollectedTest() throws IOException {
+ final File path = tmp.newFolder();
+
+ final Env env = create()
+ .setMapSize(2_085_760_999)
+ .setMaxDbs(1)
+ .open(path);
+ final Dbi db = env.openDbi(DB_NAME, MDB_CREATE);
+
+ // Trigger compilation and whatnot
+ try (Txn txn = env.txnWrite()) {
+ for (int i = 0; i < 5000; i++) {
+ putBuffer(db, txn, i);
+ }
+ txn.commit();
+ }
+ // Call gc before writing to lmdb and after last reference to buffer by changing the behavior of mask
+ try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) {
+ mockedStatic.when(MaskedFlag::mask).thenAnswer(invocationOnMock -> {
+ System.gc();
+ return 0;
+ });
+ try (Txn txn = env.txnWrite()) {
+ for (int i = 0; i < 1000; i++) {
+ putBuffer(db, txn, i);
+ }
+ txn.commit();
+ }
+ }
+
+ // Find corrupt keys
+ try (Txn txn = env.txnRead()) {
+ try (Cursor c = db.openCursor(txn)) {
+ if (c.first()) {
+ do {
+ byte[] rkey = new byte[c.key().remaining()];
+ c.key().get(rkey);
+ byte[] rval = new byte[c.val().remaining()];
+ c.val().get(rval);
+ String skey = new String(rkey, UTF_8);
+ String sval = new String(rval, UTF_8);
+ if (!skey.startsWith("Uncorruptedkey")) {
+ fail("Found corrupt key " + skey);
+ }
+ if (!sval.startsWith("Uncorruptedval")) {
+ fail("Found corrupt val " + sval);
+ }
+ } while (c.next());
+ }
+ }
+ }
+ env.close();
+ }
+}