Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5609b3d
Add RefCounter to Env to prevent closure while in use WIP
at055612 Jan 27, 2026
2a0a6c0
Tidy code
at055612 Jan 27, 2026
81e8ae7
Change checkNotClosed to allow things to close down in CLOSING state
at055612 Jan 27, 2026
5a4585e
Fix acquire behaviour in Txn/Cursor ctors
at055612 Jan 27, 2026
a7a99c9
Add onClose to NoOpRefCounter
at055612 Jan 27, 2026
d02f01b
Tweak perf test
at055612 Jan 27, 2026
7b83648
Improve stripe hashing with golden ratio
at055612 Jan 27, 2026
301df13
Add StampedLockRefCounterImpl WIP
at055612 Jan 28, 2026
e40ee22
Add more ref counters, improve tests
at055612 Jan 29, 2026
91df336
Refactor ref counter classes
at055612 Jan 30, 2026
9f7043d
Tidy code, add tests
at055612 Jan 30, 2026
e48f1c7
Fix bug in StripedRefCounter, improve tests
at055612 Feb 2, 2026
a6080b3
Rename class
at055612 Feb 2, 2026
f9138df
Refactor test classes
at055612 Feb 2, 2026
1b84247
Improve perfTest
at055612 Feb 2, 2026
49438e9
Fix NoOpRefCounter close method
at055612 Feb 2, 2026
36019a3
Return Cursor.close() to original behavior
at055612 Feb 2, 2026
3ce28de
Fix logic in SimpleRefCounter
at055612 Feb 2, 2026
8d170c7
Improve close() method
at055612 Feb 2, 2026
4b84cf6
Remove redundant method & ctor from SimpleRefCounter
at055612 Feb 2, 2026
a6eb02b
Improve SimpleRefCounter
at055612 Feb 2, 2026
ded736e
Remove redundant EnvState
at055612 Feb 2, 2026
cb890be
Fix SimpleRefCounter.release()
at055612 Feb 2, 2026
b61dbf9
Change (Striped|Simple)RefCounter to not throw in lambda
at055612 Feb 2, 2026
e2e7d5b
Add JMH benchmark for RefCounter
at055612 Feb 2, 2026
5a5b8bf
gh-279 Fix concurrency issues in StripedRefCounter
at055612 Aug 3, 2026
37dfbdb
gh-279 Tidy code, add tests
at055612 Aug 3, 2026
519479f
gh-279 Add Env.Builder.safeClose()
at055612 Aug 4, 2026
8c9c544
gh-279 Fix tests
at055612 Aug 4, 2026
3c2f181
gh-279 Run mvn build
at055612 Aug 4, 2026
542904c
gh-279 Run mvn fmt
at055612 Aug 4, 2026
16999f0
gh-279 Change tests to use Env.safeClose
at055612 Aug 4, 2026
6e8a646
Refactor tests, rename singleThreaded builder method
at055612 Aug 10, 2026
1e19b1a
Tidy up tests
at055612 Aug 10, 2026
344d798
gh-279 Fix compilation failures under java 8
at055612 Aug 10, 2026
0831fc9
gh-279 Add test
at055612 Aug 10, 2026
ed0df73
Merge branch 'master' into gh-279-env-close-protection
at055612 Aug 10, 2026
8c001e6
gh-279 Address GH code fix suggestions
at055612 Aug 10, 2026
290522d
gh-279 Add coverage
at055612 Aug 10, 2026
cccf9dd
gh-279 Format
at055612 Aug 10, 2026
819881a
gh-279 Adding in tests written by @bernardladenthin
at055612 Aug 10, 2026
6ceea10
gh-279 Add test coverage
at055612 Aug 10, 2026
fae66ee
gh-279 Add test coverage
at055612 Aug 10, 2026
04043b5
gh-279 Fix GH suggestions
at055612 Aug 10, 2026
58f77e1
gh-279 Add test coverage
at055612 Aug 10, 2026
0729728
gh-279 Fix failing test
at055612 Aug 10, 2026
b083c29
gh-279 Fix catch in Cursor ctor
at055612 Aug 10, 2026
859c0db
gh-279 Tidy asserts in TxnTest
at055612 Aug 11, 2026
3c2ad81
gh-279 Add Env.tryClose, add more tests
at055612 Aug 11, 2026
a9228db
gh-279 Add test for Cursor ctor exception
at055612 Aug 11, 2026
2ce1c76
gh-279 Remove dead code, add test for code coverage
at055612 Aug 11, 2026
8aa99a1
gh-279 Improve Env.copy tests
at055612 Aug 11, 2026
ecf7251
Add test for KeyVal.close
at055612 Aug 11, 2026
7565c94
gh-279 Add RefCounter.run test
at055612 Aug 11, 2026
b486b76
gh-279 Fix AbstractFlagSetTest
at055612 Aug 11, 2026
c3a05d7
gh-279 Changes to AbstractFlagSetTest for codecov
at055612 Aug 11, 2026
9c7f97c
gh-279 Format
at055612 Aug 11, 2026
bfdefef
gh-279 Add tests to TargetNameTest
at055612 Aug 11, 2026
eefc87f
gh-279 Change safeClose to only track RW cursors
at055612 Aug 14, 2026
017ef34
gh-279 Add more tests, improve javadoc
at055612 Aug 14, 2026
ab0fc6a
gh-279 Fix codeQL suggestion
at055612 Aug 14, 2026
e8fdd2d
gh-279 Tweak javadoc
at055612 Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<google-java-format.version>1.28.0</google-java-format.version>
<guava.version>33.5.0-jre</guava.version>
<jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>
<jmh.version>1.37</jmh.version>
<jnr-constants.version>0.10.4</jnr-constants.version>
<jnr-ffi.version>2.2.18</jnr-ffi.version>
<junit.version>5.14.1</junit.version>
Expand Down Expand Up @@ -125,6 +126,18 @@
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/org/lmdbjava/AbstractFlagSet.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2016-2025 The LmdbJava Open Source Project
* Copyright © 2016-2026 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.
Expand Down Expand Up @@ -85,6 +85,18 @@ public String toString() {
return FlagSet.asString(this);
}

@Override
public boolean equals(Object object) {
if (object == null || getClass() != object.getClass()) return false;
AbstractFlagSet<?> that = (AbstractFlagSet<?>) object;
return mask == that.mask && Objects.equals(flags, that.flags);
}

@Override
public int hashCode() {
return Objects.hash(flags, mask);
}

static class AbstractEmptyFlagSet<T extends MaskedFlag> implements FlagSet<T> {

@Override
Expand Down
92 changes: 69 additions & 23 deletions src/main/java/org/lmdbjava/Cursor.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2016-2025 The LmdbJava Open Source Project
* Copyright © 2016-2026 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.
Expand Down Expand Up @@ -30,60 +30,99 @@
import static org.lmdbjava.SeekOp.MDB_NEXT;
import static org.lmdbjava.SeekOp.MDB_PREV;

import java.util.concurrent.atomic.AtomicBoolean;
import jnr.ffi.Pointer;
import jnr.ffi.byref.NativeLongByReference;

/**
* A cursor handle.
* A cursor handle for iterating through key/value pairs in an LMDB database.
*
* <p>A cursor belongs to a {@link Txn}.
*
* <p>If {@link Txn} is a read-write transaction, LMDB will automatically close the cursor handle
* when the {@link Txn} is committed or aborted, meaning that Cursor#close() does not need to be
* called, however, if it is called, it must be called before the {@link Txn} is committed/aborted.
*
* <p>NOTE: If {@link Env.Builder#setSafeClose()} is set, the {@link Env} requires that all cursors
* are closed before the {@link Env} is closed, therefore it is good practice to explicitly call
* {@link Cursor#close()} or use a try-with-resources block on all types of cursor.
*
* <p>If {@link Txn} is a read-only transaction, {@link Cursor#close()} must be called to free up
* the cursor handle. This can be called at any time. Read-only transactions can 'moved' to a
* different transaction using the {@link Cursor#renew(Txn)} method. This can also be done at any
* time.
*
* @param <T> buffer type
*/
public final class Cursor<T> implements AutoCloseable {

private boolean closed;
private final AtomicBoolean closed;
private final KeyVal<T> kv;
private final Pointer ptrCursor;
private Txn<T> txn;
private final Env<T> env;
private final RefCounter.RefCounterReleaser refCounterReleaser;
private volatile Txn<T> txn;

Cursor(final Pointer ptr, final Txn<T> txn, final Env<T> env) {
requireNonNull(ptr);
requireNonNull(txn);
requireNonNull(env);
this.ptrCursor = ptr;
this.txn = txn;
this.kv = txn.newKeyVal();
// The env needs to track open RW cursors to prevent env closure before the cursors are closed.
// We don't care about RO cursors as LMDB will automatically free them.
refCounterReleaser = txn.isWritable() ? env.acquire() : null;
this.env = env;
this.closed = new AtomicBoolean(false);
try {
this.kv = txn.newKeyVal();
} catch (final Exception e) {
closed.set(true);
releaseRefCount();

// Clean up the native cursor
if (txn.isReadOnly() || txn.isReady()) {
LIB.mdb_cursor_close(ptrCursor);
}
throw e;
}
}

/**
* Close a cursor handle.
*
* <p>The cursor handle will be freed and must not be used again after this call. Its transaction
* must still be live if it is a write-transaction.
* must still be live (i.e. not committed or aborted) if it is a write-transaction.
*/
@Override
public void close() {
if (closed) {
return;
}
kv.close();
if (SHOULD_CHECK) {
env.checkNotClosed();
if (!txn.isReadOnly()) {
txn.checkReady();
if (closed.compareAndSet(false, true)) {
kv.close();
if (SHOULD_CHECK) {
env.checkNotClosed();
if (!txn.isReadOnly()) {
// TODO Rather than throwing if the txn is not in the right state to close
// we could check the txn state and only call mdb_cursor_close if the state is
// appropriate,
// i.e. (txn.isReadOnly() || txn.isReady())
// This would make using try-with-resources less likely to fail

// Cannot close the mdb_cursor if the txn is writable and not in a ready state
txn.checkReady();
}
}
LIB.mdb_cursor_close(ptrCursor);
releaseRefCount();
}
LIB.mdb_cursor_close(ptrCursor);
closed = true;
}

/**
* Return count of duplicates for current key.
* Return count of duplicates for the current key.
*
* <p>This call is only valid on databases that support sorted duplicate data items {@link
* DbiFlags#MDB_DUPSORT}.
*
* @return count of duplicates for current key
* @return count of duplicates for the current key
*/
public long count() {
if (SHOULD_CHECK) {
Expand Down Expand Up @@ -368,7 +407,6 @@ public void putMultiple(final T key, final T val, final int elements) {
*/
public void putMultiple(final T key, final T val, final int elements, final PutFlagSet flags) {
if (SHOULD_CHECK) {
requireNonNull(txn);
requireNonNull(key);
requireNonNull(val);
env.checkNotClosed();
Expand Down Expand Up @@ -397,19 +435,20 @@ public void putMultiple(final T key, final T val, final int elements, final PutF
* may be associated with a new read-only transaction, and referencing the same database handle as
* it was created with. This may be done whether the previous transaction is live or dead.
*
* @param newTxn transaction handle
* @param newTxn The new transaction handle to associate with this cursor. It must be a read-only
* transaction and in a ready state, i.e. not committed/aborted/closed.
*/
public void renew(final Txn<T> newTxn) {
if (SHOULD_CHECK) {
requireNonNull(newTxn);
env.checkNotClosed();
checkNotClosed();
this.txn.checkReadOnly(); // existing
txn.checkReadOnly(); // existing
newTxn.checkReadOnly();
newTxn.checkReady();
}
checkRc(LIB.mdb_cursor_renew(newTxn.pointer(), ptrCursor));
this.txn = newTxn;
txn = newTxn;
}

/**
Expand Down Expand Up @@ -518,11 +557,18 @@ public T val() {
}

private void checkNotClosed() {
if (closed) {
if (closed.get()) {
throw new ClosedException();
}
}

private void releaseRefCount() {
// May be null if the cursor was created with a read-only transaction
if (refCounterReleaser != null) {
refCounterReleaser.release();
}
}

/** Cursor has already been closed. */
public static final class ClosedException extends LmdbException {

Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/lmdbjava/CursorIterable.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2016-2025 The LmdbJava Open Source Project
* Copyright © 2016-2026 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.
Expand Down Expand Up @@ -38,6 +38,8 @@
*
* <p>An instance will create and close its own cursor.
*
* <p>Not thread safe.
*
* @param <T> buffer type
*/
public final class CursorIterable<T> implements Iterable<CursorIterable.KeyVal<T>>, AutoCloseable {
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/lmdbjava/Dbi.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2016-2025 The LmdbJava Open Source Project
* Copyright © 2016-2026 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.
Expand Down Expand Up @@ -408,6 +408,9 @@ public Cursor<T> openCursor(final Txn<T> txn) {
/**
* Starts a new read-write transaction and puts the key/data pair.
*
* <p>NOTE: If this is called while this thread already has an open write transaction, it will
* block indefinitely.
*
* @param key key to store in the database (not null)
* @param val value to store in the database (not null)
* @see #put(Txn, Object, Object, PutFlagSet)
Expand Down
Loading
Loading