# Compaq ACMS for OpenVMS
Systems Interface Programming

Previous | Contents | Index

## 2.2.4 MACRO

The MACRO library SYS$LIBRARY:ACMSMAC.MLB is supplied for MACRO programmers. See the MACRO documentation for information on using macro libraries. SYS$LIBRARY:ACMSMAC.MLB contains the following interface definition macros for agent programs:

- ACMS$SUBMITTER
- ACMS$STREAM

Use these macros to include the appropriate definitions for the services used. These macros take one parameter, either <=> or <==>. The parameter determines whether the constant definitions are made locally or globally.

Each of these macros:

- Contains the size of the IDs
- Defines constants other than return status values

## 2.2.5 Pascal

The source file SYS$LIBRARY:ACMSPAS.PAS is supplied for Pascal programmers. The system manager must process this file into a PASCAL ENVIRONMENT file. See the Pascal documentation for information on creating and using environment definitions.

The source file:

- Contains routine definitions for all the services.
- Contains record definitions for all IDs. Use these IDs in declarations. The name of the record is the same as the name given in the structure list in Section 2.2.
- Defines constants other than return status values.

Note If you use the nonpositional syntax form of parameter association, you must use PROCEDURE_ rather than PROCEDURE as the formal parameter name for ACMS$GET_PROCEDURE_INFO, because PROCEDURE is a reserved word in Pascal. See VAX Pascal Reference Manual for more information.

## 2.2.6 PL/I

The text library SYS$LIBRARY:ACMSPLI.TLB is supplied for PL/I programmers. See PL/I documentation for information on using text library files in programs.

This text library provides the following modules for agent programs:

- ACMS$SUBMITTER
- ACMS$STREAM

These modules:

- Contain routine definitions for all the services.
- Contain structure definitions for all IDs as BASED variables. Use these IDs in declarations. The name of the structure is the same as the name given in the structure list in Section 2.2.
- Define constants other than return status values. These constants are defined by %REPLACE preprocessor definitions.

## 2.2.7 Other Languages

Programmers in other languages must define:

- Services as external functions that return longword integers.
- Constants other than return status values as external longword integer literals. These values are resolved at compile or link time.
- Return status values as external longword integer literals. These values are resolved at link time.

# Chapter 3
Agent Programs that Coordinate Distributed Transactions

A distributed transaction is the grouping of operations on multiple recoverable resources (such as files and databases) into a single recovery unit or logical database transaction. Distributed transactions can include more than one type of resource manager and have the properties of atomicity, isolation, and durability.

An agent program, which can run either under the control of ACMS or external to ACMS, can call tasks that are executed as part of a distributed transaction. For example, an agent program can access a database locally and then call an ACMS task in an application on a remote node. The task can call a step procedure that accesses a second database locally on the remote node. You can coordinate both database accesses as part of the same distributed transaction. See Section 3.3.

An agent program uses a set of OpenVMS system services to start and end distributed transactions. Table 3-1 contains these services.

Table 3-1 System Services Used in Distributed Transactions
System Service | Use to
--- | ---
$START_TRANS | Start transaction
$START_TRANSW | Start transaction and wait
$END_TRANS | End transaction
$END_TRANSW | End transaction and wait
$ABORT_TRANS | Roll back transaction
$ABORT_TRANSW | Roll back transaction and wait

Note The optional TRANSW (wait) system services complete synchronously; that is, they return to the caller after the request has actually completed.

For more information on DECdtm services, refer to documentation for OpenVMS Version 5.4 or higher.

For a task to execute as part of a distributed transaction started in an agent program, the agent program and the application containing the task must conform to a number of composability rules. Lists of these rules follow.

- For a task, the composability rules are the following:
  - The task's root block step or root processing step must be a distributed transaction action.
  - The action part of the root step must not specify an explicit distributed transaction step.
  - The sequencing action in the action part of the root step must be EXIT TASK, CANCEL TASK, or RAISE EXCEPTION.
  You can use the ADU command DUMP GROUP to dump a task group to determine whether or not a task is composable.
- For an application, the composability rules are the following:
  - The application must run under ACMS Version 3.2 or higher.
  - The application must have been rebuilt using ACMS Version 3.2 or higher.
- For an agent program, the composability rules are the following:
  - The agent program must use SI services for ACMS Version 3.2 or higher.
  - Before it starts a task, the agent program must call the $START_TRANS system service to start a distributed transaction and to obtain a transaction ID (TID).
  - The agent program must call the task, passing the TID, by using either of the following:
    - ACMS$START_CALL and ACMS$WAIT_FOR_CALL_END
    - ACMS$CALL
    The task that is called joins the distributed transaction established by the $START_TRANS service if the task is composable. If the task that is called is not composable, it is cancelled.
  - When the task completes, the agent program can either end or roll back the distributed transaction by using one of the following:
    - $END_TRANS, to end the transaction
    - $ABORT_TRANS, to roll back the transaction
    The choice between $END_TRANS and $ABORT_TRANS depends on the final completion status of the task. However, a call to the $END_TRANS service can fail with an error status if the distributed transaction is rolled back between the end of the task and the time the agent program calls the $END_TRANS service. A transaction can roll back in this manner for a number of reasons; for example, the network communication between two or more participants in the distributed transaction could fail, or the ACMS/CANCEL TASK command could be used to cancel the task while it is still in the pending end-of-transaction state.

# 3.1 Starting a Distributed Transaction

When an agent program calls a task, it is important for the agent program to control the task's participation in an active distributed transaction. To do this, the agent program must use the TID argument in the ACMS$START_CALL and ACMS$CALL services in either one of two ways:

- For a task to participate in an active distributed transaction, the agent program can either pass the TID returned by the $START_TRANS service or omit the TID argument. By default, if you do not pass the TID and there is a default distributed transaction in the agent program, the task joins the distributed transaction.
- For a task not to participate in an active distributed transaction, the agent program must explicitly pass a TID consisting of zeros. If the agent process has done a $START_TRANS in the process and the agent program does not want this task to join the default distributed transaction, this is the only way to ensure that the task will not participate in an active distributed transaction.

The following services accept a TID:

> ACMS$CALL
> ACMS$CALL_A
> ACMS$START_CALL
> ACMS$START_CALL_A

The calling sequences for these services are listed in Chapter 5.

When an agent program passes a TID to one of these services, ACMS attempts to pass the TID on to the task. If an agent program tries to pass a TID to a task that cannot join a distributed transaction, either the ACMS$WAIT_FOR_CALL_END or the ACMS$CALL service returns ACMS$_TASKNOTCOMP, indicating that the task is not composable.

If ACMS attempts to pass a TID to a task in an application that does not follow composability rules, one of the following errors is returned:

- ACMS$_NOTRANSNODE - ACMS does not support transactions on this application node.
  This error is returned when the application is running on a node that has ACMS Version 3.1 or earlier installed.
- ACMS$_NOTRANSADB - Transactions are not supported in the ACMS application database (ADB).
  This error is returned when the application database has not been rebuilt with ACMS Version 3.2 or higher.

Example 3-1 illustrates the logic of an agent program that processes records from a data file and calls tasks as a single distributed transaction.

Example 3-1 A Specialized User-Written Agent

    status = $OPEN                       ! Open a data file
    status = $CONNECT                    ! Connect a record stream
    status = $OPEN                       ! Open an error file
    status = $CONNECT                    ! Connect a record stream
    UNTIL <no more records to process>
        BEGIN
        trx_aborted = FALSE              ! Assume everything will work
        status = $START_TRANSW           ! Start a transaction
        status = $GET                    ! Read a record from the file
        status = $ACMS$CALL              ! Call a task
        IF .status                       ! If task completed OK,
        THEN                             ! then...
            BEGIN
            status = $DELETE             !   Delete the record
            status = $END_TRANSW         !   and end the transaction
            IF NOT .status               !   If transaction rolled back,
            THEN                         !   then...
                txn_aborted = TRUE       !     Set the flag so we know
            END
        IF NOT .status                   ! If something went wrong (task
        THEN                             ! failed or txn rolled back,
            BEGIN                        ! then...
            IF NOT .txn_aborted          !   If txn hasn't rolled back,
            THEN                         !   then...
                $ABORT_TRANS             !     Roll back transaction
            $START_TRANSW                !   Start a new transaction
            $GET                         !   Reread record from file
            $PUT                         !   Store record in error file
            $DELETE                      !   Delete rec. from data file
            $END_TRANSW                  !   And end the transaction
            END
    $DISCONNECT                          ! Disconnect both
    $DISCONNECT                          ! record streams
    $CLOSE                               ! Close both the data
    $CLOSE                               ! and error files

# 3.2 Rolling Back a Distributed Transaction

An agent program can use the $ABORT_TRANS service to roll back a transaction and, therefore, cancel the associated task. The task is cancelled with the OpenVMS status DDTM$_ABORTED.

If you require a more meaningful reason for the cancellation, you can use the ACMS$CANCEL_CALL service to cancel the task. If the task is still active, the EXC cancels the task. However, the message sent from the agent program to cancel a task may not reach EXC before the task completes and EXC returns the status to the agent. In this case, the agent program should check the task completion status to determine whether or not to roll back the transaction. The following example illustrates this condition:

       status = ACMS$START_CALL
       < additional processing >
       IF .error_condition_detected
       THEN
           cancel_status = ACMS$CANCEL_CALL
       task_status = ACMS$WAIT_FOR_CALL_END
       IF .error_condition_detected OR NOT .task_status
       THEN
           IF .task_status
           THEN
               cancel_status = $ABORT_TRANS
       < task termination processing >

An agent program that starts a distributed transaction can roll back the transaction by a call to $ABORT_TRANS at any of the following times:

- Before calling a task
  Because a task has not been called, the rollback affects only database interactions that the agent program has performed.
- While a task is active
  If a task is active when the agent program rolls back the transaction, then the task and any subordinate tasks are cancelled with an OpenVMS status of DDTM$_ABORTED. If you need a more meaningful reason for the cancellation, use the ACMS$CANCEL_CALL service to cancel the task.
  If, however, the task was in the process of completing at the time of the cancel, then the agent process may still need to use the $ABORT_TRANS service if the task subsequently completes with a success status, that is, if the request to cancel the task arrives at the EXC process after the EXC has completed the task and returned the results to the agent.
- After the task completes, but before calling $END_TRANS
  If a transaction is rolled back after a called task completes (that is, the task instance called by the agent program is in a pending state awaiting the end of the transaction), then any database operations that were performed by the task and the agent program as part of the transaction are undone.
- After the task completes, and after calling $END_TRANS
  If the transaction has already reached the point of committing, then the DECdtm services refuse to roll back the transaction. If the transaction has not yet reached the point of committing, then the transaction is rolled back.

# 3.3 Accessing Remote Data

Compaq ACMS for OpenVMS Concepts and Design Guidelines discusses ways in which you can access remote data in a distributed transaction. Two methods for an ACMS application on one node (A) to access data on another node (B) are the following:

- Invoke an ACMS remote task on Node B from a step procedure on Node A.
  With this method, you use a step procedure as an agent program to call a task on Node B. Figure 3-1 illustrates this method.
- Invoke a database server (Rdb, for example) on Node B from a step procedure on Node A.
  With this method, each instance of a procedure server on Node A maps to one specific instance of a database server process on Node B and any additional systems that Node A needs to access.

Briefly stated, the advantages of the first method over the second are that it minimizes the following:

- Number of server processes created
- Possibility of lock contention on the remote node
- Network traffic

When you use the first method, ACMS remote access, a step procedure on Node A acts as an agent program by using the SI system service call ACMS$CALL to invoke a task in an application on Node B. ACMS$CALL passes the TID to the application on Node B and forces the called task on Node B to join the distributed transaction that starts in the calling task on Node A. The called task on Node B executes and performs database access by calling appropriate processing step procedures locally.

Figure 3-1 illustrates the use of a step procedure as an agent program to call a task in an application on a remote node.

Figure 3-1 Using a Step Procedure as an Agent Program

Example 3-2 contains the task definition that calls the procedure BANKING_SAMPLE_TXN. The processing step in the task definition starts a distributed transaction.

Example 3-2 Task Definition that Calls a Procedure Used as an Agent

    REPLACE TASK BANKING_SAMPLE_TSK /DIAGNOSTIC
    DEFAULT FORM IS BANKING_SAMPLE_FORM;
    WORKSPACE IS BANKING_SAMPLE_WORKSPACE WITH TYPE TASK;
         BLOCK WORK WITH FORM I/O

            INPUT_REQUEST:
             EXCHANGE
       TRANSCEIVE RECORD BANKING_SAMPLE_REC, BANKING_SAMPLE_REC
         IN BANKING_SAMPLE_FORM
              SENDING BANKING_SAMPLE_WORKSPACE
              RECEIVING BANKING_SAMPLE_WORKSPACE;

            BANKING_SAMPLE_PROCESSING:

            PROCESSING WITH DISTRIBUTED TRANSACTION
              CALL BANKING_SAMPLE_TXN IN BANKING_SAMPLE_SERVER
              USING BANKING_SAMPLE_WORKSPACE;

    !+
    !  Check the return status in the field ACMS$L_STATUS.
    !  This field contains 1 is success, -913 if deadlock.
    !  If deadlock, try again.  Otherwise, cancel task.
    !-

            ACTION IS
                SELECT FIRST TRUE OF
                (ACMS$L_STATUS = 1):    COMMIT TRANSACTION;
                (ACMS$L_STATUS = -913): ROLLBACK TRANSACTION;
                                        GOTO STEP BANKING_SAMPLE_PROCESSING;
                NOMATCH:                ROLLBACK TRANSACTION;
                                        CANCEL TASK;
                END SELECT;

            EXCEPTION ACTION IS
                CANCEL TASK;

            OUTPUT_REQUEST:

             EXCHANGE
              TRANSCEIVE RECORD BANKING_SAMPLE_REC, BANKING_SAMPLE_REC
                IN BANKING_SAMPLE_FORM
              SENDING BANKING_SAMPLE_WORKSPACE
              RECEIVING BANKING_SAMPLE_WORKSPACE;

    !+
    !  Examine the control field. If the request returns Y,
    !  then leave the task; else, repeat the task.
    !-

            ACTION IS
                IF (BANKING_SAMPLE_WORKSPACE.WORKSPACE_EXIT_SWITCH = "Y") THEN
                    EXIT TASK;
                ELSE
                    GOTO STEP BANKING_SAMPLE_PROCESSING;
                END IF;

        END BLOCK WORK;

    END DEFINITION;

The agent program BANKING_SAMPLE_TXN, shown in Example 3-4, performs local updates of branch and teller data. It then invokes a remote task to update an account on a remote node. The remote task, BANKING_SAMPLE_ACTUPD_TSK, joins in the distributed transaction by declaring WITH DISTRIBUTED TRANSACTION on a block step in the task definition. The remote task calls a procedure on the remote node, BANKING_SAMPLE_ACTUPD_TXN, to update the database locally on the remote node.

Example 3-3 contains the task definition in the application on the remote node.

Example 3-3 Task Definition that Calls a Procedure to Update Remote Data

    REPLACE TASK BANKING_SAMPLE_ACTUPD_TSK /DIAGNOSTIC
    WORKSPACE IS BANKING_SAMPLE_WORKSPACE WITH TYPE TASK;
    TASK ARGUMENT IS BANKING_SAMPLE_WORKSPACE WITH ACCESS MODIFY;
        BLOCK WORK WITH DISTRIBUTED TRANSACTION NO I/O

            BANKING_SAMPLE_PROCESSING:

            PROCESSING
                CALL BANKING_SAMPLE_ACTUPD_TXN IN BANKING_SAMPLE_ACTUPD_SERVER
                USING BANKING_SAMPLE_WORKSPACE;

        END BLOCK WORK;

        ACTION IS
            IF (ACMS$L_STATUS <> 1) THEN
                CANCEL TASK RETURNING ACMS$L_STATUS;
            END IF;

    END DEFINITION;

# 3.4 Step Procedure in C that Acts as an Agent Program

Following is the flow of events in the agent program shown in Example 3-4. The numbers coincide with those in the sample program.

1. Declare tid_structure used to retrieve TID from ACMS.
2. Set up SQL context structure for local updates.
3. Set up interface to SI services.
4. Pull in workspace definitions from CDD.
5. Retrieve TID from ACMS.
6. Call initialization routine in preparation for call to remote task.
7. Call remote task passing submitter ID, task information, workspace, and TID.
8. Check return status of call to remote task; log error message if failure.
9. Check for SQL errors.

   Note Steps 10 through 13 are actually substeps within step 6.

10. Specify task name and application name; note that the application is running on a remote node.
11. Sign in to ACMS.
12. Prepare and get remote procedure information.
13. Pass workspace from agent program to remote task.

Example 3-4 Step Procedure in C that Acts as an Agent Program

    /*************************************************************************/
    /*                                                                       */
    /*                        Sample Banking Application                     */
    /*                     Branch/Teller Update Transaction                  */
    /*                     --------------------------------                  */
    /*                                                                       */
    /*  This is the server procedure called by the BANKING_SAMPLE_TSK task   */
    /*  to perform local update of branch and teller data and invoke the     */
    /*  remote task, BANKING_SAMPLE_ACTUPD_TASK, for update of an account    */
    /*  on a remote node.                                                    */
    /*************************************************************************/

    #include ssdef
    #include stdio
    #include descrip
    #include string
    #include ACMS$SUBMITTER

    /* DECdtm transaction ID structure */
    struct tid_structure    (1)
        {
        long int  field_1;
        long int  field_2;
        short int field_3;
        short int field_4;
        long int  field_5;
        } tid;

    /* SQL Transaction context structure */
    struct context_structure    (2)
        {
        long int version;
        long int type;
        long int length;
        struct tid_structure in_tid;
        long int end;
        };

    char txn_time[9],txn_date[7];
    $DESCRIPTOR(date_time_dscrptr,"dd-mmm-yyyy hh:mm:ss.hh");

    /* Data Structure for ACMS/SI */    (3)
    struct ACMS$SUBMITTER_ID submitter_id;
    struct ACMS$PROCEDURE_ID procedure_id;
    struct dsc$descriptor wksp_dscrptr;
    struct dsc$descriptor tsk_dscrptr;
    struct dsc$descriptor appl_dscrptr;
    char appl_name[32];

    struct single_item_list_structure {
        short int proc_bufsize;
        short int proc_itmcode;
        char     *proc_bufaddr;
        char     *proc_retlen;
        int       item_last;
    } single_item_list;

    struct arg_list_struct {
        long int count;
        char *sel_str;
        char *ext_sts;
        char *tsk_io;
        char *ws_1;
    } arg_list;

    /* SQL and Database Declaration */
    EXEC SQL INCLUDE SQLCA;
    EXEC SQL DECLARE SCHEMA FOR FILENAME BANK_SAMPLE;
    EXEC SQL INCLUDE FROM DICTIONARY BANKING_SAMPLE_WORKSPACE;    (4)

    /*********************************************************/
    /*               Banking Sample Transaction.             */
    /*********************************************************/
    banking_sample_txn(workspace_area)
    struct banking_workspace *workspace_area;
    {
        struct banking_workspace WSBuff;
        char ActBranchBuff[5],ActAccountBuff[7];
        static struct context_structure context={1,1,16,{0,0,0,0,0},0};
        char ErrMsgText[300];
        int  ErrMsgLength;
        int  status;
        $DESCRIPTOR(ErrMsgBuffDsc,"");

        /* Copy some data values from workspace to local buffers due */
        /* to the lack of pointer support in Embedded SQL for C.     */

        WSBuff.workspace_branch = workspace_area->workspace_branch;
        WSBuff.workspace_teller = workspace_area->workspace_teller;
        strncpy(ActBranchBuff,
         workspace_area->workspace_account.workspace_acc_branch,
                4);
        strncpy(ActAccountBuff,
                workspace_area->workspace_account.workspace_acc_account,
                6);
        WSBuff.workspace_delta  = workspace_area->workspace_delta;

        /* Get TID from ACMS and store into SQL context structure */
        status = ACMS$GET_TID(&tid);     (5)
        if (!(status & SS$_NORMAL)) return(status);
        context.in_tid = tid;

        EXEC SQL WHENEVER SQLERROR GOTO sql_error_check;

        /* Initialization in preparation for ACMS RPC call. */
        status = InitializeACMSRPC();     (6)
        if (!(status & SS$_NORMAL))
          return(status);

    transaction_restart:
        /***********************************************/
        /*                Modify Branch.               */
        /***********************************************/

        /* Perform update on the MONEY field for
           appropriate branch in BRANCH relation. */
        EXEC SQL USING CONTEXT :context
          UPDATE BRANCH B
            SET B.BR_MONEY_FIELD =
              B.BR_MONEY_FIELD + :WSBuff.workspace_delta
            WHERE B.BR_BRANCH = :WSBuff.workspace_branch;

        /***********************************************/
        /*                Modify Teller.               */
        /***********************************************/

        /* Perform update on MONEY field for appropriate
           teller in the TELLER relation.                */
        EXEC SQL USING CONTEXT :context
          UPDATE TELLER T
            SET T.TEL_MONEY_FIELD =
              T.TEL_MONEY_FIELD + :WSBuff.workspace_delta
            WHERE (T.TEL_BRANCH = :WSBuff.workspace_branch)
              AND (T.TEL_TELLER = :WSBuff.workspace_teller);

        /**********************************************/
        /*  Invoke remote task to update Account and  */     (7)
        /*  store History records via ACMS$CALL.      */
        /**********************************************/
        wksp_dscrptr.dsc$a_pointer = workspace_area;
        wksp_dscrptr.dsc$w_length  = sizeof(*workspace_area);
        status = ACMS$CALL (&submitter_id,
                            &procedure_id,
                            &arg_list,
                            &tid);

        /* Check return status code - if failure, log error msg */    (8)

        if (!(status & SS$_NORMAL)) {
          error_logging("ACMS$CALL error.",
                         status,
                         WSBuff.workspace_branch,
                         WSBuff.workspace_teller,
                         ActBranchBuff,
                         ActAccountBuff,
                         WSBuff.workspace_delta);
          return(status);
          }

        /* End of Transaction. */
        workspace_area->workspace_delta = 0;
        status = SS$_NORMAL;
        return(status);

        /* Log error message if SQL error. */     (9)
        sql_error_check:

          ErrMsgBuffDsc.dsc$a_pointer = ErrMsgText;
          SQL$GET_ERROR_TEXT(&ErrMsgBuffDsc,&ErrMsgLength);
          ErrMsgText[ErrMsgLength] = '\0';
          error_logging(ErrMsgText,
                     SQLCA.SQLCODE,
                     WSBuff.workspace_branch,
                     WSBuff.workspace_teller,
                     ActBranchBuff,
                     ActAccountBuff,
                     WSBuff.workspace_delta);

          return(SQLCA.SQLCODE);
    }

    InitializeACMSRPC()
    {
        int status;

        /* initialize ACMS RPC data structure */
        tsk_dscrptr.dsc$a_pointer = "BANKING_SAMPLE_ACTUPD_TSK";    (10)
        tsk_dscrptr.dsc$w_length  = 25;

        strcpy(&appl_name[0],"SLVSTR::BANK_SAMPLE_APP");
        appl_dscrptr.dsc$a_pointer = &appl_name[0];
        appl_dscrptr.dsc$w_length  = strlen(&appl_name[0]);

        /* Sign-in to ACMS as a task submitter. */
        status = ACMS$SIGN_IN(&submitter_id,0,0,0,0);     (11)
        if (!(status & SS$_NORMAL)) {
          error_logging("ACMS sign-in error.",status,0,0," "," ",0);
          return(status);
          }

        /* Prepare ACMS$GET_PROCEDURE_INFO item list for remote task */    (12)
        single_item_list.proc_bufsize = ACMS$S_PROCEDURE_ID;
        single_item_list.proc_itmcode = ACMS$K_PROC_PROCEDURE_ID;
        single_item_list.proc_retlen  = 0;
        single_item_list.item_last = 0;

        /* Get remote procedure information */
        single_item_list.proc_bufaddr = &procedure_id;
        status = ACMS$GET_PROCEDURE_INFO (&submitter_id,
                                          &tsk_dscrptr,
                                          &appl_dscrptr,
                                          &single_item_list);
        if (!(status & SS$_NORMAL))
          {
           error_logging("ACMS GET_PROCEDURE_INFO error.",status,
                          0,0," "," ",0);
           return(status);
          }

        /* Prepare ACMS$CALL() argument list for remote task */
        arg_list.count   = 4;
        arg_list.sel_str = 0;
        arg_list.ext_sts = 0;
        arg_list.tsk_io  = 0;
        arg_list.ws_1    = &wksp_dscrptr;    (13)

        return(1);
    }

Previous | Next | Contents | Index