Skip to content

Commit 96763d5

Browse files
committed
Transaction: inactive ones are always safe to destroy #282
1 parent cf78072 commit 96763d5

File tree

1 file changed

+18
-13
lines changed

1 file changed

+18
-13
lines changed

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,25 +129,30 @@ public synchronized void close() {
129129
nativeDestroy(transaction);
130130
} else {
131131
String threadType = isOwnerThread ? "owner thread" : "non-owner thread";
132+
String activeInfo = isActive() ? " (active TX)" : " (inactive TX)";
132133
if (readOnly) {
133-
// Minor leak, but still print it so we can check logs: it should only happen occasionally.
134-
// Note that BoxStore.close() won't (briefly) wait on this transaction as isActive() already returns
135-
// false (and unregisterTransaction(this) was called) at this point.
134+
// Minor leak if TX is active, but still log so we can check logs that it only happens occasionally.
135+
// We cannot rely on the native and Java stores waiting briefly for read transactions.
136136
System.out.println("Info: closing read transaction after store was closed (should be avoided) in " +
137-
threadType);
137+
threadType + activeInfo);
138138
System.out.flush();
139+
140+
if (!isActive()) { // Note: call "isActive()" for fresh value (do not cache it)
141+
nativeDestroy(transaction);
142+
}
139143
} else { // write transaction
140144
System.out.println("WARN: closing write transaction after store was closed (must be avoided) in " +
141-
threadType);
145+
threadType + activeInfo);
142146
System.out.flush();
143-
if (store.isNativeStoreDestroyed()) {
144-
// This is an internal validation: if this is a write-TX,
147+
if (isActive() && store.isNativeStoreDestroyed()) { // Note: call "isActive()" for fresh value
148+
// This is an internal validation: if this is an active write-TX,
145149
// the (native) store will always wait for it, so it must not be destroyed yet.
146-
// If this ever happens, the above assumption is wrong, and it probably prevents a SIGSEGV.
147-
// Note that BoxStore.close() won't (briefly) wait on this transaction as isActive() already
148-
// returns false (and unregisterTransaction(this) was called).
149-
throw new IllegalStateException("Cannot close write transaction for an already closed store");
150+
// If this ever happens, the above assumption is wrong, and throwing likely prevents a SIGSEGV.
151+
throw new IllegalStateException(
152+
"Internal error: cannot close active write transaction for an already destroyed store");
150153
}
154+
// Note: inactive transactions are always safe to destroy, regardless of store state and thread.
155+
// Note: the current native impl panics if the transaction is active AND created in another thread.
151156
nativeDestroy(transaction);
152157
}
153158
}
@@ -223,8 +228,8 @@ public BoxStore getStore() {
223228
}
224229

225230
/**
226-
* A transaction is active after it was created until {@link #close()} or {@link #abort()} or for a write
227-
* transaction also until {@link #commit()} is called.
231+
* A transaction is active after it was created until {@link #close()}, {@link #abort()}, or, for write
232+
* transactions only, {@link #commit()} is called.
228233
*
229234
* @return If this transaction is active.
230235
*/

0 commit comments

Comments
 (0)