Skip to content

Commit 52375e4

Browse files
committed
Dbi comparator thread safety (fixes #127)
1 parent 5912beb commit 52375e4

File tree

2 files changed

+64
-12
lines changed

2 files changed

+64
-12
lines changed

src/main/java/org/lmdbjava/Dbi.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ public final class Dbi<T> {
5757
private final ComparatorCallback ccb;
5858
private boolean cleaned;
5959
private final Comparator<T> compFunc;
60-
private final T compKeyA;
61-
private final T compKeyB;
6260
private final Env<T> env;
6361
private final byte[] name;
6462
private final BufferProxy<T> proxy;
@@ -75,18 +73,19 @@ public final class Dbi<T> {
7573
if (comparator == null) {
7674
proxy = null;
7775
compFunc = null;
78-
compKeyA = null;
79-
compKeyB = null;
8076
ccb = null;
8177
} else {
8278
this.proxy = txn.getProxy();
8379
this.compFunc = comparator;
84-
this.compKeyA = proxy.allocate();
85-
this.compKeyB = proxy.allocate();
8680
this.ccb = (keyA, keyB) -> {
81+
final T compKeyA = proxy.allocate();
82+
final T compKeyB = proxy.allocate();
8783
proxy.out(compKeyA, keyA, keyA.address());
8884
proxy.out(compKeyB, keyB, keyB.address());
89-
return compFunc.compare(compKeyA, compKeyB);
85+
final int result = compFunc.compare(compKeyA, compKeyB);
86+
proxy.deallocate(compKeyA);
87+
proxy.deallocate(compKeyB);
88+
return result;
9089
};
9190
LIB.mdb_set_compare(txn.pointer(), ptr, ccb);
9291
}
@@ -495,10 +494,6 @@ private void clean() {
495494
return;
496495
}
497496
cleaned = true;
498-
if (compKeyA != null) {
499-
proxy.deallocate(compKeyA);
500-
proxy.deallocate(compKeyB);
501-
}
502497
}
503498

504499
/**

src/test/java/org/lmdbjava/DbiTest.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,27 @@
2828
import java.nio.ByteBuffer;
2929
import static java.nio.ByteBuffer.allocateDirect;
3030
import static java.nio.charset.StandardCharsets.UTF_8;
31+
import java.util.ArrayList;
3132
import static java.util.Collections.nCopies;
3233
import java.util.Comparator;
3334
import java.util.List;
3435
import java.util.Random;
36+
import java.util.concurrent.ExecutionException;
37+
import java.util.concurrent.ExecutorService;
38+
import java.util.concurrent.Executors;
39+
import java.util.concurrent.Future;
40+
import static java.util.concurrent.TimeUnit.SECONDS;
41+
import java.util.concurrent.TimeoutException;
42+
import java.util.concurrent.atomic.AtomicBoolean;
43+
import static java.util.stream.Collectors.toList;
44+
import static java.util.stream.IntStream.range;
3545
import org.agrona.concurrent.UnsafeBuffer;
3646
import static org.hamcrest.CoreMatchers.is;
3747
import static org.hamcrest.CoreMatchers.not;
3848
import static org.hamcrest.CoreMatchers.notNullValue;
3949
import static org.hamcrest.CoreMatchers.nullValue;
4050
import static org.hamcrest.MatcherAssert.assertThat;
51+
import org.hamcrest.Matchers;
4152
import static org.hamcrest.Matchers.hasSize;
4253
import static org.hamcrest.collection.IsEmptyCollection.empty;
4354
import org.junit.After;
@@ -47,6 +58,7 @@
4758
import org.junit.Rule;
4859
import org.junit.Test;
4960
import org.junit.rules.TemporaryFolder;
61+
import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL;
5062
import org.lmdbjava.Dbi.DbFullException;
5163
import static org.lmdbjava.DbiFlags.MDB_CREATE;
5264
import static org.lmdbjava.DbiFlags.MDB_DUPSORT;
@@ -81,7 +93,7 @@ public void before() throws IOException {
8193
final File path = tmp.newFile();
8294
env = create()
8395
.setMapSize(MEBIBYTES.toBytes(64))
84-
.setMaxReaders(1)
96+
.setMaxReaders(2)
8597
.setMaxDbs(2)
8698
.open(path, MDB_NOSUBDIR);
8799
}
@@ -127,6 +139,51 @@ public void dbOpenMaxDatabases() {
127139
env.openDbi("db3 fails", MDB_CREATE);
128140
}
129141

142+
@Test
143+
public void dbiWithComparatorThreadSafety() {
144+
final Dbi<ByteBuffer> db = env.openDbi(DB_1, PROXY_OPTIMAL::compare,
145+
MDB_CREATE);
146+
147+
final List<Integer> keys = range(0, 1000).boxed().collect(toList());
148+
149+
final ExecutorService pool = Executors.newCachedThreadPool();
150+
final AtomicBoolean proceed = new AtomicBoolean(true);
151+
final Future<?> reader = pool.submit(() -> {
152+
while (proceed.get()) {
153+
try (Txn<ByteBuffer> txn = env.txnRead()) {
154+
db.get(txn, bb(50));
155+
}
156+
}
157+
});
158+
159+
for (final Integer key : keys) {
160+
try (Txn<ByteBuffer> txn = env.txnWrite()) {
161+
db.put(txn, bb(key), bb(3));
162+
txn.commit();
163+
}
164+
}
165+
166+
try (Txn<ByteBuffer> txn = env.txnRead()) {
167+
final CursorIterator<ByteBuffer> iter = db.iterate(txn);
168+
169+
final List<Integer> result = new ArrayList<>();
170+
while (iter.hasNext()) {
171+
result.add(iter.next().key().getInt());
172+
}
173+
174+
assertThat(result, Matchers.contains(keys.toArray(new Integer[0])));
175+
}
176+
177+
proceed.set(false);
178+
try {
179+
reader.get(1, SECONDS);
180+
pool.shutdown();
181+
pool.awaitTermination(1, SECONDS);
182+
} catch (ExecutionException | InterruptedException | TimeoutException e) {
183+
throw new IllegalStateException(e);
184+
}
185+
}
186+
130187
@Test
131188
public void drop() {
132189
final Dbi<ByteBuffer> db = env.openDbi(DB_1, MDB_CREATE);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy