Skip to content

Commit d5e6651

Browse files
Merge branch '196-query-build-fail-fatal' into 'dev'
Guard any Store pointer access with open check See merge request objectbox/objectbox-java!128
2 parents 4a36ccb + 40be3e6 commit d5e6651

File tree

7 files changed

+55
-69
lines changed

7 files changed

+55
-69
lines changed

objectbox-java/src/main/java/io/objectbox/Box.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ public long panicModeRemoveAll() {
574574
* Returns a builder to create queries for Object matching supplied criteria.
575575
*/
576576
public QueryBuilder<T> query() {
577-
return new QueryBuilder<>(this, store.internalHandle(), store.getDbName(entityClass));
577+
return new QueryBuilder<>(this, store.getNativeStore(), store.getDbName(entityClass));
578578
}
579579

580580
/**

objectbox-java/src/main/java/io/objectbox/BoxStore.java

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ public static boolean isSyncServerAvailable() {
220220

221221
private final File directory;
222222
private final String canonicalPath;
223-
private final long handle;
223+
/** Reference to the native store. Should probably get through {@link #getNativeStore()} instead. */
224+
private long handle;
224225
private final Map<Class<?>, String> dbNameByClass = new HashMap<>();
225226
private final Map<Class<?>, Integer> entityTypeIdByClass = new HashMap<>();
226227
private final Map<Class<?>, EntityInfo<?>> propertiesByClass = new HashMap<>();
@@ -467,11 +468,12 @@ public static long sysProcStatusKb(String key) {
467468
* @return 0 if the size could not be determined (does not throw unless this store was already closed)
468469
*/
469470
public long sizeOnDisk() {
470-
checkOpen();
471-
return nativeSizeOnDisk(handle);
471+
return nativeSizeOnDisk(getNativeStore());
472472
}
473473

474474
/**
475+
* Closes this if this is finalized.
476+
* <p>
475477
* Explicitly call {@link #close()} instead to avoid expensive finalization.
476478
*/
477479
@SuppressWarnings("deprecation") // finalize()
@@ -481,8 +483,11 @@ protected void finalize() throws Throwable {
481483
super.finalize();
482484
}
483485

486+
/**
487+
* Verifies this has not been {@link #close() closed}.
488+
*/
484489
private void checkOpen() {
485-
if (closed) {
490+
if (isClosed()) {
486491
throw new IllegalStateException("Store is closed");
487492
}
488493
}
@@ -533,13 +538,12 @@ <T> EntityInfo<T> getEntityInfo(Class<T> entityClass) {
533538
*/
534539
@Internal
535540
public Transaction beginTx() {
536-
checkOpen();
537541
// Because write TXs are typically not cached, initialCommitCount is not as relevant than for read TXs.
538542
int initialCommitCount = commitCount;
539543
if (debugTxWrite) {
540544
System.out.println("Begin TX with commit count " + initialCommitCount);
541545
}
542-
long nativeTx = nativeBeginTx(handle);
546+
long nativeTx = nativeBeginTx(getNativeStore());
543547
if (nativeTx == 0) throw new DbException("Could not create native transaction");
544548

545549
Transaction tx = new Transaction(this, nativeTx, initialCommitCount);
@@ -555,7 +559,6 @@ public Transaction beginTx() {
555559
*/
556560
@Internal
557561
public Transaction beginReadTx() {
558-
checkOpen();
559562
// initialCommitCount should be acquired before starting the tx. In race conditions, there is a chance the
560563
// commitCount is already outdated. That's OK because it only gives a false positive for an TX being obsolete.
561564
// In contrast, a false negative would make a TX falsely not considered obsolete, and thus readers would not be
@@ -565,7 +568,7 @@ public Transaction beginReadTx() {
565568
if (debugTxRead) {
566569
System.out.println("Begin read TX with commit count " + initialCommitCount);
567570
}
568-
long nativeTx = nativeBeginReadTx(handle);
571+
long nativeTx = nativeBeginReadTx(getNativeStore());
569572
if (nativeTx == 0) throw new DbException("Could not create native read transaction");
570573

571574
Transaction tx = new Transaction(this, nativeTx, initialCommitCount);
@@ -575,6 +578,9 @@ public Transaction beginReadTx() {
575578
return tx;
576579
}
577580

581+
/**
582+
* If this was {@link #close() closed}.
583+
*/
578584
public boolean isClosed() {
579585
return closed;
580586
}
@@ -584,8 +590,7 @@ public boolean isClosed() {
584590
* If true the schema is not updated and write transactions are not possible.
585591
*/
586592
public boolean isReadOnly() {
587-
checkOpen();
588-
return nativeIsReadOnly(handle);
593+
return nativeIsReadOnly(getNativeStore());
589594
}
590595

591596
/**
@@ -621,7 +626,9 @@ public void close() {
621626
}
622627
if (handle != 0) { // failed before native handle was created?
623628
nativeDelete(handle);
624-
// TODO set handle to 0 and check in native methods
629+
// The Java API has open checks, but just in case re-set the handle so any native methods will
630+
// not crash due to an invalid pointer.
631+
handle = 0;
625632
}
626633

627634
// When running the full unit test suite, we had 100+ threads before, hope this helps:
@@ -665,7 +672,7 @@ private void checkThreadTermination() {
665672
* Note: If false is returned, any number of files may have been deleted before the failure happened.
666673
*/
667674
public boolean deleteAllFiles() {
668-
if (!closed) {
675+
if (!isClosed()) {
669676
throw new IllegalStateException("Store must be closed");
670677
}
671678
return deleteAllFiles(directory);
@@ -765,8 +772,7 @@ public static boolean deleteAllFiles(@Nullable File baseDirectoryOrNull, @Nullab
765772
* </ul>
766773
*/
767774
public void removeAllObjects() {
768-
checkOpen();
769-
nativeDropAllData(handle);
775+
nativeDropAllData(getNativeStore());
770776
}
771777

772778
@Internal
@@ -1049,8 +1055,7 @@ public <R> void callInTxAsync(final Callable<R> callable, @Nullable final TxCall
10491055
* @return String that is typically logged by the application.
10501056
*/
10511057
public String diagnose() {
1052-
checkOpen();
1053-
return nativeDiagnose(handle);
1058+
return nativeDiagnose(getNativeStore());
10541059
}
10551060

10561061
/**
@@ -1069,13 +1074,11 @@ public long validate(long pageLimit, boolean checkLeafLevel) {
10691074
if (pageLimit < 0) {
10701075
throw new IllegalArgumentException("pageLimit must be zero or positive");
10711076
}
1072-
checkOpen();
1073-
return nativeValidate(handle, pageLimit, checkLeafLevel);
1077+
return nativeValidate(getNativeStore(), pageLimit, checkLeafLevel);
10741078
}
10751079

10761080
public int cleanStaleReadTransactions() {
1077-
checkOpen();
1078-
return nativeCleanStaleReadTransactions(handle);
1081+
return nativeCleanStaleReadTransactions(getNativeStore());
10791082
}
10801083

10811084
/**
@@ -1090,11 +1093,6 @@ public void closeThreadResources() {
10901093
// activeTx is cleaned up in finally blocks, so do not free them here
10911094
}
10921095

1093-
@Internal
1094-
long internalHandle() {
1095-
return handle;
1096-
}
1097-
10981096
/**
10991097
* A {@link io.objectbox.reactive.DataObserver} can be subscribed to data changes using the returned builder.
11001098
* The observer is supplied via {@link SubscriptionBuilder#observer(DataObserver)} and will be notified once a
@@ -1146,8 +1144,7 @@ public String startObjectBrowser() {
11461144
@Nullable
11471145
public String startObjectBrowser(int port) {
11481146
verifyObjectBrowserNotRunning();
1149-
checkOpen();
1150-
String url = nativeStartObjectBrowser(handle, null, port);
1147+
String url = nativeStartObjectBrowser(getNativeStore(), null, port);
11511148
if (url != null) {
11521149
objectBrowserPort = port;
11531150
}
@@ -1158,14 +1155,13 @@ public String startObjectBrowser(int port) {
11581155
@Nullable
11591156
public String startObjectBrowser(String urlToBindTo) {
11601157
verifyObjectBrowserNotRunning();
1161-
checkOpen();
11621158
int port;
11631159
try {
11641160
port = new URL(urlToBindTo).getPort(); // Gives -1 if not available
11651161
} catch (MalformedURLException e) {
11661162
throw new RuntimeException("Can not start Object Browser at " + urlToBindTo, e);
11671163
}
1168-
String url = nativeStartObjectBrowser(handle, urlToBindTo, 0);
1164+
String url = nativeStartObjectBrowser(getNativeStore(), urlToBindTo, 0);
11691165
if (url != null) {
11701166
objectBrowserPort = port;
11711167
}
@@ -1178,8 +1174,7 @@ public synchronized boolean stopObjectBrowser() {
11781174
throw new IllegalStateException("ObjectBrowser has not been started before");
11791175
}
11801176
objectBrowserPort = 0;
1181-
checkOpen();
1182-
return nativeStopObjectBrowser(handle);
1177+
return nativeStopObjectBrowser(getNativeStore());
11831178
}
11841179

11851180
@Experimental
@@ -1204,8 +1199,7 @@ private void verifyObjectBrowserNotRunning() {
12041199
* This for example allows central error handling or special logging for database-related exceptions.
12051200
*/
12061201
public void setDbExceptionListener(@Nullable DbExceptionListener dbExceptionListener) {
1207-
checkOpen();
1208-
nativeSetDbExceptionListener(handle, dbExceptionListener);
1202+
nativeSetDbExceptionListener(getNativeStore(), dbExceptionListener);
12091203
}
12101204

12111205
@Internal
@@ -1234,18 +1228,19 @@ public TxCallback<?> internalFailedReadTxAttemptCallback() {
12341228
}
12351229

12361230
void setDebugFlags(int debugFlags) {
1237-
checkOpen();
1238-
nativeSetDebugFlags(handle, debugFlags);
1231+
nativeSetDebugFlags(getNativeStore(), debugFlags);
12391232
}
12401233

12411234
long panicModeRemoveAllObjects(int entityId) {
1242-
checkOpen();
1243-
return nativePanicModeRemoveAllObjects(handle, entityId);
1235+
return nativePanicModeRemoveAllObjects(getNativeStore(), entityId);
12441236
}
12451237

12461238
/**
1247-
* If you want to use the same ObjectBox store using the C API, e.g. via JNI, this gives the required pointer,
1248-
* which you have to pass on to obx_store_wrap().
1239+
* Gets the reference to the native store. Can be used with the C API to use the same store, e.g. via JNI, by
1240+
* passing it on to {@code obx_store_wrap()}.
1241+
* <p>
1242+
* Throws if the store is closed.
1243+
* <p>
12491244
* The procedure is like this:<br>
12501245
* 1) you create a BoxStore on the Java side<br>
12511246
* 2) you call this method to get the native store pointer<br>

objectbox-java/src/main/java/io/objectbox/InternalAccess.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@
2323

2424
@Internal
2525
public class InternalAccess {
26-
public static <T> Cursor<T> getReader(Box<T> box) {
27-
return box.getReader();
28-
}
29-
30-
public static long getHandle(BoxStore boxStore) {
31-
return boxStore.internalHandle();
32-
}
3326

3427
public static Transaction getActiveTx(BoxStore boxStore) {
3528
Transaction tx = boxStore.activeTx.get();
@@ -40,10 +33,6 @@ public static Transaction getActiveTx(BoxStore boxStore) {
4033
return tx;
4134
}
4235

43-
public static long getHandle(Cursor reader) {
44-
return reader.internalHandle();
45-
}
46-
4736
public static long getHandle(Transaction tx) {
4837
return tx.internalHandle();
4938
}
@@ -52,10 +41,6 @@ public static void setSyncClient(BoxStore boxStore, @Nullable SyncClient syncCli
5241
boxStore.setSyncClient(syncClient);
5342
}
5443

55-
public static <T> void releaseReader(Box<T> box, Cursor<T> reader) {
56-
box.releaseReader(reader);
57-
}
58-
5944
public static <T> Cursor<T> getWriter(Box<T> box) {
6045
return box.getWriter();
6146
}
@@ -68,10 +53,6 @@ public static <T> long getActiveTxCursorHandle(Box<T> box) {
6853
return box.getActiveTxCursor().internalHandle();
6954
}
7055

71-
public static <T> void releaseWriter(Box<T> box, Cursor<T> writer) {
72-
box.releaseWriter(writer);
73-
}
74-
7556
public static <T> void commitWriter(Box<T> box, Cursor<T> writer) {
7657
box.commitWriter(writer);
7758
}

objectbox-java/src/main/java/io/objectbox/sync/SyncClientImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class SyncClientImpl implements SyncClient {
4848
this.serverUrl = builder.url;
4949
this.connectivityMonitor = builder.platform.getConnectivityMonitor();
5050

51-
long boxStoreHandle = InternalAccess.getHandle(builder.boxStore);
51+
long boxStoreHandle = builder.boxStore.getNativeStore();
5252
long handle = nativeCreate(boxStoreHandle, serverUrl, builder.trustedCertPaths);
5353
if (handle == 0) {
5454
throw new RuntimeException("Failed to create sync client: handle is zero.");

objectbox-java/src/main/java/io/objectbox/sync/server/SyncServerImpl.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import javax.annotation.Nullable;
44

5-
import io.objectbox.InternalAccess;
65
import io.objectbox.annotation.apihint.Internal;
76
import io.objectbox.sync.SyncCredentials;
87
import io.objectbox.sync.SyncCredentialsToken;
@@ -24,7 +23,7 @@ public class SyncServerImpl implements SyncServer {
2423
SyncServerImpl(SyncServerBuilder builder) {
2524
this.url = builder.url;
2625

27-
long storeHandle = InternalAccess.getHandle(builder.boxStore);
26+
long storeHandle = builder.boxStore.getNativeStore();
2827
long handle = nativeCreate(storeHandle, url, builder.certificatePath);
2928
if (handle == 0) {
3029
throw new RuntimeException("Failed to create sync server: handle is zero.");

tests/objectbox-java-test/src/test/java/io/objectbox/BoxStoreTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public void testClose() {
136136
assertThrowsStoreIsClosed(() -> store.subscribe(TestEntity.class));
137137
assertThrowsStoreIsClosed(store::startObjectBrowser);
138138
assertThrowsStoreIsClosed(() -> store.startObjectBrowser(12345));
139-
assertThrowsStoreIsClosed(() -> store.startObjectBrowser(""));
139+
assertThrowsStoreIsClosed(() -> store.startObjectBrowser("http://127.0.0.1"));
140140
// assertThrowsStoreIsClosed(store::stopObjectBrowser); // Requires mocking, not testing for now.
141141
assertThrowsStoreIsClosed(() -> store.setDbExceptionListener(null));
142142
// Internal thread pool is shut down as part of closing store, should no longer accept new work.

tests/objectbox-java-test/src/test/java/io/objectbox/query/QueryTest.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,27 @@
1616

1717
package io.objectbox.query;
1818

19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Date;
22+
import java.util.List;
23+
1924
import io.objectbox.Box;
2025
import io.objectbox.BoxStore;
2126
import io.objectbox.BoxStoreBuilder;
2227
import io.objectbox.TestEntity;
2328
import io.objectbox.TestEntity_;
2429
import io.objectbox.TestUtils;
30+
import io.objectbox.config.DebugFlags;
2531
import io.objectbox.exception.DbExceptionListener;
2632
import io.objectbox.exception.NonUniqueResultException;
27-
import io.objectbox.config.DebugFlags;
2833
import io.objectbox.query.QueryBuilder.StringOrder;
2934
import io.objectbox.relation.MyObjectBox;
3035
import io.objectbox.relation.Order;
3136
import io.objectbox.relation.Order_;
3237
import org.junit.Test;
3338
import org.junit.function.ThrowingRunnable;
3439

35-
import java.util.ArrayList;
36-
import java.util.Arrays;
37-
import java.util.Date;
38-
import java.util.List;
39-
4040
import static io.objectbox.TestEntity_.simpleBoolean;
4141
import static io.objectbox.TestEntity_.simpleByteArray;
4242
import static io.objectbox.TestEntity_.simpleFloat;
@@ -58,6 +58,17 @@
5858

5959
public class QueryTest extends AbstractQueryTest {
6060

61+
@Test
62+
public void createIfStoreClosed_throws() {
63+
store.close();
64+
65+
IllegalStateException ex = assertThrows(
66+
IllegalStateException.class,
67+
() -> box.query()
68+
);
69+
assertEquals("Store is closed", ex.getMessage());
70+
}
71+
6172
@Test
6273
public void testBuild() {
6374
try (Query<TestEntity> query = box.query().build()) {

0 commit comments

Comments
 (0)