JBoss.orgCommunity Documentation

Chapter 6. Constructing a Transactional Objects for Java application

6.1. Queue description
6.2. Constructors and destructors
6.3. Required methods
6.3.1. save_state, restore_state, and type
6.3.2. enqueue and dequeue methods
6.3.3. queueSize method
6.3.4. inspectValue and setValue methods
6.4. The client
6.5. Comments

Development Phases of a TxCore Application

  1. First, develop new classes with characteristics like persistence, recoverability, and concurrency control.

  2. Then develop the applications that make use of the new classes of objects.

Although these two phases may be performed in parallel and by a single person, this guide refers to the first step as the job of the class developer, and the second as the job of the applications developer. The class developer defines appropriate save_state and restore_state operations for the class, sets appropriate locks in operations, and invokes the appropriate TxCore class constructors. The applications developer defines the general structure of the application, particularly with regard to the use of atomic actions.

This chapter outlines a simple application, a simple FIFO Queue class for integer values. The Queue is implemented with a doubly linked list structure, and is implemented as a single object. This example is used throughout the rest of this manual to illustrate the various mechanisms provided by TxCore. Although this is an unrealistic example application, it illustrates all of the TxCore modifications without requiring in depth knowledge of the application code.

Note

The application is assumed not to be distributed. To allow for distribution, context information must be propagated either implicitly or explicitly.

The queue is a traditional FIFO queue, where elements are added to the front and removed from the back. The operations provided by the queue class allow the values to be placed on to the queue (enqueue) and to be removed from it (dequeue), and values of elements in the queue can also be changed or inspected. In this example implementation, an array represents the queue. A limit of QUEUE_SIZE elements has been imposed for this example.


Using an existing persistent object requires the use of a special constructor that takes the Uid of the persistent object, as shown in Example 6.2, “Class TransactionalQueue”.


The use of an atomic action within the constructor for a new object follows the guidelines outlined earlier and ensures that the object’s state will be written to the object store when the appropriate top level atomic action commits (which will either be the action A or some enclosing action active when the TransactionalQueue was constructed). The use of atomic actions in a constructor is simple: an action must first be declared and its begin operation invoked; the operation must then set an appropriate lock on the object (in this case a WRITE lock must be acquired), then the main body of the constructor is executed. If this is successful the atomic action can be committed, otherwise it is aborted.

The destructor of the queue class is only required to call the terminate operation of LockManager.

public void finalize ()

{
    super.terminate();
}     



If the operations of the queue class are to be coded as atomic actions, then the enqueue operation might have the structure given below. The dequeue operation is similarly structured, but is not implemented here.

Example 6.6. Method enqueue

public void enqueue (int v) throws OverFlow, UnderFlow, QueueError

{
    AtomicAction A = new AtomicAction();
    boolean res = false;
    try
        {
            A.begin(0);
            if (setlock(new Lock(LockMode.WRITE), 0) == LockResult.GRANTED)
                {
                    if (numberOfElements < QUEUE_SIZE)
                        {
                            elements[numberOfElements] = v;
                            numberOfElements++;
                            res = true;
                        }
                    else
                        {
                            A.rollback();
                            throw new UnderFlow();
                        }
                }
            if (res)
                A.commit(true);
            else
                {
                    A.rollback();
                    throw new Conflict();
                }
        }
    catch (Exception e1)
        {
            throw new QueueError();
        }
}       

Example 6.8. Method inspectValue

public int inspectValue (int index) throws UnderFlow,

                                           OverFlow, Conflict, QueueError
{
    AtomicAction A = new AtomicAction();
    boolean res = false;
    int val = -1;
    try
        {
            A.begin();
            if (setlock(new Lock(LockMode.READ), 0) == LockResult.GRANTED)
                {
                    if (index < 0)
                        {
                            A.rollback();
                            throw new UnderFlow();
                        }
                    else
                        {
                            // array is 0 - numberOfElements -1
                            if (index > numberOfElements -1)
                                {
                                    A.rollback();
                                    throw new OverFlow();
                                }
                            else
                                {
                                    val = elements[index];
                                    res = true;
                                }
                        }
                }
            if (res)
                A.commit(true);
            else
                {
                    A.rollback();
                    throw new Conflict();
                }
        }
    catch (Exception e1)
        {
            throw new QueueError();
        }
    return val;
}

Rather than show all of the code for the client, this example concentrates on a representative portion. Before invoking operations on the object, the client must first bind to the object. In the local case this simply requires the client to create an instance of the object.


Since the queue object is persistent, the state of the object survives any failures of the node on which it is located. The state of the object that survives is the state produced by the last top-level committed atomic action performed on the object. If an application intends to perform two enqueue operations atomically, for example, you can nest the enqueue operations in another enclosing atomic action. In addition, concurrent operations on such a persistent object are serialized, preventing inconsistencies in the state of the object.

However, since the elements of the queue objects are not individually concurrency controlled, certain combinations of concurrent operation invocations are executed serially, even though logically they could be executed concurrently. An example of this is modifying the states of two different elements in the queue. The platform Development Guide addresses some of these issues.