com.persistit.Transaction Maven / Gradle / Ivy
Show all versions of akiban-persistit Show documentation
/**
* Copyright © 2005-2012 Akiban Technologies, Inc. All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This program may also be available under different license terms.
* For more information, see www.akiban.com or contact [email protected].
*
* Contributors:
* Akiban Technologies, Inc.
*/
package com.persistit;
import static com.persistit.util.SequencerConstants.COMMIT_FLUSH_A;
import static com.persistit.util.SequencerConstants.COMMIT_FLUSH_B;
import static com.persistit.util.SequencerConstants.COMMIT_FLUSH_C;
import static com.persistit.util.ThreadSequencer.sequence;
import java.nio.ByteBuffer;
import com.persistit.Accumulator.Delta;
import com.persistit.JournalRecord.D0;
import com.persistit.JournalRecord.D1;
import com.persistit.JournalRecord.DR;
import com.persistit.JournalRecord.DT;
import com.persistit.JournalRecord.SR;
import com.persistit.exception.PersistitException;
import com.persistit.exception.PersistitIOException;
import com.persistit.exception.PersistitInterruptedException;
import com.persistit.exception.RollbackException;
import com.persistit.util.Util;
/**
*
* Transaction context for atomic units of work performed by Persistit. Each
* Persistit thread typically uses one Transaction
object for its
* lifetime. Within that context it may execute and commit many transactions.
*
*
* The application determines when to {@link #begin}, {@link #commit},
* {@link #rollback} and {@link #end} transactions. Once a transaction has
* started, no update operation performed within its context will be visible to
* other threads until commit
is performed. At that point, all the
* updates become visible and durable.
*
*
* Persistit implements Multi-Version Concurrency Control (MVCC) with Snapshot Isolation
* for high concurrency and throughput. This protocol is optimistic in
* that competing concurrent transactions run at full speed without locking, but
* can arrive in a state where not all transactions can be allowed to commit
* while preserving correct semantics. In such cases one or more of the
* transactions must roll back (abort) and retry. These topics are covered in
* greater detail below.
*
*
* The Transaction
object itself is not thread-safe and may be
* accessed and used by only one thread at a time. However, the database
* operations being executed within the scope defined by begin
and
* end
are capable of highly concurrent execution.
*
* Lexical Scoping
*
* Applications must manage the scope of any transaction by ensuring that
* whenever the begin
method is called, there is a concluding
* invocation of the end
method. The commit
and
* rollback
methods do not end the scope of a transaction; they
* merely signify the transaction's intended outcome when end
is
* invoked. Applications should follow either the
* try/finally
or the
* TransactionRunnable
pattern to ensure correctness.
*
* Commit Policy
* Persistit provides three policies that determine the durability of a
* transaction after it has executed the commit
method. These are:
*
* - {@link CommitPolicy#HARD}
* - The
commit
method does not return until all updates created
* by the transaction have been written to non-volatile storage (e.g., disk
* storage).
* - {@link CommitPolicy#GROUP}
* - The
commit
method does not return until all updates created
* by the transaction have been written to non-volatile storage. In addition,
* the committing transaction waits briefly in an attempt to recruit other
* concurrently running transactions to write their updates with the same
* physical I/O operation.
* - {@link CommitPolicy#SOFT}
* - The
commit
method returns before the updates have
* been recorded on non-volatile storage. Persistit attempts to write them
* within 100 milliseconds, but this interval is not guaranteed.
*
*
* You can specify a default policy in the Persistit initialization properties
* using the {@value com.persistit.Configuration#COMMIT_POLICY_PROPERTY_NAME}
* property or under program control using
* {@link Persistit#setDefaultTransactionCommitPolicy} . The default policy
* applies to the {@link #commit()} method. You can override the default policy
* using {@link #commit(CommitPolicy)}.
*
*
* HARD and GROUP ensure each transaction is written durably to non-volatile
* storage before the commit
method returns. The difference is that
* GROUP can improve throughput somewhat when many transactions are running
* concurrently because the average number of I/O operations needed to commit N
* transactions can be smaller than N. However, for one or a small number of
* concurrent threads, GROUP reduces throughput because it works by introducing
* a delay to allow other concurrent transactions to commit within a single I/O
* operation.
*
*
* SOFT commits are generally much faster than HARD or GROUP commits, especially
* for single-threaded applications, because the results of numerous update
* operations can be aggregated and written to disk in a much smaller number of
* I/O operations. However, transactions written with the SOFT commit policy are
* not immediately durable and it is possible that the recovered state of a
* database will be missing transactions that were committed shortly before a
* crash.
*
*
* For SOFT commits, the state of the database after restart is such that for
* any committed transaction T, either all or none of its modifications will be
* present in the recovered database. Further, if a transaction T2 reads or
* updates data that was written by any other transaction T1, and if T2 is
* present in the recovered database, then so is T1. Any transaction that was in
* progress, but had not been committed at the time of the failure, is
* guaranteed not to be present in the recovered database. SOFT commits are
* designed to be durable within 100 milliseconds after the commit returns.
* However, this interval is determined by computing the average duration of
* recent I/O operations to predict the completion time of the I/O that will
* write the transaction to disk, and therefore the interval cannot be
* guaranteed.
*
* Optimistic Concurrent Scheduling - MVCC
*
* Persistit schedules concurrently executing transactions optimistically,
* without locking any database records. Instead, Persistit uses a well-known
* protocol called Snapshot Isolation to achieve atomicity and isolation. While
* transactions are modifying data, Persistit maintains multiple versions of
* values being modified. Each version is labeled with the commit timestamp of
* the transaction that modified it. Whenever a transaction reads a value that
* has been modified by other transactions, it reads the latest version that was
* committed before its own start timestamp. In other words, all read operations
* are performed as if from a "snapshot" of the state of the database made at
* the transaction's start timestamp - hence the name "Snapshot Isolation."
*
* Pruning
*
* Given that all updates written through transactions are created as versions
* within the MVCC scheme, a large number of versions can accumulate over time.
* Persistit reduces this number through an activity called "version pruning."
* Pruning resolves the final state of each version by removing any versions
* created by aborted transactions and removing obsolete versions no longer
* needed by currently executing transactions. If a value contains only one
* version and the commit timestamp of the transaction that created it is before
* the start of any currently running transaction, that value is called
* primordial. The goal of pruning is to reduce almost all values in a
* Persistit Tree to their primordial states because updating and reading
* primordial values is more efficient than the handling required for multiple
* version values. Pruning happens automatically and is generally not visible to
* the application.
*
* Rollbacks
*
* Usually Snapshot Isolation allows concurrent transactions to commit without
* interference but this is not always the case. Two concurrent transactions
* that attempt to modify the same Persistit key-value pair before committing
* are said to have a "write-write dependency". To avoid anomalous results one
* of them must abort, rolling back any other updates it may have created, and
* retry. Persistit implements a "first updater wins" policy in which if two
* transactions attempt to update the same record, the first transaction "wins"
* by being allowed to continue, while the second transaction "loses" and is
* required to abort.
*
*
* Once a transaction has aborted, any subsequent database operation it attempts
* throws a {@link RollbackException}. Application code should generally catch
* and handle the RollbackException
. Usually the correct and
* desired behavior is simply to retry the transaction. See try/finally
for a code pattern that
* accomplishes this.
*
*
* A transaction can also voluntarily roll back. For example, transaction logic
* could detect an error condition that it chooses to handle by throwing an
* exception back to the application. In this case the transaction should invoke
* the {@link #rollback} method to explicit declare its intent to abort the
* transaction.
*
* Read-Only Transactions
*
* Under Snapshot Isolation, transactions that read but do not modify data
* cannot generate any write-write dependencies and are therefore not subject to
* being rolled back because of the actions of other transactions. However, note
* that even if it modifies no data, a long-running transaction can force
* Persistit to retain old value versions for its duration in order to provide a
* snapshot view. This behavior can cause congestion and performance degradation
* by preventing very old values from being pruned. The degree to which this is
* a problem depends on the volume of update transactions being processed and
* the duration of long-running transactions.
*
*
* The try/finally/retry Code Pattern
*
* The following code fragment illustrates a transaction executed with up to to
* RETRY_COUNT retries. If the commit
method succeeds, the whole
* transaction is completed and the retry loop terminates. If after RETRY_COUNT
* retries commit
has not been successfully completed, the
* application throws a TransactionFailedException
.
*
*
*
* Transaction txn = Persistit.getTransaction();
* int remainingAttempts = RETRY_COUNT;
* for (;;) {
* txn.begin(); // Begin transaction scope
* try {
* //
* // ...perform Persistit fetch, remove and store operations...
* //
* txn.commit(); // attempt to commit the updates
* break; // Exit retry loop
* }
* catch (RollbackException re) {
* if (--remainingAttempts < 0) {
* throw new TransactionFailedException();
* {
* }
* finally {
* txn.end(); // End transaction scope. Implicitly
* // roll back all updates unless
* // commit completed successfully.
* }
* }
*
*
* The TransactionRunnable Pattern
*
* As an alternative, the application can embed the actual database operations
* within a {@link TransactionRunnable} and invoke the {@link #run} method to
* execute it. The retry logic detailed in the fragment shown above is handled
* automatically by run
; it could be rewritten as follows:
*
*
* Transaction txn = Persistit.getTransaction();
* txn.run(new TransactionRunnable() {
* public void runTransaction()
* throws PersistitException, RollbackException {
* //
* //...perform Persistit fetch, remove and store operations...
* //
* }
* }, RETRY_COUNT, 0);
*
*
*
* Nested Transaction Scope
*
* Persistit supports nested transactions by counting the number of nested
* {@link #begin} and {@link #end} operations. Each invocation of
* begin
increments this count and each invocation of
* end
decrements the count. These methods are intended to be used
* in a standard essential pattern, shown here, to ensure that the scope of of
* the transaction is reliably determined by the lexical the structure of the
* code rather than conditional logic:
* txn.begin();
* try {
* //
* // Application transaction logic here, possibly including
* // invocation of methods that also call txn.begin() and
* // txn.end().
* //
* txn.commit();
* } finally {
* txn.end();
* }
*
*
*
* This pattern ensures that the transaction scope is ended properly regardless
* of whether the application code throws an exception or completes and commits
* normally.
*
*
* The {@link #commit} method performs the actual commit operation only when the
* current nested level count (see {@link #getNestedTransactionDepth()}) is 1.
* That is, if begin
has been invoked N times, then
* commit
will actually commit the data only when end
* is invoked the Nth time. Data updated by an inner (nested) transaction is
* never actually committed until the outermost commit
is called.
* This permits transactional code to invoke other code (possibly an opaque
* library supplied by a third party) that may itself begin
and
* commit
transactions.
*
*
* Invoking {@link #rollback} removes all pending but uncommitted updates and
* marks the current transaction scope as rollback pending. Any
* subsequent attempt to perform any Persistit operation, including
* commit
in the current transaction scope, will fail with a
* RollbackException
.
*
*
* Application developers should beware that the {@link #end} method performs an
* implicit rollback if commit
has not completed. Therefore, if an
* application fails to call commit
, the transaction will silently
* fail. The end
method sends a warning message to the log
* subsystem when this happens, but does not throw an exception. The
* end
method is designed this way to allow an exception thrown
* within the application code to be caught and handled without being obscured
* by a RollbackException thrown by end
. But as a consequence,
* developers must carefully verify that the end
method is always
* invoked whether or not the transaction completes normally.
*
* Step Index: Controlling Visibility of Uncommitted Updates
*
* By default, application logic within the scope of a transaction can read two
* kinds of values: those that were committed by other transactions prior to the
* start of the current transaction (from the "snapshot") and those that were
* modified by the transaction itself. However, in some applications it is
* useful to control the visibility of modifications made by the current
* transaction. For example, update queries that select records to update and
* then change the very values used as selection criteria can produce anomalous
* results. See Halloween Problem
* for a succinct description of this issue. Persistit provides a mechanism to
* control visibility of a transaction's own modifications to avoid this
* problem.
*
*
* While a transaction is executing, every updated value it generates is stored
* within a multi-version value and labeled with the transaction ID of the
* transaction that produced it and a small integer index (0-99) called
* the step.
*
*
* The current step index is an attribute of the Transaction
object
* available from {@link #getStep}. The begin
method resets its
* value to zero. An application can invoke {@link #incrementStep} to increment
* it, or {@link #setStep} to control its current value. Modifications created
* by the transaction are labeled with the current step value.
*
*
* When reading data, modifications created by the current transaction are
* visible to Persistit if and only if the step number they were assigned is
* less or equal to the Transaction
's current step number. An
* application can take advantage of this by controlling the current step index,
* for example, by reading data using step 0 while posting updates with a step
* value of 1.
*
* Thread Management
*
* As noted above, a Transaction
typically belongs to one thread
* for its entire lifetime and is not threadsafe. However, to support
* server applications which may manage a large number of sessions among a
* smaller number of threads, Persisit allows an application to manage sessions
* explicitly. See {@link Persistit#getSessionId()} and
* {@link Persistit#setSessionId(SessionId)}. The method
* {@link Persistit#getTransaction()} is sensitive to the thread's current
* SessionId
, and therefore the following style of interaction is
* possible:
*
* - Thread T1 is assigned work for session S.
* - Thread T1 invokes
begin
, does some work and then returns
* control to a client.
* - Thread T2 receives additional work to perform on behalf of session S.
* - Thread T2 sets its current SessionId to session S
* - Thread T2 then uses {@link Persistit#getTransaction()} to acquire the
* same transaction context previously started by T1.
* - Thread T2 does additional work and then calls
commit
and
* end
to complete the transaction.
*
* Applications that use this technique must be written carefully to ensure that
* multiple threads never execute with the same SessionId. Concurrent access to
* a Transaction
or Exchange
can cause serious errors,
* including database corruption.
*
* Additional Notes
*
* Optimistic concurrency control works well when the likelihood of conflicting
* transactions - that is, concurrent execution of two or more transactions that
* modify the same database records - is low. For most applications this
* assumption is valid.
*
*
* For best performance, applications in which multiple threads frequently
* operate on overlapping data such that roll-backs are likely, the application
* should implement its own locks to prevent or reduce the likelihood of
* collisions.
*
*
* An application can examine counts of commits, rollbacks and rollbacks since
* the last successful commit using {@link #getCommittedTransactionCount()},
* {@link #getRolledBackTransactionCount()} and
* {@link #getRolledBackSinceLastCommitCount()}, respectively.
*
*
* @author peter
* @version 1.1
*/
public class Transaction {
final static int MAXIMUM_STEP = TransactionIndex.VERSION_HANDLE_MULTIPLIER - 1;
final static int TRANSACTION_BUFFER_SIZE = 65536;
private static long _idCounter = 100000000;
private final Persistit _persistit;
private final SessionId _sessionId;
private final long _id;
private int _nestedDepth;
private boolean _rollbackPending;
private boolean _rollbackCompleted;
private boolean _commitCompleted;
private long _rollbackCount = 0;
private long _commitCount = 0;
private int _rollbacksSinceLastCommit = 0;
private volatile TransactionStatus _transactionStatus;
private volatile long _startTimestamp;
private volatile long _commitTimestamp;
private final ByteBuffer _buffer = ByteBuffer.allocate(TRANSACTION_BUFFER_SIZE);
private long _previousJournalAddress;
private int _step;
private String _threadName;
public static enum CommitPolicy {
/**
* The {@link Transaction#commit} method returns before all updates have
* been written to durable storage. This policy is a compromise that
* offers much better throughput, especially for sequential
* transactions, but does not provide durability for every committed
* transactions. Some recently committed transactions may be lost after
* a crash/recovery cycle.
*/
SOFT,
/**
* Every committed transaction is flushed synchronously to durable
* storage before the {@link Transaction#commit()} method returns. With
* this policy every transaction is durable before commit completes.
*/
HARD,
/**
* Every committed transaction is flushed to durable storage before
* {@link Transaction#commit()} returns. Persistit attempts to
* coordinate the I/O needed to do this with other pending transactions;
* as a consequence, the commit method may pause briefly waiting for
* other transactions to reach their commit points. In general this
* option provides the slowest rate of sequential commits, but the
* aggregate transaction throughput across many threads may be much
* higher than with the HARD policy.
*/
GROUP;
static CommitPolicy forName(final String policyName) {
for (final CommitPolicy policy : values()) {
if (policy.name().equalsIgnoreCase(policyName)) {
return policy;
}
}
throw new IllegalArgumentException("No such CommitPolicy: " + policyName);
}
}
private CommitPolicy _defaultCommitPolicy = CommitPolicy.SOFT;
/**
* Creates a new transaction context. Any transaction performed within this
* context will be isolated from all other transactions.
*/
Transaction(final Persistit persistit, final SessionId sessionId) {
this(persistit, sessionId, nextId());
}
/**
* Creates a Transaction context with a specified ID value. This is used
* only during recovery processing.
*
* @param id
*/
private Transaction(final Persistit persistit, final SessionId sessionId, final long id) {
_persistit = persistit;
_sessionId = sessionId;
_id = id;
}
private static synchronized long nextId() {
return ++_idCounter;
}
/**
* Release all resources associated with this transaction context. Abort the
* transaction if it was abandoned due to thread death.
*
* @throws PersistitException
*/
void close() throws PersistitException {
if (_nestedDepth > 0 && !_commitCompleted && !_rollbackCompleted) {
final TransactionStatus ts = _transactionStatus;
if (ts != null && ts.getTs() == _startTimestamp && !_commitCompleted && !_rollbackCompleted) {
rollback();
_persistit.getLogBase().txnAbandoned.log(this);
}
}
/*
* The background rollback cleanup should be stopped before calling this
* method so the following check is deterministic.
*/
final TransactionStatus status = _persistit.getTransactionIndex().getStatus(_startTimestamp);
if (status != null && status.getMvvCount() > 0) {
flushTransactionBuffer(false);
}
}
/**
* Throws a {@link RollbackException} if this transaction context has a
* roll-back transition pending.
*
* @throws RollbackException
*/
public void checkPendingRollback() throws RollbackException {
if (_rollbackPending) {
throw new RollbackException();
}
}
/**
* Indicates whether the application has invoked {@link #begin} but has not
* yet invoked a matching {@link #commit} or {@link #rollback}.
*
* @return true
if a transaction has begun, but is not yet
* either committed or rolled back
*/
public boolean isActive() {
return _nestedDepth > 0;
}
/**
* Indicates whether the {@link #commit} method has run to successful
* completion at the current nested level. If that level is 1, then
* successful completion of commit
means that the transaction
* has actually been committed to the database. Within the scope of inner
* (nested) transactions, data is not actually committed until the outermost
* transaction scope commits.
*
* @return true
if the current transaction scope has completed
* its commit
operation; otherwise false
.
*/
public boolean isCommitted() {
return _commitCompleted;
}
/**
* Indicates whether the current transaction scope has been marked for
* rollback. If so, and if this is an inner (nested) transaction scope, all
* updates performed by the containing scope will also be rolled back.
*
* @return true
if the current transaction is marked for
* rollback; otherwise false
.
*/
public boolean isRollbackPending() {
return _rollbackPending;
}
/**
* Start a transaction. If there already is an active transaction then this
* method merely increments a counter that indicates how many times
* begin
has been called. Application code should ensure that
* every method that calls begin
also invokes end
* using a try/finally
pattern described above.
*
* @throws IllegalStateException
* if the current transaction scope has already been committed.
*/
public void begin() throws PersistitException {
if (_commitCompleted) {
throw new IllegalStateException("Attempt to begin a committed transaction " + this);
}
if (_rollbackPending) {
throw new IllegalStateException("Attempt to begin a transaction with pending rollback" + this);
}
if (_nestedDepth == 0) {
flushTransactionBuffer(false);
try {
_transactionStatus = _persistit.getTransactionIndex().registerTransaction();
} catch (final InterruptedException e) {
_rollbackCompleted = true;
throw new PersistitInterruptedException(e);
}
_rollbackPending = false;
_rollbackCompleted = false;
_startTimestamp = _transactionStatus.getTs();
_commitTimestamp = 0;
_step = 0;
_threadName = Thread.currentThread().getName();
} else {
checkPendingRollback();
}
_nestedDepth++;
}
void beginCheckpoint() throws PersistitException {
if (_commitCompleted) {
throw new IllegalStateException("Attempt to begin a committed transaction " + this);
}
if (_rollbackPending) {
throw new IllegalStateException("Attmpet to begin a transaction with pending rollback" + this);
}
if (_nestedDepth == 0) {
flushTransactionBuffer(false);
try {
_transactionStatus = _persistit.getTransactionIndex().registerCheckpointTransaction();
} catch (final InterruptedException e) {
_rollbackCompleted = true;
throw new PersistitInterruptedException(e);
}
_rollbackPending = false;
_rollbackCompleted = false;
_startTimestamp = _transactionStatus.getTs();
_commitTimestamp = 0;
_step = 0;
} else {
checkPendingRollback();
}
_nestedDepth++;
}
/**
*
* End the current transaction scope. Application code should ensure that
* every method that calls begin
also invokes end
* using a try/finally
pattern described in above.
*
*
* This method implicitly rolls back any pending, uncommitted updates.
* Updates are committed only if the commit
method completes
* successfully.
*
*
* @throws PersistitIOException
*
* @throws IllegalStateException
* if there is no current transaction scope.
*/
public void end() {
checkActive();
if (_nestedDepth == 1) {
//
// If not committed, this is an implicit rollback (with a log
// message if rollback was not called explicitly).
//
if (!_commitCompleted) {
if (!_rollbackPending) {
_persistit.getLogBase().txnNotCommitted.log(this);
}
if (!_rollbackCompleted) {
rollback();
}
} else {
_commitCount++;
_rollbacksSinceLastCommit = 0;
}
_transactionStatus = null;
_rollbackPending = false;
_threadName = null;
}
_nestedDepth--;
_commitCompleted = false;
}
/**
*
* Explicitly rolls back all work done within the scope of this transaction.
* If this transaction is not active, then this method throws an Exception.
* No further updates can be applied within the scope of this transaction.
*
*
* If called within the scope of a nested transaction, this method causes
* all enclosing transactions to roll back. This ensures that the outermost
* transaction will not commit if any inner transaction has rolled back.
*
*
* @throws PersistitIOException
*
* @throws IllegalStateException
* if there is no transaction scope or the current scope has
* already been committed.
*/
public void rollback() {
checkActive();
if (_commitCompleted) {
throw new IllegalStateException("Already committed " + this);
}
_rollbackPending = true;
if (!_rollbackCompleted) {
_rollbackCount++;
_rollbacksSinceLastCommit++;
_transactionStatus.abort();
try {
/*
* Necessary to enable rollback pruning
*/
flushTransactionBuffer(false);
} catch (final PersistitException e) {
_persistit.getLogBase().exception.log(e);
} finally {
_persistit.getTransactionIndex().notifyCompleted(_transactionStatus,
_persistit.getTimestampAllocator().getCurrentTimestamp());
_rollbackCompleted = true;
}
}
}
/**
*
* Commit this transaction. This method flushes the journal entries created
* by the transaction to the journal buffer, optionally waits for those
* changes to be written durably to disk according to the default configured
* {@link CommitPolicy}, and marks the transaction as completed so that its
* effects are visible to other transactions.
*
*
* If executed within the scope of an outer transaction, this method simply
* sets a flag indicating that the current transaction level has committed
* without modifying any data. The commit for the outermost transaction
* scope is responsible for actually committing the changes.
*
*
* Once an application thread has called commit
, no subsequent
* Persistit database operations are permitted until the end
* method has been called. An attempt to store, fetch or remove data after
* commit
has been called throws an
* IllegalStateException
.
*
*
* @throws PersistitIOException
* if the transaction could not be written to the journal due to
* an IOException. This exception also causes the transaction to
* be rolled back.
*
* @throws RollbackException
* if the {@link #rollback()} was previously called
*
* @throws PersistitInterruptedException
* if the thread was interrupted while waiting for I/O
* completion
*
* @throws IllegalStateException
* if no transaction scope is active or this transaction scope
* has already called commit
*
* @throws PersistitException
* if a PersistitException was caught by the JOURNAL_FLUSHER
* thread after this transaction began waiting for durability
*
* @see Persistit#getDefaultTransactionCommitPolicy()
*/
public void commit() throws PersistitException {
commit(_persistit.getDefaultTransactionCommitPolicy());
}
/**
* Commit to disk with {@link CommitPolicy} determined by the supplied
* boolean value. This method is obsolete and will be removed shortly.
*/
@Deprecated
public void commit(final boolean toDisk) throws PersistitException {
commit(toDisk ? CommitPolicy.HARD : CommitPolicy.SOFT);
}
/**
*
* Commit this transaction. This method flushes the journal entries created
* by the transaction to the journal buffer, optionally waits for those
* changes to be written durably to disk according to the supplied
* {@link CommitPolicy}, and marks the transaction as completed so that its
* effects are visible to other transactions.
*
*
* If executed within the scope of an outer transaction, this method simply
* sets a flag indicating that the current transaction level has committed
* without modifying any data. The commit for the outermost transaction
* scope is responsible for actually committing the changes.
*
*
* Once an application thread has called commit
, no subsequent
* Persistit database operations are permitted until the end
* method has been called. An attempt to store, fetch or remove data after
* commit
has been called throws an
* IllegalStateException
.
*
*
* @param policy
* Determines whether the commit method waits until the
* transaction has been written to durable storage before
* returning to the caller.
*
* @throws PersistitIOException
* if the transaction could not be written to the journal due to
* an IOException. This exception also causes the transaction to
* be rolled back.
*
* @throws RollbackException
* if the {@link #rollback()} was previously called
*
* @throws PersistitInterruptedException
* if the thread was interrupted while waiting for I/O
* completion
*
* @throws IllegalStateException
* if no transaction scope is active or this transaction scope
* has already called commit
*
* @throws PersistitException
* if a PersistitException was caught by the JOURNAL_FLUSHER
* thread after this transaction began waiting for durability
*
*/
public void commit(final CommitPolicy policy) throws PersistitException {
checkActive();
if (_commitCompleted) {
throw new IllegalStateException("Already committed " + this);
}
checkPendingRollback();
if (_nestedDepth == 1) {
if (_rollbackCompleted) {
throw new IllegalStateException("Already rolled back " + this);
}
for (Delta delta = _transactionStatus.getDelta(); delta != null; delta = delta.getNext()) {
writeDeltaToJournal(delta);
}
_transactionStatus.commit(_persistit.getTimestampAllocator().getCurrentTimestamp());
sequence(COMMIT_FLUSH_A);
_commitTimestamp = _persistit.getTimestampAllocator().updateTimestamp();
sequence(COMMIT_FLUSH_C);
long flushedTimetimestamp = 0;
for (Delta delta = _transactionStatus.getDelta(); delta != null; delta = delta.getNext()) {
final Accumulator acc = delta.getAccumulator();
acc.checkpointNeeded(_commitTimestamp);
}
boolean committed = false;
try {
if (flushTransactionBuffer(false)) {
flushedTimetimestamp = _persistit.getTimestampAllocator().getCurrentTimestamp();
}
committed = true;
} finally {
_persistit.getTransactionIndex().notifyCompleted(_transactionStatus,
committed ? _commitTimestamp : TransactionStatus.ABORTED);
_commitCompleted = committed;
_rollbackPending = _rollbackCompleted = !committed;
}
_persistit.getJournalManager().throttle();
if (flushedTimetimestamp != 0) {
_persistit.getJournalManager().waitForDurability(flushedTimetimestamp,
policy == CommitPolicy.SOFT ? _persistit.getTransactionCommitLeadTime() : 0,
policy == CommitPolicy.GROUP ? _persistit.getTransactionCommitStallTime() : 0);
}
}
}
/**
* Returns the nested level count. When no transaction scope is active this
* method returns 0. Within the outermost transaction scope this method
* returns 1. For nested transactions this method returns a value of 2 or
* higher.
*
* @return The nested transaction depth
*/
public int getNestedTransactionDepth() {
return _nestedDepth;
}
/**
*
* Invokes the {@link TransactionRunnable#runTransaction} method of an
* object that implements {@link TransactionRunnable} within the scope of a
* transaction. The TransactionRunnable
should neither
* begin
nor commit
the transaction; these
* operations are performed by this method. The
* TransactionRunnable
may explicitly throw a
* RollbackException
in which case the current transaction will
* be rolled back.
*
*
* This method does not perform retries, so upon the first failed attempt to
* commit the transaction this method will throw a
* RollbackException
. See
* {@link #run(TransactionRunnable, int, long, boolean)} for retry handling.
* This method commits the transaction to memory, which means that the update is not
* guaranteed to be durable.
*
*
* @param runnable
*
* @return Count of passes needed to complete the transaction. Always 1 on
* successful completion.
*
* @throws PersistitException
*/
public int run(final TransactionRunnable runnable) throws PersistitException {
return run(runnable, 0, 0, _persistit.getDefaultTransactionCommitPolicy());
}
/**
*
* Invokes the {@link TransactionRunnable#runTransaction} method of an
* object that implements {@link TransactionRunnable} within the scope of a
* transaction. The TransactionRunnable
should neither
* begin
nor commit
the transaction; these
* operations are performed by this method. The
* TransactionRunnable
may explicitly or implicitly
* throw a RollbackException
in which case the current
* transaction will be rolled back.
*
*
* If retryCount
is greater than zero, this method will make up
* to retryCount
additional of attempts to complete and commit
* the transaction. Once the retry count is exhausted, this method throws a
* RollbackException
.
*
*
* @param runnable
* An application specific implementation of
* TransactionRunnable
containing logic to access
* and update Persistit data.
*
* @param retryCount
* Number of attempts (not including the first attempt) to make
* before throwing a RollbackException
*
* @param retryDelay
* Time, in milliseconds, to wait before the next retry attempt.
*
* @param toDisk
* SOFT
, HARD
or GROUP
to
* commit achieve durability or throughput, as required by the
* application.
*
* @return Count of attempts needed to complete the transaction
*
* @throws PersistitException
* @throws RollbackException
* If after retryCount+1
attempts the transaction
* cannot be completed or committed due to concurrent updated
* performed by other threads.
*/
public int run(final TransactionRunnable runnable, final int retryCount, final long retryDelay, final boolean toDisk)
throws PersistitException {
return run(runnable, retryCount, retryDelay, toDisk ? CommitPolicy.HARD : CommitPolicy.SOFT);
}
public int run(final TransactionRunnable runnable, int retryCount, final long retryDelay, final CommitPolicy toDisk)
throws PersistitException {
if (retryCount < 0)
throw new IllegalArgumentException();
for (int count = 1;; count++) {
begin();
try {
runnable.runTransaction();
commit(toDisk);
return count;
} catch (final RollbackException re) {
if (retryCount <= 0 || _nestedDepth > 1) {
throw re;
}
retryCount--;
if (retryDelay > 0) {
try {
Util.sleep(retryDelay);
} catch (final PersistitInterruptedException ie) {
throw re;
}
}
} finally {
end();
}
}
}
/**
* Returns displayable information about this transaction.
*
* @return Information about this Transaction
*/
@Override
public String toString() {
return "Transaction_" + _id + " depth=" + _nestedDepth + " status=" + getStatus()
+ (_threadName == null ? "" : " owner=" + _threadName);
}
String getStatus() {
final TransactionStatus status = _transactionStatus;
final long ts = getStartTimestamp();
if (status != null && status.getTs() == ts) {
return status.toString();
} else {
return "";
}
}
/**
* @return the current default policy
*/
public CommitPolicy getDefaultCommitPolicy() {
return _defaultCommitPolicy;
}
/**
* Set the current default policy
*
* @param policy
*/
public void setDefaultCommitPolicy(final CommitPolicy policy) {
_defaultCommitPolicy = policy;
}
/**
* Returns the internal transaction identifier.
*
* @return The transaction identifier
*/
public long getId() {
return _id;
}
/**
* @return the internal start timestamp of this transaction.
*/
public long getStartTimestamp() {
return _startTimestamp;
}
/**
* @return the internal timestamp at which transaction committed.
*/
public long getCommitTimestamp() {
return _commitTimestamp;
}
/**
* @return the SessionId this Transaction context belongs too.
*/
public SessionId getSessionId() {
return _sessionId;
}
/**
* Return the number of transactions committed by this transaction context.
*
* @return The count
*/
public long getCommittedTransactionCount() {
return _commitCount;
}
/**
* Return the number of transactions rolled back by this transaction
* context.
*
* @return The count
*/
public long getRolledBackTransactionCount() {
return _rollbackCount;
}
/**
* Return the number of times a transaction in this Transaction
* context has rolled back since the last successful commit
* operations.
*
* @return The count
*/
public int getRolledBackSinceLastCommitCount() {
return _rollbacksSinceLastCommit;
}
/**
* Record a store operation.
*
* @param exchange
* @param key
* @param value
* @throws PersistitException
*/
void store(final Exchange exchange, final Key key, final Value value) throws PersistitException {
if (_nestedDepth > 0) {
checkPendingRollback();
writeStoreRecordToJournal(treeHandle(exchange.getTree()), key, value);
}
}
/**
* Record a remove operation.
*
* @param exchange
* @param key1
* @param key2
* @throws PersistitException
*/
void remove(final Exchange exchange, final Key key1, final Key key2) throws PersistitException {
if (_nestedDepth > 0) {
checkPendingRollback();
writeDeleteRecordToJournal(treeHandle(exchange.getTree()), key1, key2);
}
}
/**
* Record a tree delete operation
*
* @param exchange
* @throws PersistitException
*/
void removeTree(final Exchange exchange) throws PersistitException {
if (_nestedDepth > 0) {
checkPendingRollback();
writeDeleteTreeToJournal(treeHandle(exchange.getTree()));
}
}
synchronized private void prepare(final int recordSize) throws PersistitException {
if (recordSize > _buffer.remaining()) {
flushTransactionBuffer(true);
}
if (recordSize > _buffer.remaining()) {
throw new IllegalStateException("Record size " + recordSize + " is too long for Transaction buffer in "
+ this);
}
}
synchronized boolean flushTransactionBuffer(final boolean chain) throws PersistitException {
boolean didWrite = false;
if (_buffer.position() > 0 || _previousJournalAddress != 0) {
final long previousJournalAddress = _persistit.getJournalManager().writeTransactionToJournal(_buffer,
_startTimestamp, _commitTimestamp, _previousJournalAddress);
_buffer.clear();
didWrite = true;
if (chain) {
_previousJournalAddress = previousJournalAddress;
} else {
_previousJournalAddress = 0;
}
}
return didWrite;
}
synchronized void flushOnCheckpoint(final long timestamp) throws PersistitException {
if (_startTimestamp > 0 && _startTimestamp < timestamp && _commitTimestamp == 0 && _buffer.position() > 0) {
sequence(COMMIT_FLUSH_B);
_previousJournalAddress = _persistit.getJournalManager().writeTransactionToJournal(_buffer,
_startTimestamp, 0, _previousJournalAddress);
_buffer.clear();
}
}
synchronized void writeStoreRecordToJournal(final int treeHandle, final Key key, final Value value)
throws PersistitException {
final int recordSize = SR.OVERHEAD + key.getEncodedSize() + value.getEncodedSize();
prepare(recordSize);
SR.putLength(_buffer, recordSize);
SR.putType(_buffer);
SR.putTreeHandle(_buffer, treeHandle);
SR.putKeySize(_buffer, (short) key.getEncodedSize());
_buffer.position(_buffer.position() + SR.OVERHEAD);
_buffer.put(key.getEncodedBytes(), 0, key.getEncodedSize());
_buffer.put(value.getEncodedBytes(), 0, value.getEncodedSize());
}
synchronized void writeDeleteRecordToJournal(final int treeHandle, final Key key1, final Key key2)
throws PersistitException {
final int elisionCount = key2.firstUniqueByteIndex(key1);
final int recordSize = DR.OVERHEAD + key1.getEncodedSize() + key2.getEncodedSize() - elisionCount;
prepare(recordSize);
DR.putLength(_buffer, recordSize);
DR.putType(_buffer);
DR.putTreeHandle(_buffer, treeHandle);
DR.putKey1Size(_buffer, key1.getEncodedSize());
DR.putKey2Elision(_buffer, elisionCount);
_buffer.position(_buffer.position() + DR.OVERHEAD);
_buffer.put(key1.getEncodedBytes(), 0, key1.getEncodedSize());
_buffer.put(key2.getEncodedBytes(), elisionCount, key2.getEncodedSize() - elisionCount);
}
synchronized void writeDeleteTreeToJournal(final int treeHandle) throws PersistitException {
prepare(DT.OVERHEAD);
JournalRecord.putLength(_buffer, DT.OVERHEAD);
DT.putType(_buffer);
DT.putTreeHandle(_buffer, treeHandle);
_buffer.position(_buffer.position() + DT.OVERHEAD);
}
synchronized void writeDeltaToJournal(final Delta delta) throws PersistitException {
final int treeHandle = treeHandle(delta.getAccumulator().getTree());
if (delta.getValue() == 1) {
prepare(D0.OVERHEAD);
JournalRecord.putLength(_buffer, D0.OVERHEAD);
D0.putType(_buffer);
D0.putTreeHandle(_buffer, treeHandle);
D0.putAccumulatorTypeOrdinal(_buffer, delta.getAccumulator().getType().ordinal());
D0.putIndex(_buffer, delta.getAccumulator().getIndex());
_buffer.position(_buffer.position() + D0.OVERHEAD);
} else {
prepare(D1.OVERHEAD);
JournalRecord.putLength(_buffer, D1.OVERHEAD);
D1.putType(_buffer);
D1.putTreeHandle(_buffer, treeHandle);
D1.putIndex(_buffer, delta.getAccumulator().getIndex());
D1.putAccumulatorTypeOrdinal(_buffer, delta.getAccumulator().getType().ordinal());
D1.putValue(_buffer, delta.getValue());
_buffer.position(_buffer.position() + D1.OVERHEAD);
}
}
TransactionStatus getTransactionStatus() {
final TransactionStatus ts = _transactionStatus;
if (_nestedDepth > 0 && ts != null && ts.getTs() == _startTimestamp) {
return ts;
} else {
throw new IllegalArgumentException("Transaction not in scope " + this);
}
}
/**
* @return the current step index.
*/
public int getStep() {
return _step;
}
/**
* Set the current step index. Must be in the range [0, 99].
*
* Also see {@link #incrementStep()} for step semantics.
*
*
* @param step
* New step index value.
* @return Previous step value.
*/
public int setStep(final int step) {
checkPendingRollback();
checkStepRange(step);
final int previous = _step;
_step = step;
return previous;
}
void checkActive() {
if (!isActive()) {
throw new IllegalStateException("No transaction scope: begin() has not been called in " + this);
}
}
/**
* Increment this transaction's current step index. For any given step,
* values written by updates within this transaction are visible (within
* this transaction) only if they were written with earlier or equal step
* indexes. In other words, a transaction that writes an update at step N
* can see the result of that update when reading the database and any step
* <= N. This mechanism helps solve the "Halloween" problem in which a
* SELECT query producing values to UPDATE should not be able to read back
* those update values.
*
* @throws IllegalStateException
* if this method is called more than 99 times within the scope
* of one transaction.
* @return The previous value of the step.
*/
public int incrementStep() {
return setStep(_step + 1);
}
private void checkStepRange(final int newStep) {
if (newStep < 0) {
throw new IllegalStateException(this + " cannot have a step of " + newStep + ", less than 0");
}
if (newStep > MAXIMUM_STEP) {
throw new IllegalStateException(this + " cannot have a step of " + newStep + ", greater than maximum "
+ MAXIMUM_STEP);
}
}
private int treeHandle(final Tree tree) {
final int treeHandle = tree.getHandle();
assert treeHandle != 0 : "Undefined tree handle in " + tree;
return treeHandle;
}
/**
* For unit tests only
*
* @return the buffer used to accumulate update records for this transaction
*/
ByteBuffer getTransactionBuffer() {
return _buffer;
}
}