| Spec Index | Jini Technology Core Platform Specifications |
| Version 2.0 |
TX - JiniTM Transaction Specification
TX.1 Introduction
Transactions are a fundamental tool for many kinds of computing. A transaction allows a set of operations to be grouped in such a way that they either all succeed or all fail; further, the operations in the set appear from outside the transaction to occur simultaneously. Transactional behaviors are especially important in distributed computing, where they provide a means for enforcing consistency over a set of operations on one or more remote participants. If all the participants are members of a transaction, one response to a remote failure is to abort the transaction, thereby ensuring that no partial results are written.
Traditional transaction systems often center around transaction processing monitors that ensure that the correct implementation of transactional semantics is provided by all of the participants in a transaction. Our approach to transactional semantics is somewhat different. Within our system we leave it to the individual objects that take part in a transaction to implement the transactional semantics in the way that is best for that kind of object. What the system primarily provides is the coordination mechanism that those objects can use to communicate the information necessary for the set of objects to agree on the transaction. The goal of this system is to provide the minimal set of protocols and interfaces that allow objects to implement transaction semantics rather than the maximal set of interfaces, protocols, and policies that ensure the correctness of any possible transaction semantics. So the completion protocol is separate from the semantics of particular transactions.
This document presents this completion protocol, which consists of a two-phase commit protocol for distributed transactions. The two-phase commit protocol defines the communication patterns that allow distributed objects and resources to wrap a set of operations in such a way that they appear to be a single operation. The protocol requires a manager that will enable consistent resolution of the operations by a guarantee that all participants will eventually know whether they should commit the operations (roll forward) or abort them (roll backward). A participant can be any object that supports the participant contract by implementing the appropriate interface. Participants are not limited to databases or other persistent storage services.
Clients and servers will also need to depend on specific transaction semantics. The default transaction semantics for participants is also defined in this document.
The two-phase commit protocol presented here, while common in many traditional transaction systems, has the potential to be used in more than just traditional transaction processing applications. Since the semantics of the individual operations and the mechanisms that are used to ensure various properties of the meta-operation joined by the protocol are left up to the individual objects, variations of the usual properties required by transaction processing systems are possible using this protocol, as long as those variances can be resolved by this protocol. A group of objects could use the protocol, for example, as part of a process allowing synchronization of data that have been allowed to drift for efficiency reasons. While this use is not generally considered to be a classical use of transactions, the protocol defined here could be used for this purpose. Some variations will not be possible under these protocols, requiring subinterfaces and subclasses of the ones provided or entirely new interfaces and classes.
Because of the possibility of application to situations that are beyond the usual use of transactions, calling the two-phase commit protocol a transaction mechanism is somewhat misleading. However, since the most common use of such a protocol is in a transactional setting, and because we do define a particular set of default transaction semantics, we will follow the usual naming conventions used in such systems rather than attempting to invent a new, parallel vocabulary.
The classes and interfaces defined by this specification are in the packages
net.jini.core.transactionandnet.jini.core.transaction.server. In this document you will usually see these types used without a package prefix; as each type is defined, the package it is in is specified.TX.1.1 Model and Terms
A transaction is created and overseen by a manager. Each manager implements the interface
TransactionManager. Each transaction is represented by alongidentifier that is unique with respect to the transaction's manager.Semantics are represented by semantic transaction objects, such as the ones that represent the default semantics for services. Even though the manager needs to know only how to complete transactions, clients and participants need to share a common view of the semantics of the transaction. Therefore clients typically create, pass, and operate on semantic objects that contain the transaction identifier instead of using the transaction's identifier directly, and transactable services typically accept parameters of a particular semantic type, such as the
Transactioninterface used for the default semantics.As shown in Figure TX.1.1, a client asks the manager to create a transaction, typically by using a semantic factory class such as
TransactionFactoryto create a semantic object. The semantic object created is then passed as a parameter when performing operations on a service. If the service is to accept this transaction and govern its operations thereby, it must join the transaction as a participant. Participants in a transaction must implement theTransactionParticipantinterface. Particular operations associated with a given transaction are said to be performed under that transaction. The client that created the transaction might or might not be a participant in the transaction.Figure TX.1.1: Transaction Creation and Use
![]()
A transaction completes when any entity either commits or aborts the transaction. If a transaction commits successfully, then all operations performed under that transaction will complete. Aborting a transaction means that all operations performed under that transaction will appear never to have happened.
Committing a transaction requires each participant to vote, where a vote is either prepared (ready to commit), not changed (read-only), or aborted (the transaction should be aborted). If all participants vote "prepared" or "not changed," the transaction manager will tell each "prepared" participant to roll forward, thus committing the changes. Participants that voted "not changed" need do nothing more. If the transaction is ever aborted, the participants are told to roll back any changes made under the transaction.
TX.1.2 Distributed Transactions and ACID Properties
The two-phase commit protocol is designed to enable objects to provide ACID properties. The default transaction semantics define one way to preserve these properties. The ACID properties are:
- Atomicity: All the operations grouped under a transaction occur or none of them do. The protocol allows participants to discover which of these alternatives is expected by the other participants in the protocol. However, it is up to the individual object to determine whether it wishes to operate in concert with the other participants.
- Consistency: The completion of a transaction must leave the system in a consistent state. Consistency includes issues known only to humans, such as that an employee should always have a manager. The enforcement of consistency is outside of the realm of the transaction itself--a transaction is a tool to allow consistency guarantees and not itself a guarantor of consistency.
- Isolation: Ongoing transactions should not affect each other. Participants in a transaction should see only intermediate states resulting from the operations of their own transaction, not the intermediate states of other transactions. The protocol allows participating objects to know what operations are being done within the scope of a transaction. However, it is up to the individual object to determine if such operations are to be reflected only within the scope of the transaction or can be seen by others who are not participating in the transaction.
- Durability: The results of a transaction should be as persistent as the entity on which the transaction commits. However, such guarantees are up to the implementation of the object.
The dependency on the participant's implementation for the ACID properties is the greatest difference between this two-phase commit protocol and more traditional transaction processing systems. Such systems attempt to ensure that the ACID properties are met and go to considerable trouble to ensure that no participant can violate any of the properties.
This approach differs for both philosophical and practical reasons. The philosophical reason is centered on a basic tenet of object-oriented programming, which is that the implementation of an object should be hidden from any part of the system outside the object. Ensuring the ACID properties generally requires that an object's implementation correspond to certain patterns. We believe that if these properties are needed, the object (or, more precisely, the programmer implementing the object) will know best how to guarantee the properties. For this reason, the manager is solely concerned with completing transactions properly. Clients and participants must agree on semantics separately.
The practical reason for leaving the ACID properties up to the object is that there are situations in which only some of the ACID properties make sense, but that can still make use of the two-phase commit protocol. A group of transient objects might wish to group a set of operations in such a way that they appear atomic; in such a situation it makes little sense to require that the operations be durable. An object might want to enable the monitoring of the state of some long-running transactions; such monitoring would violate the isolation requirement of the ACID properties. Binding the two-phase commit protocol to all of these properties limits the use of such a protocol.
We also know that particular semantics are needed for particular services. The default transaction semantics provide useful general-purpose semantics built on the two-phase commit completion protocol.
Distributed transactions differ from single-system transactions in the same way that distributed computing differs from single-system computing. The clearest difference is that a single system can have a single view of the state of several services. It is possible in a single system to make it appear to any observer that all operations performed under a transaction have occurred or none have, thereby achieving isolation. In other words, no observer will ever see only part of the changes made under the transaction. In a distributed system it is possible for a client using two servers to see the committed state of a transaction in one server and the pre-committed state of the same transaction in another server. This can be prevented only by coordination with the transaction manager or the client that committed the transaction. Coordination between clients is outside the scope of this specification.
TX.1.3 Requirements
The transaction system has the following requirements:
- Define types and contracts that allow the two-phase commit protocol to govern operations on multiple servers of differing types or implementations.
- Allow participation in the two-phase commit protocol by any object in the JavaTM programming language, where "participation" means to perform operations on that object under a given transaction.
- Each participant may provide ACID properties with respect to that participant to observers operating under a given transaction.
- Use standard Java programming language techniques and tools to accomplish these goals. Specifically, transactions will rely upon Java Remote Method Invocation (Java RMI) semantics to communicate between participants.
- Define specific default transaction semantics for use by services.
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 aTransactionParticipantobject 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.LeaseAll the methods defined to throw
RemoteExceptionwill do so in the circumstances described by the Java 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
TransactionManagerinterface 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
TransactionManagerobject 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
createmethod, providing a desiredleaseFortime 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 throwingLeaseDeniedException. If the granted lease expires or is cancelled before the transaction manager receives acommitorabortof the transaction, the manager will abort the transaction.The purpose of the
Creatednested class is to allow thecreatemethod 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.createmethod returns a new top-level transaction. Managers that implement just theTransactionManagerinterface support only top-level transactions. Nested transactions, also known as subtransactions, can be created using managers that implement theNestableTransactionManagerinterface: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
createmethod 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
createmethod throwsUnknownTransactionExceptionif 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
createmethod throwsCannotJoinExceptionif 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
joinmethod with an object that implements theTransactionParticipantinterface. 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
joinmethod throwsRemoteException, the participant should not perform the operation requested by the client and should rethrow the exception or otherwise signal failure to the client.The
joinmethod'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.When a manager receives a
joinrequest, 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 originaljoin, thejoinis accepted but is otherwise ignored. If the crash count is different, the manager throwsCrashCountExceptionand 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
joinidempotent when it should be, but forces an abort for a secondjoinof a transaction by a participant that has no knowledge of the firstjoinand hence has lost whatever changes were made after the firstjoin.An invocation of
joincan throwUnknownTransactionException, 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. Thejoinmethod throwsCannotJoinExceptionif the transaction is known to the manager but is no longer active. In either case thejoinhas failed, and the method that was attempted under the transaction should reflect the exception back to the client. This is also the proper response ifjointhrows aNoSuchObjectException.TX.2.4 Transaction States
The
TransactionConstantsinterface 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
ACTIVEas soon ascreatereturns. The client drives the transaction to completion by invokingcommitoraborton the transaction manager, or by cancelling the lease or letting the lease expire (both of which are equivalent to anabort).The one-parameter
commitmethod returns as soon as the transaction successfully reaches theCOMMITTEDstate, or if the transaction is known to have previously reached that state due to an earliercommit. If the transaction reaches theABORTEDstate, or is known to have previously reached that state due to an earliercommitorabort, thencommitthrowsCannotCommitException.package net.jini.core.transaction; public class CannotCommitException extends TransactionException { public CannotCommitException() {...} public CannotCommitException(String desc) {...} }The one-parameter
abortmethod returns as soon as the transaction successfully reaches theABORTEDstate, or if the transaction is known to have previously reached that state due to an earliercommitorabort. If the transaction is known to have previously reached theCOMMITTEDstate due to an earliercommit, thenabortthrowsCannotAbortException.package net.jini.core.transaction; public class CannotAbortException extends TransactionException { public CannotAbortException() {...} public CannotAbortException(String desc) {...} }Both
commitandabortcan throwUnknownTransactionException, 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
commitandabortmethods take an additionalwaitFortimeout 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, aTimeoutExpiredExceptionwill be thrown. If the timeout expires before the transaction reaches theCOMMITTEDorABORTEDstate, the manager must wait until one of those states is reached before throwing the exception. Thecommittedfield in the exception is set totrueif the transaction committed or tofalseif 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
ACTIVEas soon asjoinreturns. Any operations attempted under a transaction are valid only if the participant has the transaction in theACTIVEstate. 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 isVOTINGuntil it decides what to return. There are three possible return values forprepare:
- 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 theNOTCHANGEDstate. As noted below, a well-behaved participant should stay in theNOTCHANGEDstate for some time to allow idempotentcy forprepare.
- 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
commitinvocation. When the participant has successfully prepared itself to roll forward (see Section TX.2.8 "Crash Recovery"), it must returnPREPARED, thereby entering thePREPAREDstate.
- 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 theABORTEDstate.For top-level transactions, when a participant returns
PREPAREDit is stating that it is ready to roll the changes forward by saving the necessary record of the operations for a futurecommitcall. 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 thePREPAREDstate until it is told tocommitorabort. It cannot, having returnedPREPARED, 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
PREPAREDit 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
prepareis invoked for that transaction, the participant must either: wait until that operation is complete before returning fromprepare; 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
getStateon the manager. IfgetStatethrowsUnknownTransactionExceptionorNoSuchObjectException, the participant may safely infer that the transaction has been aborted. IfgetStatethrows aRemoteExceptionthe 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 hasPREPAREDbut not yet rolled forward.If a participant has joined a nested transaction and it receives a
preparecall for an enclosing transaction, the participant must complete the nested transaction, usinggetStateon the manager to determine the proper type of completion.If a participant receives a
preparecall for a transaction that is already in a post-VOTINGstate, the participant should simply respond with that state.If a participant receives a
preparecall for a transaction that is unknown to it, it should throwUnknownTransactionException. This may happen if the participant has crashed and lost the state of a previously active transaction, or if a previousNOTCHANGEDorABORTEDresponse was not received by the manager and the participant has since forgotten the transaction.Note that a return value of
NOTCHANGEDmay not be idempotent. Should the participant returnNOTCHANGEDit may proceed directly to clean up its state. If the manager receives aRemoteExceptionbecause of network failure, the manager will likely retry theprepare. At this point a participant that has dropped the information about the transaction will throwUnknownTransactionException, and the manager will be forced to abort. A well-behaved participant should stay in theNOTCHANGEDstate for a while to allow a retry ofprepareto again returnNOTCHANGED, 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-invokeprepare.If a participant receives an
abortcall for a transaction, whether in theACTIVE,VOTING, orPREPAREDstate, it should move to theABORTEDstate and roll back all changes made under the transaction.If a participant receives a
commitcall for aPREPAREDtransaction, it should move to theCOMMITTEDstate and roll forward all changes made under the transaction.The participant's implementation of
prepareAndCommitmust be equivalent to the following:public int prepareAndCommit(TransactionManager mgr, long id) throws UnknownTransactionException, RemoteException { if (outcome for id is already known) { return that outcome; } int result = prepare(mgr, id); if (result == PREPARED) { commit(mgr, id); result = COMMITTED; } return result; }The participant can often implement
prepareAndCommitmuch more efficiently than shown, but it must preserve the above semantics. The manager's use of this method is described in the next section.Note that a call to
prepareAndCommitmight not be idempotent. Once the participant moves to theNOTCHANGED,ABORTED, orCOMMITTEDstate it may then proceed directly to cleanup. If the manager receives aRemoteExceptionbecause of network failure, the manager will likely retry theprepareAndCommit. At this point a participant that has dropped the information about the transaction will throwUnknownTransactionException, and the manager will be unable to determine the outcome of the previousprepareAndCommitcall. Although not strictly required, a well-behaved participant should remain in its current state for a while to allow for retries ofprepareAndCommit.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 isACTIVE. This is the only state in which participants mayjointhe transaction. Attempting to join the transaction in any other state throws aCannotJoinException.Invoking the manager's
commitmethod causes the manager to move to theVOTINGstate, in which it attempts to complete the transaction by rolling forward. Each participant that has joined the transaction has itspreparemethod invoked to vote on the outcome of the transaction. The participant may return one of three votes:NOTCHANGED,ABORTED, orPREPARED.If a participant votes
ABORTED, the manager must abort the transaction. IfpreparethrowsUnknownTransactionExceptionorNoSuchObjectException, the participant has lost its state of the transaction, and the manager must abort the transaction. IfpreparethrowsRemoteException, 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
ABORTEDstate. In theABORTEDstate, the manager should invokeaborton all participants that have votedPREPARED. The manager should also attempt to invokeaborton all participants on which it has not yet invokedprepare. These notifications are not strictly necessary for the one-parameter forms ofcommitandabort, 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 voteNOTCHANGEDthen the entire transaction was read-only and no participant has any changes to roll forward. The transaction moves to theCOMMITTEDstate 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 aNOTCHANGEDtransaction and one that has completed the notification phase of theCOMMITTEDstate.If no participant votes
ABORTEDand at least one participant votesPREPARED, the transaction also moves to theCOMMITTEDstate. In theCOMMITTEDstate the manager must notify each participant that returnedPREPAREDto roll forward by invoking the participant'scommitmethod. When the participant'scommitmethod returns normally, the participant has rolled forward successfully and the manager need not invokecommiton 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 invokecommitat reasonable intervals. If a participant'scommitmethod throwsUnknownTransactionException, 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 calledgetStatedirectly.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
promotemethod is designed for this use in the case in which the parent and nested transactions have different managers.The
promotemethod takes arrays of participants and crash counts, wherecrashCounts[i]is the crash count forparts[i]. If any crash count is different from a crash count that is already known to the parent transaction manager, the parent manager throwsCrashCountExceptionand the parent transaction must abort. Thedropparameter 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
ABORTEDin the parent transaction. The manager may ceasecommitinvocations 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
abortmethod, cancelling the transaction's lease, or allowing the lease to expire also moves the transaction to theABORTEDstate. Any transactions nested inside that transaction are also moved directly to theABORTEDstate.If all but one of the participants in a transaction have returned
NOTCHANGED, the manager may optimize theVOTINGstate by invoking that participant'sprepareAndCommitmethod. (Note that this includes the special case in which the transaction has exactly one participant.) If the manager receives anABORTEDresult fromprepareAndCommit, it proceeds to theABORTEDstate. In effect, aprepareAndCommitmoves through theVOTINGstate straight to operating on the results.As previously noted, a call to
prepareAndCommitmight not be idempotent. If the manager receives aRemoteExceptionbecause of network failure, but the participant has successfully moved to the cleanup stage, then the manager will be unable to determine the result of a previousprepareAndCommitcall by simply retrying it. At this point, the participant has dropped the information about the transaction and will throwUnknownTransactionExceptionon any retry attempt. When this occurs, the manager must indicate this condition by throwing aRemoteExceptionback to the client. (Note that there is no added benefit of usingpreparethencommitversusprepareAndCommitin this case.)A
getStatecall on the manager can return any ofACTIVE,VOTING,ABORTED,NOTCHANGED, orCOMMITTED. A manager is permitted, but not required, to returnNOTCHANGEDif it is in theCOMMITTEDstate and all participants votedNOTCHANGED.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
COMMITTEDstate with at least onePREPAREDparticipant. The manager must, at this point, commit the list ofPREPAREDparticipants into durable storage. This storage must persist until allPREPAREDparticipants successfully roll forward. A manager may choose to also store the list ofPREPAREDparticipants that have already successfully rolled forward or to rewrite the list ofPREPAREDparticipants 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 theCOMMITTEDstate until it can successfully notify allPREPAREDparticipants.The participant also has one commit point, which is prior to voting
PREPARED. When it votesPREPARED, the participant must have durably recorded the record of changes necessary to successfully roll forward in the event of a future invocation ofcommitby the manager. It can remove this record when it is prepared to successfully return from acommitor anabort.Because of these commitments, manager and participant implementations should use durable forms of Java RMI references, such as the
Activatablereferences introduced in the JavaTM 2 platform. An unreachable manager causes much havoc and should be avoided as much as possible. A vanishedPREPAREDparticipant 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
PREPAREDfor a top-level transaction, it must guarantee that it will execute a recovery process if it crashes between completing its durable record and receiving acommitnotification 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
getStatemethod on the transaction manager. This can have the following results:
getStatereturnsCOMMITTED: The recovery should move the participant to theCOMMITTEDstate.
getStatereturnsABORTED: The recovery should move the participant to theABORTEDstate.
getStatethrows either anUnknownTransactionExceptionor aNoSuchObjectException: The recovery should move the participant to theABORTEDstate.
getStatethrowsRemoteException: 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.
TX.3 Default Transaction Semantics
The two-phase commit protocol defines how a transaction is created and later driven to completion by either committing or aborting. It is neutral with respect to the semantics of locking under the transaction or other behaviors that impart semantics to the use of the transaction. Specific clients and servers, however, must be written to expect specific transaction semantics. This model is to separate the completion protocol from transaction semantics, where transaction semantics are represented in the parameters and return values of methods by which clients and participants interact.
This chapter defines the default transaction semantics of services. These semantics preserve the traditional ACID properties (you will find a brief description of the ACID properties in Section TX.1.2 "Distributed Transactions and ACID Properties"). The semantics are represented by the
TransactionandNestableTransactioninterfaces and their implementation classesServerTransactionandNestableServerTransaction. Any participant that accepts as a parameter or returns any of these types is promising to abide by the following definition of semantics for any activities performed under that transaction.TX.3.1
TransactionandNestableTransactionInterfacesThe client's view of transactions is through two interfaces:
Transactionfor top-level transactions andNestableTransactionfor transactions under which nested transactions can be created. First, theTransactioninterface:package net.jini.core.transaction; public interface Transaction { public static class Created implements Serializable { public final Transaction transaction; public final Lease lease; Created(Transaction transaction, Lease lease) {...} } void commit() // §TX.2.5 throws UnknownTransactionException, CannotCommitException, RemoteException; void commit(long waitFor) // §TX.2.5 throws UnknownTransactionException, CannotCommitException, TimeoutExpiredException, RemoteException; void abort() // §TX.2.5 throws UnknownTransactionException, CannotAbortException, RemoteException; void abort(long waitFor) // §TX.2.5 throws UnknownTransactionException, CannotAbortException, TimeoutExpiredException, RemoteException; }The
Creatednested class is used in a factorycreatemethod for top-level transactions (defined in the next section) to hold two return values: the newly createdTransactionobject and the transaction's lease, which is the lease granted by the transaction manager. Thecommitandabortmethods have the same semantics as discussed in Section TX.2.5 "Completing a Transaction: The Client's View".Nested transactions are created using
NestableTransactionmethods:package net.jini.core.transaction; public interface NestableTransaction extends Transaction { public static class Created implements Serializable { public final NestableTransaction transaction; public final Lease lease; Created(NestableTransaction transaction, Lease lease) {...} } Created create(long leaseFor) // §TX.2.2 throws UnknownTransactionException, CannotJoinException, LeaseDeniedException, RemoteException; Created create(NestableTransactionManager mgr, long leaseFor) // §TX.2.2 throws UnknownTransactionException, CannotJoinException, LeaseDeniedException, RemoteException; }The
Creatednested class is used to hold two return values: the newly createdTransactionobject and the transaction's lease, which is the lease granted by the transaction manager. In bothcreatemethods,leaseForis the requested lease time in milliseconds. In the one-parametercreatemethod the nested transaction is created with the same transaction manager as the transaction on which the method is invoked. The othercreatemethod can be used to specify a different transaction manager to use for the nested transaction.TX.3.2
TransactionFactoryClassThe
TransactionFactoryclass is used to create top-level transactions.package net.jini.core.transaction; public class TransactionFactory { public static Transaction.Created create(TransactionManager mgr, long leaseFor) // §TX.2.1 throws LeaseDeniedException, RemoteException {...} public static NestableTransaction.Created create(NestableTransactionManager mgr,long leaseFor) // §TX.2.2 throws LeaseDeniedException, RemoteException {...} }The first
createmethod is usually used when nested transactions are not required. However, if the manager that is passed to this method is in fact aNestableTransactionManager, then the returnedTransactioncan in fact be cast to aNestableTransaction. The secondcreatemethod is used when it is known that nested transactions need to be created. In both cases, aCreatedinstance is used to hold two return values: the newly created transaction object and the granted lease.TX.3.3
ServerTransactionandNestableServerTransactionClassesThe
ServerTransactionclass exposes functionality necessary for writing participants that support top-level transactions. Participants can cast aTransactionto aServerTransactionto obtain access to this functionality.public class ServerTransaction implements Transaction, Serializable { public final TransactionManager mgr; public final long id; public ServerTransaction(TransactionManager mgr, long id) {...} public void join(TransactionParticipant part, long crashCount) // §TX.2.3 throws UnknownTransactionException, CannotJoinException, CrashCountException, RemoteException {...} public int getState() // §TX.2.7 throws UnknownTransactionException, RemoteException {...} public boolean isNested() {...} // §TX.3.3 }The
mgrfield is a reference to the transaction manager that created the transaction. Theidfield is the transaction identifier returned by the transaction manager'screatemethod.The constructor should not be used directly; it is intended for use by the
TransactionFactoryimplementation.The methods
join,commit,abort, andgetStateinvoke the corresponding methods on the manager, passing the transaction identifier. They are provided as a convenience to the programmer, primarily to eliminate the possibility of passing an identifier to the wrong manager. For example, given aServerTransactionobjecttr, the invocationtr.join(participant, crashCount);tr.mgr.join(tr.id, participant, crashCount);The
isNestedmethod returnstrueif the transaction is a nested transaction (that is, if it is aNestableServerTransactionwith a non-nullparent) andfalseotherwise. It is provided as a method onServerTransactionfor the convenience of participants that do not support nested transactions.The
hashCodemethod returns theidcast to anintXORed with the result ofmgr.hashCode(). Theequalsmethod returnstrueif the specified object is aServerTransactionobject with the same manager and transaction identifier as the object on which it is invoked.The
NestableServerTransactionclass exposes functionality that is necessary for writing participants that support nested transactions. Participants can cast aNestableTransactionto aNestableServerTransactionto obtain access to this functionality.package net.jini.core.transaction.server; public class NestableServerTransaction extendsServerTransaction implements NestableTransaction { public final NestableServerTransaction parent; public NestableServerTransaction( NestableTransactionManager mgr, long id, NestableServerTransaction parent) {...} public void promote(TransactionParticipant[] parts, long[] crashCounts, TransactionParticipant drop) // §TX.2.7 throws UnknownTransactionException, CannotJoinException, CrashCountException, RemoteException {...} public boolean enclosedBy(NestableTransaction enclosing) {...} }The
parentfield is a reference to the parent transaction if the transaction is nested (see Section TX.2.2 "Starting a Nested Transaction") ornullif it is a top-level transaction.The constructor should not be used directly; it is intended for use by the
TransactionFactoryandNestableServerTransactionimplementations.Given a
NestableServerTransactionobjecttr, the invocationtr.promote(parts, crashCounts, drop)((NestableTransactionManager)tr.mgr).promote(tr.id, parts, crashCounts, drop)The
enclosedBymethod returnstrueif the specified transaction is an enclosing transaction (parent, grandparent, etc.) of the transaction on which the method is invoked; otherwise it returnsfalse.TX.3.4
CannotNestExceptionClassIf a service implements the default transaction semantics but does not support nested transactions, it usually needs to throw an exception if a nested transaction is passed to it. The
CannotNestExceptionis provided as a convenience for this purpose, although a service is not required to use this specific exception.package net.jini.core.transaction; public class CannotNestException extends TransactionException { public CannotNestException() {...} public CannotNestException(String desc) {...} }TX.3.5 Semantics
Activities that are performed as pure transactions (all access to shared mutable state is performed under transactional control) are subject to sequential ordering, meaning the overall effect of executing a set of sibling (all at the same level, whether top-level or nested) pure transactions concurrently is always equivalent to some sequential execution.
Ancestor transactions can execute concurrently with child transactions, subject to the locking rules below.
Transaction semantics for objects are defined in terms of strict two-phase locking. Every transactional operation is described in terms of acquiring locks on objects; these locks are held until the transaction completes. The most typical locks are read and write locks, but others are possible. Whatever the lock types are, conflict rules are defined such that if two operations do not commute, then they acquire conflicting locks. For objects using standard read and write locks, read locks do not conflict with other read locks, but write locks conflict with both read locks and other write locks. A transaction can acquire a lock if the only conflicting locks are those held by ancestor transactions (or itself). If a necessary lock cannot be acquired and the operation is defined to proceed without waiting for that lock, then serializability might be violated. When a subtransaction commits, its locks are inherited by the parent transaction.
In addition to locks, transactional operations can be defined in terms of object creation and deletion visibility. If an object is defined to be created under a transaction, then the existence of the object is visible only within that transaction and its inferiors, but will disappear if the transaction aborts. If an object is defined to be deleted under a transaction, then the object is not visible to any transaction (including the deleting transaction) but will reappear if the transaction aborts. When a nested transaction commits, visibility state is inherited by the parent transaction.
Once a transaction reaches the
VOTINGstage, if all execution under the transaction (and its subtransactions) has finished, then the only reasons the transaction can abort are:
- The manager crashes (or has crashed)
- One or more participants crash (or have crashed)
- There is an explicit abort
Transaction deadlocks are not guaranteed to be prevented or even detected, but managers and participants are permitted to break known deadlocks by aborting transactions.
An active transaction is an orphan if it or one of its ancestors is guaranteed to abort. This can occur because an ancestor has explicitly aborted or because some participant or manager of the transaction or an ancestor has crashed. Orphans are not guaranteed to be detected by the system, so programmers using transactions must be aware that orphans can see internally inconsistent state and take appropriate action.
Causal ordering information about transactions is not guaranteed to be propagated. First, given two sibling transactions (at any level), it is not possible to tell whether they were created concurrently or sequentially (or in what order). Second, if two transactions are causally ordered and the earlier transaction has completed, the outcome of the earlier transaction is not guaranteed to be known at every participant used by the later transaction, unless the client is successful in using the variant of
commitorabortthat takes a timeout parameter. Programmers using non-blocking forms of operations must take this into account.Furthermore, the default semantics do not require the participants to implement atomic versions ofcommitandabort.As long as a transaction persists in attempting to acquire a lock that conflicts with another transaction, the participant will persist in attempting to resolve the outcome of the transaction that holds the conflicting lock. Attempts to acquire a lock include making a blocking call, continuing to make non-blocking calls, and registering for event notification under a transaction.
TX.3.6 Serialized Forms
TX.4 History
Version Description v1.0 Initial release of this specification. v2.0 Added History section (TX.4)
Clarified that transactions rely on Java RMI semantics (TX.1.3)
Removed crash count example (TX.2.3)
Clarified language with respect to abort and participants and corrected permissible return values for thepreparemethod. (TX.2.7)
Clarified commit point removal andgetStatesemantics (TX.2.8)
Clarified idempotentcy ofprepareAndCommit(TX.2.6, TX.2.7)
Clarified atomicity of participantabortandcommit(TX.3.5)
Changed semantics ofprepareAndCommitlogic (TX.2.6)License
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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.
| Spec Index | Jini Technology Core Platform Specifications |