Skip to content

Commit 6cb96f4

Browse files
committed
corner case fix for open file check: use separate thread to avoid finalizers that block us (probably only relevant for very special unit test scenarios)
1 parent 54dbf50 commit 6cb96f4

1 file changed

Lines changed: 34 additions & 5 deletions

File tree

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public class BoxStore implements Closeable {
7171

7272
/** Currently used DB dirs with values from {@link #getCanonicalPath(File)}. */
7373
private static final Set<String> openFiles = new HashSet<>();
74+
private static volatile Thread openFilesCheckerThread;
7475

7576
/**
7677
* Convenience singleton instance which gets set up using {@link BoxStoreBuilder#buildDefault()}.
@@ -260,7 +261,7 @@ static String getCanonicalPath(File directory) {
260261
}
261262
}
262263

263-
private static void verifyNotAlreadyOpen(String canonicalPath) {
264+
static void verifyNotAlreadyOpen(String canonicalPath) {
264265
synchronized (openFiles) {
265266
isFileOpen(canonicalPath); // for retries
266267
if (!openFiles.add(canonicalPath)) {
@@ -271,15 +272,44 @@ private static void verifyNotAlreadyOpen(String canonicalPath) {
271272
}
272273

273274
/** Also retries up to 500ms to improve GC race condition situation. */
274-
private static boolean isFileOpen(String canonicalPath) {
275+
static boolean isFileOpen(final String canonicalPath) {
276+
synchronized (openFiles) {
277+
if (!openFiles.contains(canonicalPath)) return false;
278+
}
279+
if(openFilesCheckerThread == null || !openFilesCheckerThread.isAlive()) {
280+
// Use a thread to avoid finalizers that block us
281+
openFilesCheckerThread = new Thread() {
282+
@Override
283+
public void run() {
284+
isFileOpenSync(canonicalPath, true);
285+
openFilesCheckerThread = null; // Clean ref to itself
286+
}
287+
};
288+
openFilesCheckerThread.setDaemon(true);
289+
openFilesCheckerThread.start();
290+
try {
291+
openFilesCheckerThread.join(500);
292+
} catch (InterruptedException e) {
293+
e.printStackTrace();
294+
}
295+
} else {
296+
// Waiting for finalizers are blocking; only do that in the thread ^
297+
return isFileOpenSync(canonicalPath, false);
298+
}
299+
synchronized (openFiles) {
300+
return openFiles.contains(canonicalPath);
301+
}
302+
}
303+
304+
static boolean isFileOpenSync(String canonicalPath, boolean runFinalization) {
275305
synchronized (openFiles) {
276306
int tries = 0;
277307
while (tries < 5 && openFiles.contains(canonicalPath)) {
278308
tries++;
279309
System.gc();
280-
System.runFinalization();
310+
if (runFinalization && tries > 1) System.runFinalization();
281311
System.gc();
282-
System.runFinalization();
312+
if (runFinalization && tries > 1) System.runFinalization();
283313
try {
284314
openFiles.wait(100);
285315
} catch (InterruptedException e) {
@@ -513,7 +543,6 @@ public static boolean deleteAllFiles(File objectStoreDirectory) {
513543
* @return true if the directory 1) was deleted successfully OR 2) did not exist in the first place.
514544
* Note: If false is returned, any number of files may have been deleted before the failure happened.
515545
* @throws IllegalStateException if the given name is still used by a open {@link BoxStore}.
516-
*
517546
*/
518547
public static boolean deleteAllFiles(Object androidContext, @Nullable String customDbNameOrNull) {
519548
File dbDir = BoxStoreBuilder.getAndroidDbDir(androidContext, customDbNameOrNull);

0 commit comments

Comments
 (0)