Skip to content

Commit a8619d6

Browse files
BoxStore: close active tx cursors if write tx runnable/callable throws #286
1 parent bc28da4 commit a8619d6

3 files changed

Lines changed: 25 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ Notable changes to the ObjectBox Java library.
44

55
For more insights into what changed in the ObjectBox C++ core, [check the ObjectBox C changelog](https://github.com/objectbox/objectbox-c/blob/main/CHANGELOG.md).
66

7-
## 5.0.2 - in development
7+
## Next release
8+
9+
- `BoxStore.runInTx` and `callInTx` close a write cursor even if the runnable or callable throws. This would previously
10+
result in cursor not closed warnings when the cursor was closed by the finalizer daemon.
811

912
## 5.0.1 - 2025-09-30
1013

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.Iterator;
2424
import java.util.List;
2525
import java.util.Map;
26-
import java.util.concurrent.Callable;
2726

2827
import javax.annotation.Nullable;
2928
import javax.annotation.concurrent.ThreadSafe;
@@ -173,20 +172,16 @@ public void closeThreadResources() {
173172
}
174173
}
175174

176-
void txCommitted(Transaction tx) {
177-
// Thread local readers will be renewed on next get, so we do not need clean them up
178-
179-
Cursor<T> cursor = activeTxCursor.get();
180-
if (cursor != null) {
181-
activeTxCursor.remove();
182-
cursor.close();
183-
}
184-
}
185-
186175
/**
187-
* Called by {@link BoxStore#callInReadTx(Callable)} - does not throw so caller does not need try/finally.
176+
* If there is one, and it was created using the given {@code tx}, removes and closes the {@link #activeTxCursor}
177+
* for the current thread.
178+
* <p>
179+
* This should be called before the active transaction is closed to clean up native resources.
180+
* <p>
181+
* Note: {@link #threadLocalReader} is either renewed by the next call to {@link #getReader()} or cleaned up by
182+
* {@link #closeThreadResources()}.
188183
*/
189-
void readTxFinished(Transaction tx) {
184+
void closeActiveTxCursorForCurrentThread(Transaction tx) {
190185
Cursor<T> cursor = activeTxCursor.get();
191186
if (cursor != null && cursor.getTx() == tx) {
192187
activeTxCursor.remove();

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -926,15 +926,20 @@ void txCommitted(Transaction tx, @Nullable int[] entityTypeIdsAffected) {
926926
}
927927
}
928928

929-
for (Box<?> box : boxes.values()) {
930-
box.txCommitted(tx);
931-
}
932-
933929
if (entityTypeIdsAffected != null) {
934930
objectClassPublisher.publish(entityTypeIdsAffected);
935931
}
936932
}
937933

934+
/**
935+
* For all boxes, calls {@link Box#closeActiveTxCursorForCurrentThread(Transaction)}.
936+
*/
937+
void closeActiveTxCursorsForCurrentThread(Transaction tx) {
938+
for (Box<?> box : boxes.values()) {
939+
box.closeActiveTxCursorForCurrentThread(tx);
940+
}
941+
}
942+
938943
/**
939944
* Returns a Box for the given type. Objects are put into (and get from) their individual Box.
940945
* <p>
@@ -977,6 +982,7 @@ public void runInTx(Runnable runnable) {
977982
tx.commit();
978983
} finally {
979984
activeTx.remove();
985+
closeActiveTxCursorsForCurrentThread(tx);
980986
tx.close();
981987
}
982988
} else {
@@ -1003,13 +1009,9 @@ public void runInReadTx(Runnable runnable) {
10031009
runnable.run();
10041010
} finally {
10051011
activeTx.remove();
1006-
10071012
// TODO That's rather a quick fix, replace with a more general solution
10081013
// (that could maybe be a TX listener with abort callback?)
1009-
for (Box<?> box : boxes.values()) {
1010-
box.readTxFinished(tx);
1011-
}
1012-
1014+
closeActiveTxCursorsForCurrentThread(tx);
10131015
tx.close();
10141016
}
10151017
} else {
@@ -1089,10 +1091,7 @@ public <T> T callInReadTx(Callable<T> callable) {
10891091

10901092
// TODO That's rather a quick fix, replace with a more general solution
10911093
// (that could maybe be a TX listener with abort callback?)
1092-
for (Box<?> box : boxes.values()) {
1093-
box.readTxFinished(tx);
1094-
}
1095-
1094+
closeActiveTxCursorsForCurrentThread(tx);
10961095
tx.close();
10971096
}
10981097
} else {
@@ -1119,6 +1118,7 @@ public <R> R callInTx(Callable<R> callable) throws Exception {
11191118
return result;
11201119
} finally {
11211120
activeTx.remove();
1121+
closeActiveTxCursorsForCurrentThread(tx);
11221122
tx.close();
11231123
}
11241124
} else {

0 commit comments

Comments
 (0)