TX.2. The Two-Phase Commit Protocol

The two-phase commit protocol is defined using three primary types:

  • TransactionManager: A transaction manager creates new transactions and coordinates the activities of the participants.

  • NestableTransactionManager: Some transaction managers are capable of supporting nested transactions.

  • TransactionParticipant: When an operation is performed under a transaction, the participant must join the transaction, providing the manager with a reference to a TransactionParticipant object that will be asked to vote, roll forward, or roll back.

The following types are imported from other packages and are referenced in unqualified form in the rest of this specification:

java.rmi.Remote 
java.rmi.RemoteException 
java.rmi.NoSuchObjectException 
java.io.Serializable 
net.jini.core.lease.LeaseDeniedException 
net.jini.core.lease.Lease 

All the methods defined to throw RemoteException will do so in the circumstances described by the RMI specification.

Each type is defined where it is first described. Each method is described where it occurs in the lifecycle of the two-phase commit protocol. All methods, fields, and exceptions that can occur during the lifecycle of the protocol will be specified. The section in which each method or field is specified is shown in a comment, using the § abbreviation for the word “section.”

TX.2.1. Starting a Transaction

The TransactionManager interface is implemented by servers that manage the two-phase commit protocol:

package net.jini.core.transaction.server; 

public interface TransactionManager 
    extends Remote, TransactionConstants // §TX.2.4 
{
    public static class Created implements Serializable {
        public final long id; 
        public final Lease lease; 
        public Created(long id, Lease lease) {...} 
    } 
    Created create(long leaseFor) // §TX.2.1 
        throws LeaseDeniedException, RemoteException; 
    void join(long id, TransactionParticipant part, 
              long crashCount) // §TX.2.3 
        throws UnknownTransactionException, 
               CannotJoinException, CrashCountException, 
               RemoteException; 
    int getState(long id) // §TX.2.7 
        throws UnknownTransactionException, RemoteException; 
    void commit(long id) // §TX.2.5 
        throws UnknownTransactionException, 
               CannotCommitException, 
               RemoteException; 
    void commit(long id, long waitFor) // §TX.2.5 
        throws UnknownTransactionException, 
               CannotCommitException, 
               TimeoutExpiredException, RemoteException; 
    void abort(long id) // §TX.2.5 
        throws UnknownTransactionException, 
               CannotAbortException, 
               RemoteException; 
    void abort(long id, long waitFor) // §TX.2.5 
        throws UnknownTransactionException, 
               CannotAbortException, 
               TimeoutExpiredException, RemoteException; 
} 

A client obtains a reference to a TransactionManager object via a lookup service or some other means. The details of obtaining such a reference are outside the scope of this specification.

A client creates a new transaction by invoking the manager’s create method, providing a desired leaseFor time in milliseconds. This invocation is typically indirect via creating a semantic object. The time is the client’s expectation of how long the transaction will last before it completes. The manager may grant a shorter lease or may deny the request by throwing LeaseDeniedException. If the granted lease expires or is cancelled before the transaction manager receives a commit or abort of the transaction, the manager will abort the transaction.

The purpose of the Created nested class is to allow the create method to return two values: the transaction identifier and the granted lease. The constructor simply sets the two fields from its parameters.

TX.2.2. Starting a Nested Transaction

The TransactionManager.create method returns a new top-level transaction. Managers that implement just the TransactionManager interface support only top-level transactions. Nested transactions, also known as subtransactions, can be created using managers that implement the NestableTransactionManager interface:

package net.jini.core.transaction.server; 

public interface NestableTransactionManager 
    extends TransactionManager 
{
    TransactionManager.Created 
        create(NestableTransactionManager parentMgr, 
               long parentID, long leaseFor) // §TX.2.2 
        throws UnknownTransactionException, 
               CannotJoinException, LeaseDeniedException, 
               RemoteException; 
    void promote(long id, TransactionParticipant[] parts, 
                 long[] crashCounts, 
                 TransactionParticipant drop) 
        throws UnknownTransactionException, 
               CannotJoinException, CrashCountException, 
               RemoteException; // §TX.2.7 
} 

The create method takes a parent transaction—represented by the manager for the parent transaction and the identifier for that transaction—and a desired lease time in milliseconds, and returns a new nested transaction that is enclosed by the specified parent along with the granted lease.

When you use a nested transaction you allow changes to a set of objects to abort without forcing an abort of the parent transaction, and you allow the commit of those changes to still be conditional on the commit of the parent transaction.

When a nested transaction is created, its manager joins the parent transaction. When the two managers are different, this is done explicitly via join (see Section TX.2.3 “Joining a Transaction”). When the two managers are the same, this may be done in a manager-specific fashion.

The create method throws UnknownTransactionException if the parent transaction is unknown to the parent transaction manager, either because the transaction ID is incorrect or because the transaction is no longer active and its state has been discarded by the manager.

package net.jini.core.transaction; 

public class UnknownTransactionException 
    extends TransactionException 
{
    public UnknownTransactionException() {...} 
    public UnknownTransactionException(String desc) {...} 
} 

public class TransactionException extends Exception {
    public TransactionException() {...} 
    public TransactionException(String desc) {...} 
} 

The create method throws CannotJoinException if the parent transaction is

known to the manager but is no longer active.

package net.jini.core.transaction; 

public class CannotJoinException extends TransactionException 
{
    public CannotJoinException() {...} 
    public CannotJoinException(String desc) {...} 
} 

TX.2.3. Joining a Transaction

The first time a client tells a participant to perform an operation under a given transaction, the participant must invoke the transaction manager’s join method with an object that implements the TransactionParticipant interface. This object will be used by the manager to communicate with the participant about the transaction.

package net.jini.core.transaction.server; 

public interface TransactionParticipant 
    extends Remote, TransactionConstants // §TX.2.4 
{
    int prepare(TransactionManager mgr, long id) // §TX.2.6 
        throws UnknownTransactionException, RemoteException; 
    void commit(TransactionManager mgr, long id) // §TX.2.6 
        throws UnknownTransactionException, RemoteException; 
    void abort(TransactionManager mgr, long id) // §TX.2.6 
        throws UnknownTransactionException, RemoteException; 
    int prepareAndCommit(TransactionManager mgr, long id) 
                                                // §TX.2.7 
        throws UnknownTransactionException, RemoteException; 
} 

If the participant’s invocation of the join method throws RemoteException, the participant should not perform the operation requested by the client and should rethrow the exception or otherwise signal failure to the client.

The join method’s third parameter is a crash count that uniquely defines the version of the participant’s storage that holds the state of the transaction. Each time the participant loses the state of that storage (because of a system crash if the storage is volatile, for example) it must change this count. For example, the participant could store the crash count in stable storage.

When a manager receives a join request, it checks to see if the participant has already joined the transaction. If it has, and the crash count is the same as the one specified in the original join, the join is accepted but is otherwise ignored. If the crash count is different, the manager throws CrashCountException and forces the transaction to abort.

package net.jini.core.transaction.server; 

public class CrashCountException extends TransactionException 
{
    public CrashCountException() {...} 
    public CrashCountException(String desc) {...} 
} 

The participant should reflect this exception back to the client. This check makes join idempotent when it should be, but forces an abort for a second join of a transaction by a participant that has no knowledge of the first join and hence has lost whatever changes were made after the first join.

An invocation of join can throw UnknownTransactionException, which means the transaction is unknown to the manager, either because the transaction ID was incorrect, or because the transaction is no longer active and its state has been discarded by the manager. The join method throws CannotJoinException if the transaction is known to the manager but is no longer active. In either case the join has failed, and the method that was attempted under the transaction should reflect the exception back to the client. This is also the proper response if join throws a NoSuchObjectException.

TX.2.4. Transaction States

The TransactionConstants interface defines constants used in the communication between managers and participants.

package net.jini.core.transaction.server; 

public interface TransactionConstants {
    int ACTIVE = 1; 
    int VOTING = 2; 
    int PREPARED = 3; 
    int NOTCHANGED = 4; 
    int COMMITTED = 5; 
    int ABORTED = 6; 
} 

These correspond to the states and votes that participants and managers go through during the lifecycle of a given transaction.

TX.2.5. Completing a Transaction: The Client’s View

In the client’s view, a transaction goes through the following states:

For the client, the transaction starts out ACTIVE as soon as create returns. The client drives the transaction to completion by invoking commit or abort on the transaction manager, or by cancelling the lease or letting the lease expire (both of which are equivalent to an abort).

The one-parameter commit method returns as soon as the transaction successfully reaches the COMMITTED state, or if the transaction is known to have previously reached that state due to an earlier commit. If the transaction reaches the ABORTED state, or is known to have previously reached that state due to an earlier commit or abort, then commit throws CannotCommitException.

package net.jini.core.transaction; 

public class CannotCommitException 
    extends TransactionException 
{
    public CannotCommitException() {...} 
    public CannotCommitException(String desc) {...} 
} 

The one-parameter abort method returns as soon as the transaction successfully reaches the ABORTED state, or if the transaction is known to have previously reached that state due to an earlier commit or abort. If the transaction is known to have previously reached the COMMITTED state due to an earlier commit, then abort throws CannotAbortException.

package net.jini.core.transaction; 

public class CannotAbortException extends TransactionException 
{
    public CannotAbortException() {...} 
    public CannotAbortException(String desc) {...} 
} 

Both commit and abort can throw UnknownTransactionException, which means the transaction is unknown to the manager. This may be because the transaction ID was incorrect, or because the transaction has proceeded to cleanup due to an earlier commit or abort, and has been forgotten.

Overloads of the commit and abort methods take an additional waitFor timeout parameter specified in milliseconds that tells the manager to wait until it has successfully notified all participants about the outcome of the transaction before the method returns. If the timeout expires before all participants have been notified, a TimeoutExpiredException will be thrown. If the timeout expires before the transaction reaches the COMMITTED or ABORTED state, the manager must wait until one of those states is reached before throwing the exception. The committed field in the exception is set to true if the transaction committed or to false if it aborted.

package net.jini.core.transaction; 

public class TimeoutExpiredException extends 
             TransactionException 
{
    public boolean committed; 
    public TimeoutExpiredException(boolean committed) {...} 
    public TimeoutExpiredException(String desc, 
                                   boolean committed) {...} 
} 

TX.2.6. Completing a Transaction: A Participant’s View

In a participant’s view, a transaction goes through the following states:

For the participant, the transaction starts out ACTIVE as soon as join returns. Any operations attempted under a transaction are valid only if the participant has the transaction in the ACTIVE state. In any other state, a request to perform an operation under the transaction should fail, signaling the invoker appropriately.

When the manager asks the participant to prepare, the participant is VOTING until it decides what to return. There are three possible return values for prepare:

  • The participant had no changes to its state made under the transaction—that is, for the participant the transaction was read-only. It should release any internal state associated with the transaction. It must signal this with a return of NOTCHANGED, effectively entering the NOTCHANGED state. As noted below, a well-behaved participant should stay in the NOTCHANGED state for some time to allow idempotency for prepare.

  • The participant had its state changed by operations performed under the transaction. It must attempt to prepare to roll those changes forward in the event of a future incoming commit invocation. When the participant has successfully prepared itself to roll forward (see Section TX.2.8 “Crash Recovery”), it must return PREPARED, thereby entering the PREPARED state.

  • The participant had its state changed by operations performed under the transaction but is unable to guarantee a future successful roll forward. It must signal this with a return of ABORTED, effectively entering the ABORTED state.

For top-level transactions, when a participant returns PREPARED it is stating that it is ready to roll the changes forward by saving the necessary record of the operations for a future commit call. The record of changes must be at least as durable as the overall state of the participant. The record must also be examined during recovery (see Section TX.2.8 “Crash Recovery”) to ensure that the participant rolls forward or rolls back as the manager dictates. The participant stays in the PREPARED state until it is told to commit or abort. It cannot, having returned PREPARED, drop the record except by following the “roll decision” described for crash recovery (see Section TX.2.8.1 “The Roll Decision”).

For nested transactions, when a participant returns PREPARED it is stating that it is ready to roll the changes forward into the parent transaction. The record of changes must be as durable as the record of changes for the parent transaction.

If a participant is currently executing an operation under a transaction when prepare is invoked for that transaction, the participant must either: wait until that operation is complete before returning from prepare; know that the operation is guaranteed to be read-only, and so will not affect its ability to prepare; or abort the transaction.

If a participant has not received any communication on or about a transaction over an extended period, it may choose to invoke getState on the manager. If getState throws UnknownTransactionException or NoSuchObjectException, the participant may safely infer that the transaction has been aborted. If getState throws a RemoteException the participant may choose to believe that the manager has crashed and abort its state in the transaction—this is not to be done lightly, since the manager may save state across crashes, and transient network failures could cause a participant to drop out of an otherwise valid and committable transaction. A participant should drop out of a transaction only if the manager is unreachable over an extended period. However, in no case should a participant drop out of a transaction it has PREPARED but not yet rolled forward.

If a participant has joined a nested transaction and it receives a prepare call for an enclosing transaction, the participant must complete the nested transaction, using getState on the manager to determine the proper type of completion.

If a participant receives a prepare call for a transaction that is already in a postVOTING state, the participant should simply respond with that state.

If a participant receives a prepare call for a transaction that is unknown to it, it should throw UnknownTransactionException. This may happen if the participant has crashed and lost the state of a previously active transaction, or if a previous NOTCHANGED or ABORTED response was not received by the manager and the participant has since forgotten the transaction.

Note that a return value of NOTCHANGED may not be idempotent. Should the participant return NOTCHANGED it may proceed directly to clean up its state. If the manager receives a RemoteException because of network failure, the manager will likely retry the prepare. At this point a participant that has dropped the information about the transaction will throw UnknownTransactionException, and the manager will be forced to abort. A well-behaved participant should stay in the NOTCHANGED state for a while to allow a retry of prepare to again return NOTCHANGED, thus keeping the transaction alive, although this is not strictly required. No matter what it voted, a well-behaved participant should also avoid exiting for a similar period of time in case the manager needs to re-invoke prepare.

If a participant receives an abort call for a transaction, whether in the ACTIVE, VOTING, or PREPARED state, it should move to the ABORTED state and roll back all changes made under the transaction.

If a participant receives a commit call for a PREPARED transaction, it should move to the COMMITTED state and roll forward all changes made under the transaction.

The participant’s implementation of prepareAndCommit must be equivalent to the following:

public int prepareAndCommit(TransactionManager mgr, long id) 
    throws UnknownTransactionException, RemoteException 
{
    int result = prepare(mgr, id); 
    if (result == PREPARED) {
        commit(mgr, id); 
        result = COMMITTED; 
    } 
    return result; 
} 

The participant can often implement prepareAndCommit much more efficiently than shown, but it must preserve the above semantics. The manager’s use of this method is described in the next section.

TX.2.7. Completing a Transaction: The Manager’s View

In the manager’s view, a transaction goes through the following states:

When a transaction is created using create, the transaction is ACTIVE. This is the only state in which participants may join the transaction. Attempting to join the transaction in any other state throws a CannotJoinException.

Invoking the manager’s commit method causes the manager to move to the VOTING state, in which it attempts to complete the transaction by rolling forward. Each participant that has joined the transaction has its prepare method invoked to vote on the outcome of the transaction. The participant may return one of three votes: NOTCHANGED, ABORTED, or COMMITTED.

If a participant votes ABORTED, the manager must abort the transaction. If prepare throws UnknownTransactionException or NoSuchObjectException, the participant has lost its state of the transaction, and the manager must abort the transaction. If prepare throws RemoteException, the manager may retry as long as it wishes until it decides to abort the transaction.

To abort the transaction, the manager moves to the ABORTED state. In the ABORTED state, the manager should invoke abort on all participants that have voted PREPARED. The manager should also attempt to invoke abort on all participants on which it has not yet invoked prepare. These notifications are not strictly necessary for the one-parameter forms of commit and abort, since the participants will eventually abort the transaction either by timing out or by asking the manager for the state of the transaction. However, informing the participants of the abort can speed up the release of resources in these participants, and so attempting the notification is strongly encouraged.

If a participant votes NOTCHANGED, it is dropped from the list of participants, and no further communication will ensue. If all participants vote NOTCHANGED then the entire transaction was read-only and no participant has any changes to roll forward. The transaction moves to the COMMITTED state and then can immediately move to cleanup, in which resources in the manager are cleaned up. There is no behavioral difference to a participant between a NOTCHANGED transaction and one that has completed the notification phase of the COMMITTED state.

If no participant votes ABORTED and at least one participant votes PREPARED, the transaction also moves to the COMMITTED state. In the COMMITTED state the manager must notify each participant that returned PREPARED to roll forward by invoking the participant’s commit method. When the participant’s commit method returns normally, the participant has rolled forward successfully and the manager need not invoke commit on it again. As long as there exists at least one participant that has not rolled forward successfully, the manager must preserve the state of the transaction and repeat attempts to invoke commit at reasonable intervals. If a participant’s commit method throws UnknownTransactionException, this means that the participant has already successfully rolled the transaction forward even though the manager did not receive the notification, either due to a network failure on a previous invocation that was actually successful or because the participant called getState directly.

If the transaction is a nested one and the manager is prepared to roll the transaction forward, the members of the nested transaction must become members of the parent transaction. This promotion of participants into the parent manager must be atomic—all must be promoted simultaneously, or none must be. The multi-participant promote method is designed for this use in the case in which the parent and nested transactions have different managers.

The promote method takes arrays of participants and crash counts, where crashCounts[i] is the crash count for parts[i]. If any crash count is different from a crash count that is already known to the parent transaction manager, the parent manager throws CrashCountException and the parent transaction must abort. The drop parameter allows the nested transaction manager to drop itself out of the parent transaction as it promotes its participants into the parent transaction if it no longer has any need to be a participant itself.

The manager for the nested transaction should remain available until it has successfully driven each participant to completion and promoted its participants into the parent transaction. If the nested transaction’s manager disappears before a participant is positively informed of the transaction’s completion, that participant will not know whether to roll forward or back, forcing it to vote ABORTED in the parent transaction. The manager may cease commit invocations on its participants if any parent transaction is aborted. Aborting any transaction implicitly aborts any uncommitted nested transactions. Additionally, since any committed nested transaction will also have its results dropped, any actions taken on behalf of that transaction can be abandoned.

Invoking the manager’s abort method, cancelling the transaction’s lease, or allowing the lease to expire also moves the transaction to the ABORTED state as described above. Any transactions nested inside that transaction are also moved directly to the ABORTED state.

The manager may optimize the VOTING state by invoking a participant’s prepareAndCommit method if the transaction has only one participant that has not yet been asked to vote and all previous participants have returned NOTCHANGED. (Note that this includes the special case in which the transaction has exactly one participant.) If the manager receives an ABORTED result from prepareAndCommit, it proceeds to the ABORTED state. In effect, a prepareAndCommit moves through the VOTING state straight to operating on the results.

A getState call on the manager can return any of ACTIVE, VOTING, ABORTED, NOTCHANGED, or COMMITTED. A manager is permitted, but not required, to return NOTCHANGED if it is in the COMMITTED state and all participants voted NOTCHANGED.

TX.2.8. Crash Recovery

Crash recovery ensures that a top-level transaction will consistently abort or roll forward in the face of a system crash. Nested transactions are not involved.

The manager has one commit point, where it must save state in a durable fashion. This is when it enters the COMMITTED state with at least one PREPARED participant. The manager must, at this point, commit the list of PREPARED participants into durable storage. This storage must persist until all PREPARED participants successfully roll forward. A manager may choose to also store the list of PREPARED participants that have already successfully rolled forward or to rewrite the list of PREPARED participants as it shrinks, but this optimization is not required (although it is recommended as good citizenship). In the event of a manager crash, the list of participants must be recovered, and the manager must continue acting in the COMMITTED state until it can successfully notify all PREPARED participants.

The participant also has one commit point, which is prior to voting PREPARED. When it votes PREPARED, the participant must have durably recorded the record of changes necessary to successfully roll forward in the event of a future invocation of commit by the manager. It can remove this record when it is prepared to successfully return from commit.

Because of these commitments, manager and participant implementations should use durable forms of RMI references, such as the Activatable references introduced in the Java 2 platform. An unreachable manager causes much havoc and should be avoided as much as possible. A vanished PREPARED participant puts a transaction in an untenable permanent state in which some, but not all, of the participants have rolled forward.

TX.2.8.1. The Roll Decision

If a participant votes PREPARED for a top-level transaction, it must guarantee that it will execute a recovery process if it crashes between completing its durable record and receiving a commit notification from the manager. This recovery process must read the record of the crashed participant and make a roll decision—whether to roll the recorded changes forward or roll them back.

To make this decision, it invokes the getState method on the transaction manager. This can have the following results:

  • getState returns COMMITTED: The recovery should move the participant to the COMMITTED state.

  • getState throws either an UnknownTransactionException or a NoSuchObjectException: The recovery should move the participant to the ABORTED state.

  • getState throws RemoteException: The recovery should repeat the attempt after a pause.

TX.2.9. Durability

Durability is a commitment, but it is not a guarantee. It is impossible to guarantee that any given piece of stable storage can never be lost; one can only achieve decreasing probabilities of loss. Data that is force-written to a disk may be considered durable, but it is less durable than data committed to two or more separate, redundant disks. When we speak of “durability” in this system it is always used relative to the expectations of the human who decided which entities to use for communication.

With multi-participant transactions it is entirely possible that different participants have different durability levels. The manager may be on a tightly replicated system with its durable storage duplicated on several host systems, giving a high degree of durability, while a participant may be using only one disk. Or a participant may always store its data in memory, expecting to lose it in a system crash (a database of people currently logged into the host, for example, need not survive a system crash). When humans make a decision to use a particular manager and set of participants for a transaction they must take into account these differences and be aware of the ramifications of committing changes that may be more durable on one participant than another. Determining, or even defining and exposing, varying levels of durability is outside the scope of this specification.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.237.201