@@ -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