|
19 | 19 | import org.junit.Test; |
20 | 20 |
|
21 | 21 | import java.util.concurrent.CountDownLatch; |
| 22 | +import java.util.concurrent.ThreadLocalRandom; |
22 | 23 | import java.util.concurrent.TimeUnit; |
23 | 24 |
|
24 | 25 | import io.objectbox.annotation.IndexType; |
@@ -227,24 +228,54 @@ public void testClose() { |
227 | 228 | } |
228 | 229 | } |
229 | 230 |
|
| 231 | + /** |
| 232 | + * Begin the first write-TX and ensure the second one blocks until the first one is closed. |
| 233 | + * A secondary test goal is to check races of a closing TX and a closing store. |
| 234 | + */ |
230 | 235 | @Test |
231 | 236 | public void testWriteTxBlocksOtherWriteTx() throws InterruptedException { |
| 237 | + // To change the likelihood of the TX vs store closing race, close the store using one of 3 different variants. |
| 238 | + // Assign and print the randomly chosen variant beforehand so it does not mess with thread timings later. |
| 239 | + // Warning: test variant 2 only manually, it will close the write-TX from a non-owner thread (in BoxStore.close) |
| 240 | + // where the native database is expected to panic. |
| 241 | + int closeStoreVariant = ThreadLocalRandom.current().nextInt(2 /* 3 - test variant 2 manually, see above */); |
| 242 | + System.out.println("Closing store variant: " + closeStoreVariant); |
| 243 | + |
232 | 244 | long time = System.currentTimeMillis(); |
233 | 245 | Transaction tx = store.beginTx(); |
234 | 246 | long duration = System.currentTimeMillis() - time; // Usually 0 on desktop |
235 | 247 | final CountDownLatch latchBeforeBeginTx = new CountDownLatch(1); |
236 | 248 | final CountDownLatch latchAfterBeginTx = new CountDownLatch(1); |
| 249 | + final CountDownLatch latchCloseStoreInMainThread = closeStoreVariant != 0 ? new CountDownLatch(1) : null; |
237 | 250 | new Thread(() -> { |
238 | 251 | latchBeforeBeginTx.countDown(); |
239 | 252 | Transaction tx2 = store.beginTx(); |
240 | 253 | latchAfterBeginTx.countDown(); |
| 254 | + |
| 255 | + if (closeStoreVariant != 0) { |
| 256 | + try { |
| 257 | + assertTrue(latchCloseStoreInMainThread.await(5, TimeUnit.SECONDS)); |
| 258 | + } catch (InterruptedException e) { |
| 259 | + throw new RuntimeException("Interrupted", e); |
| 260 | + } |
| 261 | + } |
241 | 262 | tx2.close(); |
242 | 263 | }).start(); |
243 | 264 | assertTrue(latchBeforeBeginTx.await(1, TimeUnit.SECONDS)); |
244 | 265 | long waitTime = 100 + duration * 10; |
245 | 266 | assertFalse(latchAfterBeginTx.await(waitTime, TimeUnit.MILLISECONDS)); |
246 | 267 | tx.close(); |
247 | 268 | assertTrue(latchAfterBeginTx.await(waitTime * 2, TimeUnit.MILLISECONDS)); |
| 269 | + |
| 270 | + // closeStoreVariant == 0: not latch waiting, close store when tearing down test |
| 271 | + if (closeStoreVariant == 1) { |
| 272 | + // This variant tries to close the store and the TX at the same time. |
| 273 | + latchCloseStoreInMainThread.countDown(); |
| 274 | + store.close(); // Maybe this runs a tiny bit before the close() in teardown(?) |
| 275 | + } else if (closeStoreVariant == 2) { |
| 276 | + store.close(); // Enforces closing the store before the TX. |
| 277 | + latchCloseStoreInMainThread.countDown(); |
| 278 | + } |
248 | 279 | } |
249 | 280 |
|
250 | 281 | @Test |
|
0 commit comments