Skip to content

Commit 3e6c101

Browse files
committed
Transaction: close write transactions even if store is closing #282
Otherwise, the native store would wait forever for the write transaction
1 parent f5ff1eb commit 3e6c101

File tree

2 files changed

+39
-1
lines changed

2 files changed

+39
-1
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,11 @@ public static boolean isSyncServerAvailable() {
244244
private final PrintStream errorOutputStream;
245245
private final File directory;
246246
private final String canonicalPath;
247+
247248
/** Reference to the native store. Should probably get through {@link #getNativeStore()} instead. */
248249
volatile private long handle;
250+
volatile private boolean nativeStoreDestroyed = false;
251+
249252
private final Map<Class<?>, String> dbNameByClass = new HashMap<>();
250253
private final Map<Class<?>, Integer> entityTypeIdByClass = new HashMap<>();
251254
private final Map<Class<?>, EntityInfo<?>> propertiesByClass = new HashMap<>();
@@ -724,6 +727,7 @@ public void close() {
724727
handle = 0;
725728
if (handleToDelete != 0) { // failed before native handle was created?
726729
nativeDelete(handleToDelete);
730+
nativeStoreDestroyed = true;
727731
}
728732
}
729733
}
@@ -1414,6 +1418,18 @@ public boolean isNativeStoreClosed() {
14141418
return handle == 0;
14151419
}
14161420

1421+
/**
1422+
* For internal use only. This API might change or be removed with a future release.
1423+
* <p>
1424+
* Returns if the native Store was completely destroyed.
1425+
* <p>
1426+
* This is {@code true} shortly after {@link #close()} was called and {@link #isClosed()} returns {@code true}.
1427+
*/
1428+
@Internal
1429+
public boolean isNativeStoreDestroyed() {
1430+
return nativeStoreDestroyed;
1431+
}
1432+
14171433
/**
14181434
* Returns the {@link SyncClient} associated with this store. To create one see {@link io.objectbox.sync.Sync Sync}.
14191435
*/

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ public synchronized void close() {
101101
closed = true;
102102
store.unregisterTransaction(this);
103103

104-
if (!nativeIsOwnerThread(transaction)) {
104+
boolean isOwnerThread = nativeIsOwnerThread(transaction);
105+
if (!isOwnerThread) {
105106
boolean isActive = nativeIsActive(transaction);
106107
boolean isRecycled = nativeIsRecycled(transaction);
107108
if (isActive || isRecycled) {
@@ -126,6 +127,27 @@ public synchronized void close() {
126127
// TODO not destroying is probably only a small leak on rare occasions, but still could be fixed
127128
if (!store.isNativeStoreClosed()) {
128129
nativeDestroy(transaction);
130+
} else {
131+
String threadType = isOwnerThread ? "owner thread" : "non-owner thread";
132+
if (readOnly) {
133+
// Minor leak, but still print it so we can check logs: it should only happen occasionally.
134+
// We cannot rely on the store waiting for read transactions; it only waits for a limited time.
135+
System.out.println("Info: closing read transaction after store was closed (should be avoided) in " +
136+
threadType);
137+
System.out.flush();
138+
} else { // write transaction
139+
System.out.println("WARN: closing write transaction after store was closed (must be avoided) in " +
140+
threadType);
141+
System.out.flush();
142+
if (store.isNativeStoreDestroyed()) {
143+
// This is an internal validation: if this is a write-TX,
144+
// the (native) store will always wait for it, so it must not be destroyed yet.
145+
// If this ever happens, the above assumption is wrong, and it probably prevents a SIGSEGV.
146+
throw new IllegalStateException(
147+
"Cannot destroy write transaction for an already destroyed store");
148+
}
149+
nativeDestroy(transaction);
150+
}
129151
}
130152
}
131153
}

0 commit comments

Comments
 (0)