/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.persistit.Accumulator.Delta;
import com.persistit.CleanupManager.CleanupAction;
import com.persistit.CleanupManager.CleanupPruneAction;
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;
/**
* <p>
* Transaction context for atomic units of work performed by Persistit. Each
* Persistit thread typically uses one <code>Transaction</code> object for its
* lifetime. Within that context it may execute and commit many transactions.
* </p>
* <p>
* 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 <code>commit</code> is performed. At that point, all the
* updates become visible and durable.
* </p>
* <p>
* Persistit implements Multi-Version Concurrency Control (MVCC) with <a
* href="http://wikipedia.org/wiki/Snapshot_isolation">Snapshot Isolation</a>
* for high concurrency and throughput. This protocol is <i>optimistic</i> 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.
* </p>
* <p>
* The <code>Transaction</code> 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 <code>begin</code> and
* <code>end</code> are capable of highly concurrent execution.
* </p>
* <h2>Lexical Scoping</h2>
* <p>
* Applications must manage the scope of any transaction by ensuring that
* whenever the <code>begin</code> method is called, there is a concluding
* invocation of the <code>end</code> method. The <code>commit</code> and
* <code>rollback</code> methods do not end the scope of a transaction; they
* merely signify the transaction's intended outcome when <code>end</code> is
* invoked. Applications should follow either the <a href="#_pattern1">
* <code>try/finally</code></a> or the <a href="#_pattern2">
* <code>TransactionRunnable</code></a> pattern to ensure correctness.
* </p>
* <p>
* <a name="commitPolicy"/>
* <h2>Commit Policy</h2>
* Persistit provides three policies that determine the durability of a
* transaction after it has executed the <code>commit</code> method. These are:
* <dl>
* <dt>{@link CommitPolicy#HARD}</dt>
* <dd>The <code>commit</code> method does not return until all updates created
* by the transaction have been written to non-volatile storage (e.g., disk
* storage).</dd>
* <dt>{@link CommitPolicy#GROUP}</dt>
* <dd>The <code>commit</code> 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.</dd>
* <dt>{@link CommitPolicy#SOFT}</dt>
* <dd>The <code>commit</code> method returns <i>before</i> the updates have
* been recorded on non-volatile storage. Persistit attempts to write them
* within 100 milliseconds, but this interval is not guaranteed.</dd>
* </dl>
* <p>
* 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)}.
* </p>
* <p>
* HARD and GROUP ensure each transaction is written durably to non-volatile
* storage before the <code>commit</code> 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <h2>Optimistic Concurrent Scheduling - MVCC</h2>
* <p>
* 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."
* </p>
* <h3>Pruning</h3>
* <p>
* 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
* <i>primordial</i>. 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.
* </p>
* <h3>Rollbacks</h3>
* <p>
* 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.
* </p>
* <p>
* Once a transaction has aborted, any subsequent database operation it attempts
* throws a {@link RollbackException}. Application code should generally catch
* and handle the <code>RollbackException</code>. Usually the correct and
* desired behavior is simply to retry the transaction. See <a
* href="#_pattern1"><code>try/finally</code></a> for a code pattern that
* accomplishes this.
* </p>
* <p>
* 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.
* </p>
* <h3>Read-Only Transactions</h3>
* <p>
* 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.
* </p>
*
* <a name="_pattern1"/> <h2>The try/finally/retry Code Pattern</h2>
* <p>
* The following code fragment illustrates a transaction executed with up to to
* RETRY_COUNT retries. If the <code>commit</code> method succeeds, the whole
* transaction is completed and the retry loop terminates. If after RETRY_COUNT
* retries <code>commit</code> has not been successfully completed, the
* application throws a <code>TransactionFailedException</code>.
* </p>
*
* <blockquote><code><pre>
* 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.
* }
* }
* </pre></code></blockquote>
*
* <a name="_pattern2" /> <h2>The TransactionRunnable Pattern</h2>
* <p>
* 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 <code>run</code>; it could be rewritten as follows:
*
* <blockquote><code><pre>
* Transaction txn = Persistit.getTransaction();
* txn.run(new TransactionRunnable() {
* public void runTransaction()
* throws PersistitException, RollbackException {
* //
* //...perform Persistit fetch, remove and store operations...
* //
* }
* }, RETRY_COUNT, 0);
* </pre></code></blockquote>
*
* </p>
* <a name="_scopedCodePattern"/> <h2>Nested Transaction Scope</h2>
* <p>
* Persistit supports nested transactions by counting the number of nested
* {@link #begin} and {@link #end} operations. Each invocation of
* <code>begin</code> increments this count and each invocation of
* <code>end</code> 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: <blockquote><code><pre>
* <b>txn.begin();</b>
* try {
* //
* // Application transaction logic here, possibly including
* // invocation of methods that also call txn.begin() and
* // txn.end().
* //
* <b>txn.commit();</b>
* } finally {
* <b>txn.end();</b>
* }
* </pre></code></blockquote>
* </p>
* <p>
* This pattern ensures that the transaction scope is ended properly regardless
* of whether the application code throws an exception or completes and commits
* normally.
* </p>
* <p>
* The {@link #commit} method performs the actual commit operation only when the
* current nested level count (see {@link #getNestedTransactionDepth()}) is 1.
* That is, if <code>begin</code> has been invoked N times, then
* <code>commit</code> will actually commit the data only when <code>end</code>
* is invoked the Nth time. Data updated by an inner (nested) transaction is
* never actually committed until the outermost <code>commit</code> is called.
* This permits transactional code to invoke other code (possibly an opaque
* library supplied by a third party) that may itself <code>begin</code> and
* <code>commit</code> transactions.
* </p>
* <p>
* Invoking {@link #rollback} removes all pending but uncommitted updates and
* marks the current transaction scope as <i>rollback pending</i>. Any
* subsequent attempt to perform any Persistit operation, including
* <code>commit</code> in the current transaction scope, will fail with a
* <code>RollbackException</code>.
* </p>
* <p>
* Application developers should beware that the {@link #end} method performs an
* implicit rollback if <code>commit</code> has not completed. Therefore, if an
* application fails to call <code>commit</code>, the transaction will silently
* fail. The <code>end</code> method sends a warning message to the log
* subsystem when this happens, but does not throw an exception. The
* <code>end</code> 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 <code>end</code>. But as a consequence,
* developers must carefully verify that the <code>end</code> method is always
* invoked whether or not the transaction completes normally.
* </p>
* <h2>Step Index: Controlling Visibility of Uncommitted Updates</h2>
* <p>
* 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 <a
* href="http://en.wikipedia.org/wiki/Halloween_Problem">Halloween Problem</a>
* for a succinct description of this issue. Persistit provides a mechanism to
* control visibility of a transaction's own modifications to avoid this
* problem.
* </p>
* <p>
* 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 <u>and</u> a small integer index (0-99) called
* the <i>step</i>.
* </p>
* <p>
* The current step index is an attribute of the <code>Transaction</code> object
* available from {@link #getStep}. The <code>begin</code> 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.
* </p>
* <p>
* 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 <code>Transaction</code>'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.
* </p>
* <a name="_threadManagement" /> <h2>Thread Management</h2>
* <p>
* As noted above, a <code>Transaction</code> typically belongs to one thread
* for its entire lifetime and is <i>not</i> 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
* <code>SessionId</code>, and therefore the following style of interaction is
* possible:
* <ul>
* <li>Thread T1 is assigned work for session S.</li>
* <li>Thread T1 invokes <code>begin</code>, does some work and then returns
* control to a client.</li>
* <li>Thread T2 receives additional work to perform on behalf of session S.</li>
* <li>Thread T2 sets its current SessionId to session S</li>
* <li>Thread T2 then uses {@link Persistit#getTransaction()} to acquire the
* same transaction context previously started by T1.</li>
* <li>Thread T2 does additional work and then calls <code>commit</code> and
* <code>end</code> to complete the transaction.</li>
* </ul>
* Applications that use this technique must be written carefully to ensure that
* multiple threads never execute with the same SessionId. Concurrent access to
* a <code>Transaction</code> or <code>Exchange</code> can cause serious errors,
* including database corruption.
* </p>
* <h2>Additional Notes</h2>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* An application can examine counts of commits, rollbacks and rollbacks since
* the last successful commit using {@link #getCommittedTransactionCount()},
* {@link #getRolledBackTransactionCount()} and
* {@link #getRolledBackSinceLastCommitCount()}, respectively.
* </p>
*
* @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 volatile int _nestedDepth;
private volatile boolean _rollbackPending;
private volatile boolean _rollbackCompleted;
private volatile boolean _commitCompleted;
private volatile long _rollbackCount = 0;
private volatile long _commitCount = 0;
private volatile 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;
private final Set<CleanupAction> _lockCleanupActions = new HashSet<CleanupAction>();
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 <code>true</code> 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 <code>commit</code> 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 <code>true</code> if the current transaction scope has completed
* its <code>commit</code> operation; otherwise <code>false</code>.
*/
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 <code>true</code> if the current transaction is marked for
* rollback; otherwise <code>false</code>.
*/
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
* <code>begin</code> has been called. Application code should ensure that
* every method that calls <code>begin</code> also invokes <code>end</code>
* using a <code>try/finally</code> pattern described <a
* href="#_scopedCodePattern">above</a>.
*
* @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++;
}
/**
* <p>
* End the current transaction scope. Application code should ensure that
* every method that calls <code>begin</code> also invokes <code>end</code>
* using a <code>try/finally</code> pattern described in <a
* href="#_scopedCodePattern">above</a>.
* </p>
* <p>
* This method implicitly rolls back any pending, uncommitted updates.
* Updates are committed only if the <code>commit</code> method completes
* successfully.
* </p>
*
* @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;
}
try {
pruneLockPages();
} catch (final Exception e) {
_persistit.getLogBase().pruneException.log(e, "locks");
}
_transactionStatus = null;
_rollbackPending = false;
_threadName = null;
}
_nestedDepth--;
_commitCompleted = false;
}
/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*
* @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;
}
}
}
/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* Once an application thread has called <code>commit</code>, no subsequent
* Persistit database operations are permitted until the <code>end</code>
* method has been called. An attempt to store, fetch or remove data after
* <code>commit</code> has been called throws an
* <code>IllegalStateException</code>.
* </p>
*
* @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 <code>commit</code>
*
* @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);
}
/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* Once an application thread has called <code>commit</code>, no subsequent
* Persistit database operations are permitted until the <code>end</code>
* method has been called. An attempt to store, fetch or remove data after
* <code>commit</code> has been called throws an
* <code>IllegalStateException</code>.
* </p>
*
* @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 <code>commit</code>
*
* @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;
}
/**
* <p>
* Invokes the {@link TransactionRunnable#runTransaction} method of an
* object that implements {@link TransactionRunnable} within the scope of a
* transaction. The <code>TransactionRunnable</code> should neither
* <code>begin</code> nor <code>commit</code> the transaction; these
* operations are performed by this method. The
* <code>TransactionRunnable</code> <i>may</i> explicitly throw a
* <code>RollbackException</code> in which case the current transaction will
* be rolled back.
* </p>
* <p>
* This method does not perform retries, so upon the first failed attempt to
* commit the transaction this method will throw a
* <code>RollbackException</code>. See
* {@link #run(TransactionRunnable, int, long, boolean)} for retry handling.
* This method commits the transaction to <a
* href="#commitPolicy">memory</a>, which means that the update is not
* guaranteed to be durable.
* </p>
*
* @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());
}
/**
* <p>
* Invokes the {@link TransactionRunnable#runTransaction} method of an
* object that implements {@link TransactionRunnable} within the scope of a
* transaction. The <code>TransactionRunnable</code> should neither
* <code>begin</code> nor <code>commit</code> the transaction; these
* operations are performed by this method. The
* <code>TransactionRunnable</code> <i>may</i> explicitly or implicitly
* throw a <code>RollbackException</code> in which case the current
* transaction will be rolled back.
* </p>
* <p>
* If <code>retryCount</code> is greater than zero, this method will make up
* to <code>retryCount</code> additional of attempts to complete and commit
* the transaction. Once the retry count is exhausted, this method throws a
* <code>RollbackException</code>.
* </p>
*
* @param runnable
* An application specific implementation of
* <code>TransactionRunnable</code> containing logic to access
* and update Persistit data.
*
* @param retryCount
* Number of attempts (not including the first attempt) to make
* before throwing a <code>RollbackException</code>
*
* @param retryDelay
* Time, in milliseconds, to wait before the next retry attempt.
*
* @param toDisk
* <code>SOFT</code>, <code>HARD</code> or <code>GROUP</code> 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 <code>retryCount+1</code> 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 "<not running>";
}
}
/**
* @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 <code>Transaction</code>
* context has rolled back since the last successful <code>commit</code>
* 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].
* <p>
* Also see {@link #incrementStep()} for step semantics.
* </p>
*
* @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;
}
void addLockPage(final Long page, final int treeHandle) {
_lockCleanupActions.add(new CleanupPruneAction(treeHandle, page));
}
void pruneLockPages() {
if (_lockCleanupActions.isEmpty()) {
return;
}
_persistit.getTransactionIndex().updateActiveTransactionCache(_commitTimestamp);
List<CleanupAction> actions = new ArrayList<CleanupAction>(_lockCleanupActions);
_lockCleanupActions.clear();
while (!actions.isEmpty()) {
final List<CleanupAction> consequentActions = new ArrayList<CleanupAction>();
for (final CleanupAction cleanupAction : actions) {
try {
cleanupAction.performAction(_persistit, consequentActions);
} catch (final PersistitException pe) {
_persistit.getLogBase().pruneException.log(pe, this);
}
actions = consequentActions;
}
}
}
/**
* For unit tests only
*
* @return the buffer used to accumulate update records for this transaction
*/
ByteBuffer getTransactionBuffer() {
return _buffer;
}
}