public void commitAndClose() { commit(); close(); }
@Override protected void finalize() throws Throwable { // Committed & aborted transactions are fine: remaining native resources are not expensive if (!closed && nativeIsActive(transaction)) { // TODO what about recycled state? System.err.println("Transaction was not finished (initial commit count: " + initialCommitCount + ")."); if (creationThrowable != null) { System.err.println("Transaction was initially created here:"); creationThrowable.printStackTrace(); } System.err.flush(); } close(); super.finalize(); }
/** * Runs the given runnable inside a read(-only) transaction. Multiple read transactions can occur at the same time. * This allows multiple read operations (gets) using a single consistent state of data. * Also, for a high number of read operations (thousands, e.g. in loops), * it is advised to run them in a single read transaction for efficiency reasons. */ public void runInReadTx(Runnable runnable) { Transaction tx = activeTx.get(); // Only if not already set, allowing to call it recursively with first (outer) TX if (tx == null) { tx = beginReadTx(); activeTx.set(tx); try { runnable.run(); } finally { activeTx.remove(); // TODO That's rather a quick fix, replace with a more general solution // (that could maybe be a TX listener with abort callback?) for (Box box : boxes.values()) { box.readTxFinished(tx); } tx.close(); } } else { runnable.run(); } }
/** * Like {@link #runInTx(Runnable)}, but allows returning a value and throwing an exception. */ public <R> R callInTx(Callable<R> callable) throws Exception { Transaction tx = activeTx.get(); // Only if not already set, allowing to call it recursively with first (outer) TX if (tx == null) { tx = beginTx(); activeTx.set(tx); try { R result = callable.call(); tx.commit(); return result; } finally { activeTx.remove(); tx.close(); } } else { if (tx.isReadOnly()) { throw new IllegalStateException("Cannot start a transaction while a read only transaction is active"); } return callable.call(); } }
/** * Runs the given runnable inside a transaction. * <p> * Efficiency notes: it is advised to run multiple puts in a transaction because each commit requires an expensive * disk synchronization. */ public void runInTx(Runnable runnable) { Transaction tx = activeTx.get(); // Only if not already set, allowing to call it recursively with first (outer) TX if (tx == null) { tx = beginTx(); activeTx.set(tx); try { runnable.run(); tx.commit(); } finally { activeTx.remove(); tx.close(); } } else { if (tx.isReadOnly()) { throw new IllegalStateException("Cannot start a transaction while a read only transaction is active"); } runnable.run(); } }
void releaseWriter(Cursor<T> cursor) { // NOP if TX is ongoing if (activeTxCursor.get() == null) { Transaction tx = cursor.getTx(); if (!tx.isClosed()) { cursor.close(); tx.abort(); tx.close(); } } }
@Override public void run() { latchBeforeBeginTx.countDown(); Transaction tx2 = store.beginTx(); latchAfterBeginTx.countDown(); tx2.close(); } }.start();
Cursor<T> getWriter() { Cursor<T> cursor = getActiveTxCursor(); if (cursor != null) { return cursor; } else { Transaction tx = store.beginTx(); try { return tx.createCursor(entityClass); } catch (RuntimeException e) { tx.close(); throw e; } } }
@Test public void testWriteTxBlocksOtherWriteTx() throws InterruptedException { long time = System.currentTimeMillis(); Transaction tx = store.beginTx(); long duration = System.currentTimeMillis() - time; // Usually 0 on desktop final CountDownLatch latchBeforeBeginTx = new CountDownLatch(1); final CountDownLatch latchAfterBeginTx = new CountDownLatch(1); new Thread() { @Override public void run() { latchBeforeBeginTx.countDown(); Transaction tx2 = store.beginTx(); latchAfterBeginTx.countDown(); tx2.close(); } }.start(); assertTrue(latchBeforeBeginTx.await(1, TimeUnit.SECONDS)); long waitTime = 50 + duration * 10; assertFalse(latchAfterBeginTx.await(waitTime, TimeUnit.MILLISECONDS)); tx.close(); assertTrue(latchAfterBeginTx.await(waitTime, TimeUnit.MILLISECONDS)); }
@Test(expected = IllegalArgumentException.class) public void testPutEntityWithInvalidId() { TestEntity entity = new TestEntity(); entity.setId(777); Transaction transaction = store.beginTx(); Cursor<TestEntity> cursor = transaction.createCursor(TestEntity.class); try { cursor.put(entity); } finally { cursor.close(); transaction.close(); } }
@Test public void testClose() { Transaction tx = store.beginReadTx(); try { Cursor<TestEntity> cursor = tx.createCursor(TestEntity.class); assertFalse(cursor.isClosed()); cursor.close(); assertTrue(cursor.isClosed()); // Double close should be fine cursor.close(); } finally { tx.close(); } }
@Test public void testRenew() throws IOException { insertTestEntities("orange"); Transaction transaction = store.beginReadTx(); Cursor<TestEntity> cursor = transaction.createCursor(TestEntity.class); transaction.recycle(); transaction.renew(); cursor.renew(); assertEquals("orange", cursor.get(1).getSimpleString()); cursor.close(); transaction.close(); }
@Test public void testRemove() { Transaction transaction = store.beginTx(); try { KeyValueCursor cursor = transaction.createKeyValueCursor(); cursor.put(1, new byte[]{1, 1, 0, 0}); cursor.put(2, new byte[]{2, 1, 0, 0}); cursor.put(4, new byte[]{4, 1, 0, 0}); assertTrue(cursor.removeAt(2)); // now 4 should be next to 1 assertTrue(cursor.seek(1)); byte[] next = cursor.getNext(); assertNotNull(next); assertTrue(Arrays.equals(new byte[]{4, 1, 0, 0}, next)); } finally { transaction.close(); } }
@Test public void testPutSameIndexValue() { TestEntity entity = new TestEntity(); String value = "lulu321"; entity.setSimpleString(value); Transaction transaction = store.beginTx(); TestEntity read; try { Cursor<TestEntity> cursor = transaction.createCursor(TestEntity.class); long key = cursor.put(entity); // And again entity.setSimpleInt(1977); cursor.put(entity); assertEquals(key, cursor.lookupKeyUsingIndex(9, value)); read = cursor.get(key); cursor.close(); } finally { transaction.close(); } assertEquals(1977, read.getSimpleInt()); assertEquals(value, read.getSimpleString()); }