Compaq ACMS for OpenVMS
Writing Server Procedures


Previous Contents Index

4.3.5 Handling Errors

You typically write an error handler to process errors returned by DBMS when accessing records in a database. The examples in Section 4.3.3 and Section 4.3.4 illustrate how to handle some of the standard errors, such as record-not-found, that DBMS can return when you read, write, or update a record in a database. In addition, also be aware of the error conditions that can occur when you are using DBMS in distributed transactions.

Some DBMS errors are expected and are handled by resuming normal program execution. For example, DBMS returns an end-of-collection error if a procedure reads past the last record in a set. In this case, the program can resume execution and process the records that have been read. DBMS can also return a number of recoverable errors that the program should check for and handle. For example, if DBMS returns a deadlock error, you might want to roll back the transaction and process the transaction again. Finally, DBMS can return a number of nonrecoverable errors. For example, a disk on which one of the database storage areas resides might fail. In this case, the program cannot continue until the problem has been resolved.

A distributed transaction can abort at any time. If a transaction aborts while a step procedure is executing, DBMS automatically rolls back an active database transaction. However, the step procedure will receive an error the next time it executes a DML statement in a database transaction that was participating in the distributed transaction. Therefore, an error handler for a step procedure should check for and handle the errors that DBMS returns in this situation.

Typically, you want to retry a transaction automatically in the event of a recoverable error condition such as a deadlock, lock-timeout or transaction timeout error. If DBMS detects deadlock or lock-timeout error conditions, it returns an error to your step procedure when you access the database. In contrast, if a distributed transaction times out, the distributed transaction is aborted, and ACMS raises a transaction exception in the task. In this case, DBMS returns an error if the step procedure accesses the file after the transaction has aborted.

There is an easy technique, illustrated in examples in this section, that allows you to simplify an exception handler that handles recoverable transaction exceptions in a task definition. The following list indicates how the error handler in the step procedure handles each type of error returned by DBMS:

The following example illustrates the error handler for the COBOL example in Section 4.3.4. If a record with the same badge number already exists in the database, the procedure returns a failure status. If the employee record set is locked, it raises a transaction exception using ACMS$_TRANSTIMEDOUT as the exception code. If the distributed transaction has aborted, it raises a transaction exception using the DBMS error status as the exception code. If any other error condition occurred, the procedure calls DBM$SIGNAL to signal the error and then raises a nonrecoverable exception.


       . 
       . 
       . 
WORKING-STORAGE SECTION. 
 
01 return_status                PIC S9(5) COMP. 
01 error_cond                   PIC S9(5) COMP. 
01 dist_tid. 
   03 tid_data                  PIC X(16). 
       . 
       . 
       . 
PROCEDURE DIVISION USING emp_wksp_record GIVING return_status. 
 
 
DECLARATIVES. 
dml-failure SECTION. 
    USE FOR DB-EXCEPTION. 
 
010-dbm-failure. 
    CALL "LIB$MATCH_COND" USING 
                BY REFERENCE DB-CONDITION, 
                BY REFERENCE DBM$_DUPNOTALL, 
                BY REFERENCE DBM$_DEADLOCK, 
                BY REFERENCE DBM$_LCKCNFLCT, 
                BY REFERENCE DBM$_TIMEOUT, 
                BY REFERENCE DBM$_PARTDTXNERR, 
                BY REFERENCE DBM$_NOTIP, 
                BY REFERENCE DBM$_DTXNABORTED 
                GIVING error_cond 
 
    EVALUATE error_cond 
        WHEN 1 
            MOVE persmsg_empexists TO return_status 
        WHEN 2 THRU 4 
            CALL "ACMS$RAISE_TRANS_EXCEPTION" USING 
                            BY REFERENCE ACMS$_TRANSTIMEDOUT 
        WHEN 5 THRU 7 
            CALL "ACMS$RAISE_TRANS_EXCEPTION" USING 
                            BY REFERENCE DB-CONDITION 
        WHEN OTHER 
            CALL "DBM$SIGNAL" 
            CALL "ACMS$RAISE_NONREC_EXCEPTION" USING 
                            BY REFERENCE DB-CONDITION 
 
    END-EVALUATE 
    EXIT PROGRAM. 
END DECLARATIVES. 
       . 
       . 
       . 

The following example illustrates the error handler for the BASIC example in Section 4.3.4. If the employee's record has been deleted, the procedure returns a failure status. If the record is locked by another user, it raises a transaction exception using ACMS$_TRANSTIMEDOUT as the exception code. If the distributed transaction has aborted, it raises a transaction exception using the DBMS error status as the exception code. If any other error condition occurred, the procedure uses the EXIT HANDLER statement to resignal the error.


       . 
       . 
       . 
    WHEN ERROR IN 
        employee_record::emp_badge_number = emp_wksp::emp_badge_number 
        # READY CONCURRENT UPDATE FOR TRANSACTION dist_tid 
        # FETCH FIRST WITHIN ALL_EMPLOYEES - 
                      USING emp_badge_number 
 
        IF employee_record::emp_last_update =                           & 
           emp_wksp::emp_last_update                                    & 
        THEN 
            employee_record = emp_wksp 
            sts = SYS$GETTIM( employee_record::emp_last_update BY REF ) 
            IF ( sts AND 1% ) = 0%                                      & 
            THEN 
                CALL LIB$STOP( sts ) 
            END IF 
            # MODIFY employee_record 
            pers_change_employee_proc = persmsg_success 
 
        ELSE 
            pers_change_employee_proc = persmsg_empchanged 
        END IF 
 
    USE 
        SELECT LIB$MATCH_COND( DBM_COND, DBM$_END,                      & 
                                         DBM$_DEADLOCK,                 & 
                                         DBM$_LCKCNFLCT,                & 
                                         DBM$_TIMEOUT,                  & 
                                         DBM$_PARTDTXNERR,              & 
                                         DBM$_NOTIP,                    & 
                                         DBM$_DTXNABORTED ) 
 
            CASE 1          ! DBM$_END 
                pers_change_employee_proc = persmsg_empdeleted 
 
            CASE 2, 3, 4    ! DBM$_DEADLOCK, DBM$_LCKCNFLCT, DBM$_TIMEOUT 
                CALL ACMS$RAISE_TRANS_EXCEPTION( ACMS$_TRANSTIMEDOUT ) 
 
            CASE 5, 6, 7    ! DBM$_PARTDTXNERR, DBM$_NOTIP, DBM$_DTXNABORTED 
                CALL ACMS$RAISE_TRANS_EXCEPTION( VMSSTATUS  ) 
 
                
            CASE ELSE 
                EXIT HANDLER 
        END SELECT 
    END WHEN 
       . 
       . 
       . 

4.3.6 Compiling DBMS Procedures

If you are using COBOL, use the COBOL compiler to compile your procedure. However, if you are using another programming language, such as BASIC, use the DBMS DML precompiler when you compile a procedure containing embedded DML statements. The DML precompiler processes the embedded DML statements in your program, producing an intermediate host language source file, which it then submits to the host language compiler to produce an object module.

The DML precompiler command line includes both precompiler and host language compiler qualifiers. For the precompiler, use the /LANGUAGE qualifier to specify in which host language the source is written; you can, optionally, include other qualifiers. On the command line, include any language compiler qualifiers (such as LIST or DEBUG) that you want in effect when the precompiler submits the preprocessed source file to the language compiler using the /OPTION qualifier. For more information on DML precompiler qualifiers, see the DBMS documentation.

The following command line precompiles a procedure called PERS_CHANGE_EMPLOYEE_PROC:


$ DML/LANGUAGE=BASIC/OPTION="/LIST" PERS_CHANGE_EMPLOYEE_PROC

Note

Do not make changes to the language source module created by the DML precompiler and then use the language compiler directly to compile that source module. This rule applies even if you want to make source changes that do not affect DML statements because the next precompilation of the original embedded DML module overwrites the changes you make to the temporary language source module generated by the precompiler.

Chapter 6 explains how to link procedures that use DML.

4.4 Using RMS

This section describes how to write step procedures that access RMS files. A step procedure that accesses an RMS file on behalf of an ACMS task is similar to any other program that uses RMS to access a file.

The RMS Journaling layered product provides recovery-unit journaling, after-image journaling, and before-image journaling for RMS sequential, relative, and Prologue 3 indexed files. If you have installed the RMS Journaling product, you can use recovery-unit journaling and distributed transactions to coordinate modifications to records in RMS files with modifications to records in Rdb and DBMS databases. If you do not have the RMS Journaling product, modifications to RMS files will not be coordinated with modifications to Rdb and DBMS databases. See RMS Journaling for OpenVMS Manual for more information on RMS journaling.

This section first discusses how to access RMS files that are marked for recovery-unit journaling. The section then illustrates how to read, write, and modify records in an RMS file. Note that there are no special considerations for using RMS files that are marked for after-image journaling or before-image journaling or for using files that are not journaled.

4.4.1 Using Files Marked for RMS Recovery-Unit Journaling

There are no special considerations for using RMS recovery-unit journaling in a distributed transaction started by a task or an agent program. If an RMS file that is marked for recovery-unit journaling is accessed by a step procedure that is participating in a distributed transaction, RMS automatically associates the record stream with the default transaction established by ACMS for the server process. See Chapter 3 for more information on the participation of a step procedure in a distributed transaction.

Note

Processing steps that participate in a distributed transaction must not make calls to the RMS Recovery Unit services ($START_RU, $PREPARE_RU, $COMMIT_RU, $END_RU, and $ABORT_RU). Any attempt to intermix these services with distributed transactions leads to unpredictable results.

In contrast, if you access an RMS file marked for recovery-unit journaling outside a distributed transaction, you must start a transaction in the step procedure. Use the OpenVMS transactions services $START_TRANS, $END_TRANS, and $ABORT_TRANS to start and end a transaction in a step procedure. Note that the OpenVMS transaction services have superseded the RMS Recovery Unit services. See RMS Journaling for OpenVMS Manual for more information on using the OpenVMS transaction services and RMS recovery-unit journaling.

4.4.2 Reading RMS Records

The examples in this section illustrate how to read a record from an RMS file and return the data to the task in a workspace.

Each example reads a record from a file containing employee records using a key from a field in a workspace. If the record exists, the procedure returns a success status to the task. If the record does not exist, the procedure returns a failure status. Because the PERS_FIND_EMPLOYEE_PROC procedure executes in a server that opens the employee file for read-only access, there is no need to use manual locking statements.

Example 4-12 is a step procedure in COBOL that reads an RMS record.

Example 4-12 Step Procedure in COBOL that Reads an RMS Record

IDENTIFICATION DIVISION. 
PROGRAM-ID. pers_find_employee_proc. 
       . 
       . 
       . 
DATA DIVISION. 
 
FILE SECTION. 
FD      emp_file 
        EXTERNAL 
        DATA RECORD IS employee_record 
        RECORD KEY emp_badge_number OF employee_record. 
COPY "pers_cdd.employee_record" FROM DICTIONARY. 
       . 
       . 
       . 
 
LINKAGE SECTION. 
COPY "pers_cdd.employee_record" FROM DICTIONARY 
    REPLACING ==employee_record== BY ==emp_wksp_record==. 
 
PROCEDURE DIVISION USING emp_wksp_record GIVING return_status. 
MAIN SECTION. 
 
 
000-start. 
    MOVE persmsg_success TO return_status. 
    MOVE emp_badge_number OF emp_wksp_record TO 
         emp_badge_number OF employee_record. 
    READ emp_file RECORD INTO emp_wksp_record 
        KEY IS emp_badge_number OF employee_record 
        INVALID KEY 
            MOVE persmsg_empnotfound TO return_status 
            GO TO 999-end 
    END-READ. 
 
       . 
       . 
       . 
999-end. 
    EXIT PROGRAM. 

Example 4-13 is a step procedure in BASIC that reads an RMS record.

Example 4-13 Step Procedure in BASIC that Reads an RMS Record

    FUNCTION LONG pers_find_employee_proc( employee_record emp_wksp ) 
 
    %INCLUDE "pers_files:pers_common_defns" 
    %INCLUDE %FROM %CDD "pers_cdd.employee_record" 
 
 
    MAP ( emp_map ) employee_record emp_rec 
 
    WHEN ERROR IN 
        GET # emp_file,                                         & 
            KEY # 0 EQ emp_wksp::emp_badge_number 
        MOVE FROM # emp_file, emp_wksp 
        pers_find_employee_proc = persmsg_success 
 
    USE 
        SELECT ERR 
            CASE basicerr_record_not_found 
                pers_find_employee_proc = persmsg_empnotfound 
            CASE ELSE 
                CALL ACMS$RAISE_NONREC_EXCEPTION( RMSSTATUS( emp_file ) ) 
                EXIT HANDLER 
        END SELECT 
    END WHEN 
 
    END FUNCTION 

4.4.3 Writing and Updating RMS Records

This section explains how to write a new record into an RMS file and how to update a record in an RMS file.

Example 4-14 illustrates how to write a new record to a file. The PERS_ADD_EMPLOYEE_PROC procedure is used to store a new record in an employee file using the information entered by the user and passed to the procedure in a task workspace. The procedure first stores the current time in the employee record; the time-stamp field is used for consistency checking by the update procedure. It then initializes the return status to success and writes the new record to the file. Because this procedure is executing in a server that opens the file for read and write access with explicit lock control, the procedure must unlock the record if the write operation completes successfully. If the write operation fails with a duplicate key, the procedure returns an error status to the task.

Example 4-14 Step Procedure in COBOL that Writes an RMS Record

IDENTIFICATION DIVISION. 
PROGRAM-ID. pers_add_employee_proc. 
       . 
       . 
       . 
DATA DIVISION. 
 
FILE SECTION. 
FD      emp_file 
        EXTERNAL 
        DATA RECORD IS employee_record 
        RECORD KEY emp_badge_number OF employee_record. 
 
COPY "pers_cdd.employee_record" FROM DICTIONARY. 
       . 
       . 
       . 
 
LINKAGE SECTION. 
COPY "pers_cdd.employee_record" FROM DICTIONARY 
    REPLACING ==employee_record== BY ==emp_wksp_record==. 
 
 
PROCEDURE DIVISION USING emp_wksp_record GIVING return_status. 
MAIN SECTION. 
 
 
000-start. 
    CALL "SYS$GETTIM" USING 
            BY REFERENCE emp_last_update OF emp_wksp_record 
            GIVING return_status. 
    IF return_status IS FAILURE 
    THEN 
        CALL "LIB$STOP" USING BY VALUE return_status 
    END-IF. 
 
    MOVE persmsg_success TO return_status. 
    WRITE employee_record FROM emp_wksp_record 
        ALLOWING NO 
        INVALID KEY 
            MOVE persmsg_empexists TO return_status 
        NOT INVALID KEY 
            UNLOCK emp_file ALL RECORDS 
    END-WRITE. 
       . 
       . 
       . 
999-end. 
    EXIT PROGRAM. 

Example 4-15 illustrates how to update a record in an RMS file. The PERS_CHANGE_EMPLOYEE_PROC procedure updates a record in an employee file using the information that is entered by the user and passed to the procedure in a task workspace. To conserve resources, the task does not retain server context while the user is modifying the employee's information. Therefore, the procedure must ensure that the information in the record has not changed while the user was updating the information on the screen.

The procedure first rereads the original record in the file and then uses a time-stamp stored in the record to ensure that the version read in this procedure is the same as the version read previously by the PERS_FIND_EMPLOYEE_PROC procedure. If the record has been updated, the procedure returns an error and unlocks the record. If the record has not been changed, the procedure copies the data from the task workspace record to the file record, calls SYS$GETTIM to retrieve the current system time, and updates the current record.

Because the employee file was opened using explicit lock control, the procedure must unlock the record after updating it. The error handling in this procedure checks for record-locked and record-lock timeout errors in case another user is trying to update the employee's record at the same time. In addition, it also checks for a record-not-found error in case the employee's record was deleted while the user was modifying the information. In both cases, the procedure returns an error status so the task can retrieve the error message text and inform the user of the problem.

Example 4-15 Step Procedure in BASIC that Updates an RMS Record

    FUNCTION LONG pers_change_employee_proc( employee_record emp_wksp ) 
 
    %INCLUDE "pers_files:pers_common_defns" 
    %INCLUDE %FROM %CDD "pers_cdd.employee_record" 
 
 
    DECLARE LONG sts 
 
    MAP ( emp_map ) employee_record emp_rec 
 
 
    WHEN ERROR IN 
        GET # emp_file,                                         & 
            KEY # 0 EQ emp_wksp::emp_badge_number,              & 
            ALLOW NONE,                                         & 
            WAIT 20 
        IF emp_rec::emp_last_update = emp_wksp::emp_last_update & 
 
        THEN 
            MOVE TO # emp_file, emp_wksp 
            sts = SYS$GETTIM( emp_rec::emp_last_update BY REF ) 
            IF ( sts AND 1% ) = 0%                              & 
            THEN 
                CALL LIB$STOP( sts ) 
            END IF 
            UPDATE # emp_file 
            pers_change_employee_proc = persmsg_success 
 
        ELSE 
            pers_change_employee_proc = persmsg_empchanged 
        END IF 
        UNLOCK # emp_file 
 
    USE 
        SELECT ERR 
            CASE    basicerr_record_not_found 
                pers_change_employee_proc = persmsg_empdeleted 
            CASE    basicerr_record_locked,                             & 
                    basicerr_deadlock,                                  & 
                    basicerr_wait_exhausted 
                pers_change_employee_proc = persmsg_emplocked 
            CASE    ELSE 
                CALL ACMS$RAISE_NONREC_EXCEPTION( RMSSTATUS( emp_file ) ) 
                EXIT HANDLER 
        END SELECT 
    END WHEN 
 
    END FUNCTION 

4.4.4 Handling Errors

You typically write an error handler to process errors returned by RMS when accessing records in a file. The examples in Section 4.4.2 and Section 4.4.3 illustrate how to handle some standard errors, such as record-not-found, that RMS can return when you read, write, or update a record in an RMS file. In addition, also be aware of the error conditions that can occur when you use RMS files in distributed transactions.

Some RMS errors are expected and are handled by resuming normal program execution. For example, RMS returns an end-of-file error if a procedure reads past the last record in a file. In this case, the program can resume execution and process the records that have been read. RMS can also return a number of recoverable errors that the program should check for and handle. For example, if RMS returns a deadlock error, you might want to roll back the transaction and process the transaction again. Finally, RMS can return a number of nonrecoverable errors. For example, a disk on which a file resides might fail. In this case, the program cannot continue until the problem has been resolved.

A distributed transaction can abort at any time. For example, if the PERS_CHANGE_EMPLOYEE_PROC procedure shown in Section 4.4.3 participates in a distributed transaction, the transaction could time out while the procedure is reading the original copy of the employee's record or while updating the record with the new information. If a transaction aborts while a step procedure is executing, RMS automatically rolls back an active recovery unit. If a step procedure reads a record from the file after a distributed transaction has aborted, RMS completes the operation successfully if the record exists and is not locked by another process. However, the step procedure receives an error if it executes a recoverable operation, such as a write or update operation, on the file. Therefore, an error handler for a step procedure should check for and handle the errors that RMS returns in this situation.

If you use RMS in a distributed transaction, you must write a server cancel procedure to release any records that might be read and locked by a step procedure after a distributed transaction aborts. See Chapter 2 for more information on writing server cancel procedures.

Typically, you want to retry a transaction automatically in the event of a recoverable error condition such as a deadlock, lock-timeout or transaction timeout error. RMS returns deadlock and lock-timeout errors to your step procedure when you access the file. In contrast, if a distributed transaction times out, the distributed transaction is aborted, and ACMS raises a transaction exception in the task. In this case, RMS returns an error if the step procedure accesses the file after the transaction has aborted.

There is an easy technique, illustrated in examples in this section, that allows you to simplify an exception handler that handles recoverable transaction exceptions in a task definition. The following list indicates how the error handler in the step procedure handles each type of error returned by RMS:

The following example illustrates how to handle RMS errors using COBOL. In this example, the error-handling code in the Declaratives section uses the RMS error status when checking for record locks because COBOL returns error 30 for all but the record-locked error.


   . 
   . 
   . 
PROCEDURE DIVISION USING emp_wksp_record GIVING return_status. 
 
 
DECLARATIVES. 
employee_file SECTION. 
    USE AFTER STANDARD ERROR PROCEDURE ON emp_file. 
 
employee_file_handler. 
    EVALUATE TRUE 
        WHEN    ( ( RMS-STS OF emp_file = RMS$_RLK ) OR 
                  ( RMS-STS OF emp_file = RMS$_DEADLOCK ) ) 
            CALL "ACMS$RAISE_TRANS_EXCEPTION" USING 
                        BY REFERENCE ACMS$_TRANSTIMEDOUT 
        WHEN    ( ( RMS-STS OF emp_file = RMS$_NRU ) OR 
                  ( RMS-STS OF emp_file = RMS$_DDTM_ERR ) ) 
            CALL "ACMS$RAISE_TRANS_EXCEPTION" USING 
                        BY REFERENCE RMS-STS OF emp_file 
 
        WHEN    OTHER 
            CALL "LIB$SIGNAL" USING 
                        BY REFERENCE RMS-STS OF emp_file, 
                        BY REFERENCE RMS-STV OF emp_file 
            CALL "ACMS$RAISE_NONREC_EXCEPTION" USING 
                        BY REFERENCE RMS-STS OF emp_file 
    END-EVALUATE. 
END DECLARATIVES. 
 
 
MAIN SECTION. 
000-start. 
    MOVE persmsg_success TO return_status. 
 
 
    MOVE emp_badge_number OF emp_wksp_record TO 
         emp_badge_number OF employee_record. 
    READ emp_file RECORD 
        ALLOWING NO OTHERS 
        KEY IS emp_badge_number OF employee_record 
        INVALID KEY 
            MOVE persmsg_empdeleted TO return_status 
            GO TO 999-end 
    END-READ. 
 
 
    IF emp_last_update OF employee_record = emp_last_update OF emp_wksp_record 
    THEN 
        CALL "SYS$GETTIM" USING 
                BY REFERENCE emp_last_update OF emp_wksp_record 
                GIVING return_status 
        IF return_status IS FAILURE 
        THEN 
            CALL "LIB$STOP" USING BY VALUE return_status 
        END-IF 
 
        REWRITE employee_record FROM emp_wksp_record 
            ALLOWING NO OTHERS 
            INVALID KEY 
                CALL "ACMS$RAISE_NONREC_EXCEPTION" 
                            USING RMS-STS OF emp_file 
        END-REWRITE 
 
    ELSE 
        MOVE persmsg_empchanged TO return_status 
    END-IF. 
 
    UNLOCK emp_file ALL RECORDS. 
 
999-end. 
    EXIT PROGRAM. 


Previous Next Contents Index