All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.persistit.Transaction Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/**
 * 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy