Programming an Oracle Tuxedo ATMI Application Using C

     Previous  Next    Open TOC in new window    View as PDF - New Window  Get Adobe Reader - New Window
Content starts here

Writing Global Transactions

This topic includes the following sections:

 


What Is a Global Transaction?

A global transaction is a mechanism that allows a set of programming tasks, potentially using more than one resource manager and potentially executing on multiple servers, to be treated as one logical unit.

Once a process is in transaction mode, any service requests made to servers may be processed on behalf of the current transaction. The services that are called and join the transaction are referred to as transaction participants. The value returned by a participant may affect the outcome of the transaction.

A global transaction may be composed of several local transactions, each accessing the same resource manager. The resource manager is responsible for performing concurrency control and atomicity of updates. A given local transaction may be either successful or unsuccessful in completing its access; it cannot be partially successful.

A maximum of 16 server groups can participate in a single transaction.

The Oracle Tuxedo system manages a global transaction in conjunction with the participating resource managers and treats it as a specific sequence of operations that is characterized by atomicity, consistency, isolation, and durability. In other words, a global transaction is a logical unit of work in which:

The Oracle Tuxedo system tracks the status of each global transaction and determines whether it should be committed or rolled back.

Note: If a transaction includes calls to tpcall(), tpacall(), or tpconnect() for which the flags parameter is explicitly set to TPNOTRAN, the operations performed by the called service do not become part of that transaction. In this case, the calling process does not invite the called service to be a participant in the current transaction. As a result, services performed by the called process are not affected by the outcome of the current transaction. If TPNOTRAN is set for a call that is directed to a service in an XA-compliant server group, the call may be executed outside of transaction mode or in a separate transaction, depending on how the service is configured and coded. For more information, refer to Implicitly Defining a Global Transaction.

 


Starting the Transaction

To start a global transaction, use the tpbegin(3c) function with the following signature:

int
tpbegin(unsigned long timeout, long flags)

Table 9-1 describes the arguments to the tpbegin() function

Table 9-1 tpbegin( ) Function Arguments
Field
Description
timeout
Specifies the amount of time, in seconds, a transaction can execute before timing out. You can set this value to the maximum number of seconds allowed by the system, by specifying a value of 0. In other words, you can set timeout to the maximum value for an unsigned long as defined by the system.
The use of 0 or an unrealistically large value for the timeout parameter delays system detection and reporting of errors. The system uses the timeout parameter to ensure that responses to service requests are sent within a reasonable time, and to terminate transactions that encounter problems such as network failures before executing a commit.
For a transaction in which a person is waiting for a response, you should set this parameter to a small value: if possible, less than 30 seconds.
In a production system, you should set timeout to a value large enough to accommodate expected delays due to system load and database contention. A small multiple of the expected average response time is often an appropriate choice.

Note: The value assigned to the timeout parameter should be consistent with that of the SCANUNIT parameter set by the Oracle Tuxedo application administrator in the configuration file. The SCANUNIT parameter specifies the frequency with which the system checks, or scans, for timed-out transactions and blocked calls in service requests. The value of this parameter represents the interval of time between these periodic scans, referred to as the scanning unit.

You should set the timeout parameter to a value that is greater than the scanning unit. If you set the timeout parameter to a value smaller than the scanning unit, there will be a discrepancy between the time at which a transaction times out and the time at which this timeout is discovered by the system. The default value for SCANUNIT is 10 seconds. You may need to discuss the setting of the timeout parameter with your application administrator to make sure the value you assign to the timeout parameter is compatible with the values assigned to your system parameters.

flags
Currently undefined; must be set to 0.

Any process may call tpbegin() unless the process is already in transaction mode or is waiting for outstanding replies. If tpbegin() is called in transaction mode, the call fails due to a protocol error and tperrno(5) is set to TPEPROTO. If the process is in transaction mode, the transaction is unaffected by the failure.

Listing 9-1 provides a high-level view of how a global transaction is defined.

Listing 9-1 Defining a Global Transaction - High-level View
. . .
if (tpbegin(timeout,flags) == -1)
 error routine
program statements
. . .
if (tpcommit(flags) == -1)
 error routine

Listing 9-2 provides a more detailed view of how to define a transaction. This example is excerpted from audit.c, a client program included in bankapp, the sample banking application delivered with the Oracle Tuxedo system.

Listing 9-2 Defining a Global Transaction - Detailed View
#include <stdio.h>         /* UNIX */
#include <string.h> /* UNIX */
#include <atmi.h> /* BEA Tuxedo System */
#include <Uunix.h> /* BEA Tuxedo System */
#include <userlog.h> /* BEA Tuxedo System */
#include "bank.h" /* BANKING #defines */
#include "aud.h" /* BANKING view defines */

#define INVI 0 /* account inquiry */
#define ACCT 1 /* account inquiry */
#define TELL 2 /* teller inquiry */

static int sum_bal _((char *, char *));
static long sitelist[NSITE] = SITEREP; /* list of machines to audit */
static char pgmname[STATLEN]; /* program name = argv[0] */
static char result_str[STATLEN]; /* string to hold results of query */

main(argc, argv)
int argc;
char *argv[];
{
        int aud_type=INVI; /* audit type -- invalid unless specified */
        int clarg; /* command line arg index from optind */
        int c; /* Option character */
        int cflgs=0; /* Commit flags, currently unused */
        int aflgs=0; /* Abort flags, currently unused */
        int nbl=0; /* count of branch list entries */
        char svc_name[NAMELEN]; /* service name */
        char hdr_type[NAMELEN]; /* heading to appear on output */
        int retc; /* return value of sum_bal() */
        struct aud *audv; /* pointer to audit buf struct */
        int audrl=0; /* audit return length */
        long q_branchid; /* branch_id to query */

. . . /* Get Command Line Options and Set Variables */

/* Join application */

if (tpinit((TPINIT *) NULL) == -1) {
        (void)userlog("%s: failed to join application\n", pgmname);
        exit(1);
}

/* Start global transaction */

if (tpbegin(30, 0) == -1) {
        (void)userlog("%s: failed to begin transaction\n", pgmname);
        (void)tpterm();
        exit(1);
}

if (nbl == 0) { /* no branch id specified so do a global sum */
retc = sum_bal(svc_name, hdr_type); /* sum_bal routine not shown */

} else {

        /* Create buffer and set data pointer */
        
        if ((audv = (struct aud *)tpalloc("VIEW", "aud", sizeof(struct aud)))
                == (struct aud *)NULL) {
                (void)userlog("audit: unable to allocate space for VIEW\n");
                exit(1);
        }

        /* Prepare aud structure */

        audv->b_id = q_branchid;
        audv->balance = 0.0;
        audv->ermsg[0] = '\0';

        /* Do tpcall */

        if (tpcall(svc_name,(char *)audv,sizeof(struct aud),
           (char **)audv,(long *)audrl,0) == -1){
                    (void)fprintf (stderr,"%s service failed\n%s: %s\n",
                    svc_name, svc_name, audv->ermsg);
                    retc = -1;

        }else {

                 (void)sprintf(result_str,"Branch %ld %s balance is $%.2f\n",
                  audv->b_id, hdr_type, audv->balance);
        }
                 tpfree((char *)audv);
}

/* Commit global transaction */

if (retc < 0) /* sum_bal failed so abort */
       (void) tpabort(aflgs);
else {
         if (tpcommit(cflgs) == -1) {
                 (void)userlog("%s: failed to commit transaction\n", pgmname);
                 (void)tpterm();
                 exit(1);
        }
        /*print out results only when transaction has committed successfully*/
        (void)printf("%s",result_str);
}

/* Leave application */

if (tpterm() == -1) {
        (void)userlog("%s: failed to leave application\n", pgmname);
        exit(1);
}

If a transaction times out, a call to tpcommit() causes the transaction to be aborted. As a result, tpcommit() fails and sets tperrno(5) to TPEABORT.

Listing 9-3 shows how to test for a transaction timeout. Note that the value of timeout is set to 30 seconds.

Listing 9-3 Testing for Transaction Timeout
if (tpbegin(30, 0) == -1) {
  (void)userlog("%s: failed to begin transaction\n", argv[0]);
  tpterm();
  exit(1);
}
. . .
communication calls
. . .
if (tperrno == TPETIME){
    if (tpabort(0) == -1) {
      check for errors;
}
else if (tpcommit(0) == -1){
      check for errors;
}
. . .
Note: When a process is in transaction mode and makes a communication call with flags set to TPNOTRAN, it prohibits the called service from becoming a participant in the current transaction. Whether the service request succeeds or fails has no impact on the outcome of the transaction. The transaction can still timeout while waiting for a reply that is due from a service, whether it is part of the transaction or not. Refer to Managing Errors for more information on the effects of the TPNOTRAN flag.

 


Suspending and Resuming a Transaction

At times, it may be desirable to temporarily remove a process from an incomplete transaction and allow it to initiate a different transaction by calling tpbegin() or tpresume(). For example, suppose a server wants to log a request to the database central event log, but does not want the logging activity to be rolled back if the transaction aborts.

The Oracle Tuxedo system provides two functions that allow a client or server to suspend and resume a transaction in such situations: tpsuspend(3c) and tpresume(3c). Using these functions, a process can:

  1. Temporarily suspend the current transaction by calling tpsuspend().
  2. Start a separate transaction. (In the preceding example, the server writes an entry to the event log.)
  3. Commit the transaction started in step 2.
  4. Resume the original transaction by calling tpresume().

Suspending a Transaction

Use the tpsuspend(3c) function to suspend the current transaction. Use the following signature to call the tpsuspend() function:

int
tpsuspend(TPTRANID *t_id,long flags)

Table 9-2 describes the arguments to the tpsuspend() function.

Table 9-2 tpsuspend( ) Function Arguments
Field
Description
*t_id
Pointer to the transaction identifier.
flags
Currently not used. Reserved for future use.

You cannot suspend a transaction with outstanding asynchronous events. When a transaction is suspended, all modifications previously performed are preserved in a pending state until the transaction is committed, aborted, or timed out.

Resuming a Transaction

To resume the current transaction, use the tpresume(3c) function with the following signature.

int
tpresume(TPTRANID *t_id,long flags)

Table 9-3 describes the arguments to the tpresume() function:

Table 9-3 tpresume( ) Function Arguments
Field
Description
*t_id
Pointer to the transaction identifier.
flags
Currently not used. Reserved for future use.

It is possible to resume a transaction from a process other than the one that suspended it, subject to certain restrictions. For a list of these restrictions, refer to tpsuspend(3c) and tpresume(3c) in the Oracle Tuxedo ATMI C Function Reference.

Example: Suspending and Resuming a Transaction

Listing 9-4 shows how to suspend one transaction, start and commit a second transaction, and resume the initial transaction. For the sake of simplicity, error checking code has been omitted.

Listing 9-4 Suspending and Resuming a Transaction
DEBIT(SVCINFO *s)
{
       TPTRANID t;
       tpsuspend(&t,TPNOFLAGS); /* suspend invoking transaction*/

       tpbegin(30,TPNOFLAGS); /* begin separate transaction */
       Perform work in the separate transaction.
       tpcommit(TPNOFLAGS); /* commit separate transaction */

       tpresume(&t,TPNOFLAGS); /* resume invoking transaction*/

       .
       .
       .
       tpreturn(. . . );
}

 


Terminating the Transaction

To end a global transaction, call tpcommit(3c) to commit the current transaction, or tpabort(3c) to abort the transaction and roll back all operations.

Note: If tpcall(), tpacall(), or tpconnect() is called by a process that has explicitly set the flags argument to TPNOTRAN, the operations performed by the called service do not become part of the current transaction. In other words, when you call the tpabort() function, the operations performed by these services are not rolled back.

Committing the Current Transaction

The tpcommit(3c) function commits the current transaction. When tpcommit() returns successfully, all changes to resources as a result of the current transaction become permanent.

Use the following signature to call the tpcommit() function:

int
tpcommit(long flags)

Although the flags argument is not used currently, you must set it to zero to ensure compatibility with future releases.

Prerequisites for a Transaction Commit

For tpcommit() to succeed, the following conditions must be true:

If the first condition is false, the call fails and tperrno(5) is set to TPEPROTO, indicating a protocol error. If the second or third condition is false, the call fails and tperrno() is set to TPEABORT, indicating that the transaction has been rolled back. If tpcommit() is called by the initiator with outstanding transaction replies, the transaction is aborted and those reply descriptors associated with the transaction become invalid. If a participant calls tpcommit() or tpabort(), the transaction is unaffected.

A transaction is placed in a rollback-only state if any service call returns TPFAIL or indicates a service error. If tpcommit() is called for a rollback-only transaction, the function cancels the transaction, returns -1, and sets tperrno(5) to TPEABORT. The results are the same if tpcommit() is called for a transaction that has already timed out: tpcommit() returns -1 and sets tperrno() to TPEABORT. Refer to Managing Errors for more information on transaction errors.

Two-phase Commit Protocol

When the tpcommit() function is called, it initiates the two-phase commit protocol. This protocol, as the name suggests, consists of two steps:

  1. Each participating resource manager indicates a readiness to commit.
  2. The initiator of the transaction gives permission to commit to each participating resource manager.

The commit sequence begins when the transaction initiator calls the tpcommit() function. The Oracle Tuxedo TMS server process in the designated coordinator group contacts the TMS in each participant group that is to perform the first phase of the commit protocol. The TMS in each group then instructs the resource manager (RM) in that group to commit using the XA protocol that is defined for communications between the Transaction Managers and RMs. The RM writes, to stable storage, the states of the transaction before and after the commit sequence, and indicates success or failure to the TMS. The TMS then passes the response back to the coordinating TMS.

When the coordinating TMS has received a success indication from all groups, it logs a statement to the effect that a transaction is being committed and sends second-phase commit notifications to all participant groups. The RM in each group then finalizes the transaction updates.

If the coordinator TMS is notified of a first-phase commit failure from any group, or if it fails to receive a reply from any group, it sends a rollback notification to each RM and the RMs back out all transaction updates. tpcommit() then fails and sets tperrno(5) to TPEABORT.

Selecting Criteria for a Successful Commit

When more than one group is involved in a transaction, you can specify which of two criteria must be met for tpcommit() to return successfully:

To specify one of these prerequisites, set the CMTRET parameter in the RESOURCES section of the configuration file to one of the following values:

By default, CMTRET is set to COMPLETE.

If you later want to override the setting in the configuration file, you can do so by calling the tpscmt() function with its flags argument set to either TP_CMT_LOGGED or TP_CMT_COMPLETE.

Trade-offs Between Possible Commit Criteria

In most cases, when all participants in a global transaction have logged successful completion of phase 1, they do not fail to complete phase 2. By setting CMTRET to LOGGED, you allow a slightly faster return of calls to tpcommit(), but you run the slight risk that a participant may heuristically complete its part of the transaction in a way that is not consistent with the commit decision.

Whether it is prudent to accept the risk depends to a large extent on the nature of your application. If your application demands complete accuracy (for example, if you are running a financial application), you should probably wait until all participants fully complete the two-phase commit process before returning. If your application is more time-sensitive, you may prefer to have the application execute faster at the expense of accuracy.

Aborting the Current Transaction

Use the tpabort(3c) function to indicate an abnormal condition and explicitly abort a transaction. This function invalidates the call descriptors of any outstanding transactional replies. None of the changes produced by the transaction are applied to the resource. Use the following signature to call the tpabort() function:

int
tpabort(long flags)

Although the flags argument is not used currently, you must set it to zero to ensure compatibility with future releases.

Example: Committing a Transaction in Conversational Mode

Figure 9-1 illustrates a conversational connection hierarchy that includes a global transaction.

Figure 9-1 Connection Hierarchy in Transaction Mode

Connection Hierarchy in Transaction Mode

The connection hierarchy is created through the following process:

  1. A client (process A) initiates a connection in transaction mode by calling tpbegin() and tpconnect().
  2. The client calls subsidiary services, which are executed.
  3. As each subordinate service completes, it sends a reply indicating success or failure (TPEV_SVCSUCC or TPEV_SVCFAIL, respectively) back up through the hierarchy to the process that initiated the transaction. In this example the process that initiated the transaction is the client (process A). When a subordinate service has completed sending replies (that is, when no more replies are outstanding), it must call tpreturn().
  4. The client (process A) determines whether all subordinate services have returned successfully.
    • If so, the client commits the changes made by those services, by calling tpcommit(), and completes the transaction.
    • If not, the client calls tpabort(), since it knows that tpcommit() could not be successful.

Example: Testing for Participant Errors

In Listing 9-5, a client makes a synchronous call to the fictitious REPORT service (line 18). Then the code checks for participant failures by testing for errors that can be returned on a communication call (lines 19-34).

Listing 9-5 Testing for Participant Success or Failure
001    #include <stdio.h>
002 #include "atmi.h"
003
004 main()
005 {
006 char *sbuf, *rbuf;
007 long slen, rlen;
008 if (tpinit((TPINIT *) NULL) == -1)
009 error message, exit program;
010 if (tpbegin(30, 0) == -1)
011 error message, tpterm, exit program;
012 if ((sbuf=tpalloc("STRING", NULL, 100)) == NULL)
013 error message, tpabort, tpterm, exit program;
014 if ((rbuf=tpalloc("STRING", NULL, 2000)) == NULL)
015 error message, tpfree sbuf, tpabort, tpterm, exit program;
016 (void)strcpy(sbuf, "REPORT=accrcv DBNAME=accounts");
017 slen=strlen(sbuf);
018 if (tpcall("REPORT", sbuf, slen, &rbuf, &rlen, 0) == -1) {
019 switch(tperrno) {
020 case TPESVCERR:
021 fprintf(stderr,
022 "REPORT service's tpreturn encountered problems\n");
023 break;
024 case TPESVCFAIL:
025 fprintf(stderr,
026 "REPORT service TPFAILED with return code of %d\n", tpurcode);
027 break;
028 case TPEOTYPE:
029 fprintf(stderr,
030 "REPORT service's reply is not of any known data type\n");
031 break;
032 default:
033 fprintf(stderr,
034 "REPORT service failed with error %d\n", tperrno);
035 break;
036 }
037 if (tpabort(0) == -1){
038 check for errors;
039 }
040 }
041 else
042 if (tpcommit(0) == -1)
043 fprintf(stderr, "Transaction failed at commit time\n");
044 tpfree(rbuf);
045 tpfree(sbuf);
046 tpterm();
047 exit(0);
048 }

 


Implicitly Defining a Global Transaction

An application can start a global transaction in either of two ways:

This section describes the second method.

Implicitly Defining a Transaction in a Service Routine

You can implicitly place a service routine in transaction mode by setting the system parameter AUTOTRAN in the configuration file. If you set AUTOTRAN to Y, the system automatically starts a transaction in the service subroutine when a request is received from another process.

When implicitly defining a transaction, observe the following rules:

Note: Because a service can be placed in transaction mode automatically, it is possible for a service with the TPNOTRAN flag set to call services that have the AUTOTRAN parameter set. If such a service requests another service, the flags member of the service information structure returns TPTRAN when queried. For example, if the call is made with the communication flags member set to TPNOTRAN | TPNOREPLY, and the service automatically starts a transaction when called, the flags member of the information structure is set to TPTRAN | TPNOREPLY.

 


Defining Global Transactions for an XA-Compliant Server Group

Generally, the application programmer writes a service that is part of an XA-compliant server group to perform some operation via the group’s resource manager. In the normal case, the service expects to perform all operations within a transaction. If, on the other hand, the service is called with the communication flags set to TPNOTRAN, you may receive unexpected results when executing database operations.

In order to avoid unexpected behavior, design the application so that services in groups associated with XA-compliant resource managers are always called in transaction mode or are always defined in the configuration file with AUTOTRAN set to Y. You should also test the transaction level in the service code early.

 


Testing Whether a Transaction Has Started

When a process in transaction mode requests a service from another process, the latter process becomes part of the transaction, unless specifically instructed not to join it.

It is important to know whether or not a process is in transaction mode in order to avoid and interpret certain error conditions. For example, it is an error for a process already in transaction mode to call tpbegin(). When tpbegin() is called by such a process, it fails and sets tperrno(5) to TPEPROTO to indicate that it was invoked while the caller was already participating in a transaction. The transaction is not affected.

You can design a service subroutine so that it tests whether it is in transaction mode before invoking tpbegin(). You can test the transaction level by either of the following methods:

Use the following signature to call the tpgetlev() function:

int
tpgetlev() /* Get current transaction level */

The tpgetlev() function requires no arguments. It returns 0 if the caller is not in a transaction, and 1 if it is.

Listing 9-6 is a variation of the OPEN_ACCT service that shows how to test for transaction level using the tpgetlev() function (line 12). If the process is not already in transaction mode, the application starts a transaction (line 14). If tpbegin() fails, a message is returned to the status line (line 16) and the rcode argument of tpreturn() is set to a code that can be retrieved in the global variable tpurcode(5) (lines 1 and 17).

Listing 9-6 Testing Transaction Level
001 #define BEGFAIL    3    /* tpurcode setting for return if tpbegin fails */

002 void
003 OPEN_ACCT(transb)

004 TPSVCINFO *transb;

005 {
... other declarations ...
006 FBFR *transf; /* fielded buffer of decoded message */
007 int dotran; /* checks whether service tpbegin/tpcommit/tpaborts */

008 /* set pointer to TPSVCINFO data buffer */

009 transf = (FBFR *)transb->data;

010 /* Test if transaction exists; initiate if no, check if yes */

011 dotran = 0;
012 if (tpgetlev() == 0) {
013 dotran = 1;
014 if (tpbegin(30, 0) == -1) {
015 Fchg(transf, STATLIN, 0,
016 "Attempt to tpbegin within service routine failed\n");
017 tpreturn(TPFAIL, BEGFAIL, transb->data, 0, 0);
018 }
019 }
. . .

If the AUTOTRAN parameter is set to Y, you do not need to call the tpbegin(), and tpcommit() or tpabort() transaction functions explicitly. As a result, you can avoid the overhead of testing for transaction level. In addition, you can set the TRANTIME parameter to specify the time-out interval: the amount of time that may elapse after a transaction for a service begins, and before it is rolled back if not completed.

For example, suppose you are revising the OPEN_ACCT service shown in the preceding code listing. Currently, OPEN_ACCT defines the transaction explicitly and then tests for its existence (see lines 7 and 10-19). To reduce the overhead introduced by these tasks, you can eliminate them from the code. Therefore, you need to require that whenever OPEN_ACCT is called, it is called in transaction mode. To specify this requirement, enable the AUTOTRAN and TRANTIME system parameters in the configuration file.

See Also


  Back to Top       Previous  Next