Skip to content

Commit 69ed84f

Browse files
Tests: try to confirm close() waiting forever on openFiles locked by checker thread #240
1 parent bc95f51 commit 69ed84f

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,11 @@ static String getCanonicalPath(File directory) {
358358
}
359359
}
360360

361-
static void verifyNotAlreadyOpen(String canonicalPath) {
361+
void verifyNotAlreadyOpen(String canonicalPath) {
362362
synchronized (openFiles) {
363-
isFileOpen(canonicalPath); // for retries
363+
boolean fileOpen = isFileOpen(canonicalPath); // for retries
364364
if (!openFiles.add(canonicalPath)) {
365+
System.out.println("verifyNotAlreadyOpen failed for " + this + " (fileOpen=" + fileOpen + ")");
365366
throw new DbException("Another BoxStore is still open for this directory: " + canonicalPath +
366367
". Hint: for most apps it's recommended to keep a BoxStore for the app's life time.");
367368
}
@@ -377,7 +378,8 @@ static boolean isFileOpen(final String canonicalPath) {
377378
if (checkerThread == null || !checkerThread.isAlive()) {
378379
// Use a thread to avoid finalizers that block us
379380
checkerThread = new Thread(() -> {
380-
isFileOpenSync(canonicalPath, true);
381+
boolean fileOpen = isFileOpenSync(canonicalPath, true);
382+
System.out.println("checkerThread retries completed (fileOpen=" + fileOpen + ")");
381383
BoxStore.openFilesCheckerThread = null; // Clean ref to itself
382384
});
383385
checkerThread.setDaemon(true);
@@ -403,6 +405,7 @@ static boolean isFileOpenSync(String canonicalPath, boolean runFinalization) {
403405
int tries = 0;
404406
while (tries < 5 && openFiles.contains(canonicalPath)) {
405407
tries++;
408+
System.out.println("isFileOpenSync calling System.gc() and System.runFinalization()");
406409
System.gc();
407410
if (runFinalization && tries > 1) System.runFinalization();
408411
System.gc();
@@ -529,6 +532,7 @@ public long getDbSizeOnDisk() {
529532
@SuppressWarnings("deprecation") // finalize()
530533
@Override
531534
protected void finalize() throws Throwable {
535+
System.out.println("finalize() called for " + this + " by " + Thread.currentThread());
532536
close();
533537
super.finalize();
534538
}
@@ -654,6 +658,7 @@ public boolean isReadOnly() {
654658
* are properly finished.
655659
*/
656660
public void close() {
661+
System.out.println("close() called for " + this + " (handle=" + handle + ")");
657662
boolean oldClosedState;
658663
synchronized (this) {
659664
oldClosedState = closed;
@@ -716,6 +721,7 @@ public void close() {
716721
openFiles.notifyAll();
717722
}
718723
}
724+
System.out.println("close() finished for " + this);
719725
}
720726

721727
/** dump thread stacks if pool does not terminate promptly. */

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.objectbox;
1818

19+
import org.junit.Ignore;
1920
import org.junit.Test;
2021
import org.junit.function.ThrowingRunnable;
2122

@@ -174,6 +175,26 @@ public void openSamePath_afterClose_works() {
174175
assertEquals(0, BoxStore.nativeGloballyActiveEntityTypes());
175176
}
176177

178+
@Ignore("Fails due to related issue")
179+
@Test
180+
public void openSamePath_closedByFinalizer_works() {
181+
System.out.println("Removing reference to " + store);
182+
store = null;
183+
184+
// TODO Can't, at least on a JVM 21, confirm that the gc and runFinalization calls are blocking close() from
185+
// accessing openFiles.
186+
// Instead, found another issue where openFiles appears to still contain the db directory on this thread but
187+
// not for the checker thread.
188+
189+
// When another Store is still open using the same path, a checker thread is started that periodically triggers
190+
// garbage collection and finalization in the VM, which here should close store and allow store2 to be opened.
191+
// Note that user code should not rely on this (for ex. finalizers on Android have a timeout, which might be
192+
// exceeded if closing the store takes long, because it needs to wait on transactions to finish), the store
193+
// should be explicitly closed instead.
194+
BoxStore store2 = createBoxStore();
195+
store2.close();
196+
}
197+
177198
@Test
178199
public void testOpenTwoBoxStoreTwoFiles() {
179200
File boxStoreDir2 = new File(boxStoreDir.getAbsolutePath() + "-2");

0 commit comments

Comments
 (0)