,lockhandle => temp_lockhandle); /* || add to end of lockhandle_tbl */ temp_index := lockhandle_tbl.LAST+1; lockhandle_tbl(temp_index).handle := temp_lockhandle; lockhandle_tbl(temp_index).name := lockname_IN; END IF; RETURN temp_lockhandle; END lockhandle; The lockhandle function alone is enough to make using named locks much easier. It relieves the programmer of having to create lockhandle variables for each named lock and also guarantees that the ALLOCATE_UNIQUE procedure is called only once per named lock. New named locks can be used immediately without coding supporting routines, as these are handled generically in the function. Furthermore, the lockhandle function can be invoked directly in calls to REQUEST or CONVERT. In the following procedure, the printer_lockname variable holds the name of a lock being used to serialize access to a printer: PROCEDURE get_printer_lock (lock_status_OUT OUT INTEGER) IS BEGIN lock_status_OUT := DBMS_LOCK.REQUEST (dblock.lockhandle(printer_lockname)); END get_printer_lock; 4.1.4.1.2 get_lock_TF function Applications using DBMS_LOCK usually must check return values from calls to the REQUEST or CONVERT functions to determine if access to the locked resource has been acquired. The dblock package includes a function called get_lock_TF, which takes a lockname and lock mode as IN parameters and returns the Boolean value TRUE if the named lock has been acquired in the desired mode. Using get_lock_TF, we can write code like the following: IF dblock.get_lock_TF (printer_lockname,DBMS_LOCK.x_mode) THEN /* invoke print routine here */ ELSE /* cannot print, tell user to try later */ END IF; Code like this is far easier to understand and maintain than code that calls DBMS_LOCK programs directly. All the complexity of using DBMS_LOCK is eliminated; the program merely calls get_lock_TF and proceeds directly to appropriate logic based on the return value. Here is the body of get_lock_TF: /* Filename on companion disk: dblock.sql */* FUNCTION get_lock_TF (lockname_IN IN lockname_var%TYPE ,mode_IN IN INTEGER := DBMS_LOCK.x_mode ,timeout_IN IN INTEGER := 1 ,release_on_commit_TF IN BOOLEAN := FALSE) RETURN BOOLEAN IS call_status INTEGER; /* handle for the named lock */ temp_lockhandle lockhandle_var%TYPE := lockhandle(lockname_IN); [Appendix A] What's on the Companion Disk? 4.1.4 DBMS_LOCK Examples 216 BEGIN call_status := DBMS_LOCK.REQUEST (lockhandle => temp_lockhandle ,lockmode => mode_IN ,timeout => timeout_IN ,release_on_commit => release_on_commit_TF ); /* || if lock already owned, convert to requested mode */ IF call_status = 4 THEN call_status := DBMS_LOCK.CONVERT (lockhandle => temp_lockhandle ,lockmode => mode_IN ,timeout => timeout_IN ); END IF; RETURN (call_status = 0); END get_lock_TF; Notice that get_lock_TF first calls REQUEST and then CONVERT if the lock is already owned. This relieves the programmer of yet another bit of housekeeping, and the return value accurately reflects whether the lock is owned in the requested mode. The temp_lockhandle variable is used in the calls to DBMS_LOCK programs to avoid calling the lockhandle function more than once. 4.1.4.1.3 The committed_TF and release functions The dblock package also includes a procedure called release, which releases a named lock, and a function called committed_TF. The latter demonstrates using the release_on_commit parameter of the REQUEST function to determine whether a COMMIT has taken place in the session. The body of committed_TF looks like this: /* Filename on companion disk: dblock.sql */* /* used by committed_TF, unique to each session */ commit_lockname lockname_var%TYPE := DBMS_SESSION.UNIQUE_SESSION_ID; FUNCTION committed_TF RETURN BOOLEAN IS call_status INTEGER; BEGIN /* get unique lock, expire in one day */ call_status := DBMS_LOCK.REQUEST (lockhandle => lockhandle(commit_lockname,86400) ,lockmode => DBMS_LOCK.x_mode ,timeout => 0 ,release_on_commit => TRUE); RETURN (call_status = 0); END committed_TF; The committed_TF function uses a named lock called commit_lockname that is unique to each session, having been initialized by calling DBMS_SESSION.UNIQUE_SESSION_ID. It then calls DBMS_LOCK.REQUEST to acquire an exclusive lock on commit_lockname, making sure to specify TRUE for the release_on_commit parameter. Once the lock has been acquired initially, the success of subsequent calls indicates that the lock has been released, and thus a COMMIT (or ROLLBACK) has taken place. The function is probably not that useful in practice, but it makes a nice academic exercise. [Appendix A] What's on the Companion Disk? 4.1.4 DBMS_LOCK Examples 217 4.1.4.2 Using locks to signal service availability One way in which DBMS_LOCK can be usefully employed is to indicate the availability of service programs to database sessions. The basic steps are quite simple: 1. Assign specific locks to the server and/or each service provided. 2. The server process holds the lock(s) in exclusive mode when services are available. 3. Client programs request the lock to determine service availability. To make this more concrete, the following code fragments might be part of a package used to coordinate access to a computation server called calcman: PACKAGE calcman IS /* the actual service provider program */ PROCEDURE calcman_driver; /* function called by clients to determine availability */ FUNCTION calcman_available RETURN BOOLEAN; END calcman; PACKAGE BODY calcman IS /* lock name used to flag service availability */ calcman_lockname VARCHAR2(100):= 'CALCMAN_LOCK'; PROCEDURE calcman_driver IS BEGIN /* || get the special lock in exclusive mode */ IF dblock.get_lock_TF (lockname_IN => calcman_lockname ,mode_IN => DBMS_LOCK.x_mode ,timeout_IN => 1 ,release_on_commit_TF => FALSE) THEN /* || execute the service loop here, which probably || involves listening on a database pipe for || service requests and sending responses on pipes */ /* || loop forever and process calc requests */ WHILE NOT terminate_TF LOOP receive_unpack_calc_request (timeout_IN => DBMS_PIPE.maxwait ,request_rec_OUT=> request_rec ,return_code_OUT => temp_return_code); IF temp_return_code != 0 THEN DBMS_PIPE.PURGE(request_pipe); ELSE process_request(request_rec); [Appendix A] What's on the Companion Disk? 4.1.4 DBMS_LOCK Examples 218 END IF; END LOOP; ELSE /* service is already running in another process, exit */ RETURN; END IF: END calcman_driver; FUNCTION calcman_available RETURN BOOLEAN IS got_lock BOOLEAN; BEGIN got_lock := dblock.get_lock_TF (lockname => calcman_lockname ,mode_IN => DBMS_LOCK.sx_mode ,timeout_IN => 0 ,release_on_commit_TF => TRUE); /* || do not hold lock, this could conflict with || starting service */ dblock.release(calcman_lockname); /* failure to get lock indicates server available */ RETURN NOT got_lock; END calcman_available; END calcman; The calcman_driver procedure grabs and holds the lock as long as it is executing. If the lock is not available within one second, the procedure is already running in another session and exits silently in the current session. Thus, the lock ensures that only one calcman_driver will be executing at any time. Note the importance of not releasing the lock at COMMIT, ensuring that the lock is held as long as the service process is alive. The service can make itself unavailable at any time by simply releasing the lock. The service that calcman_driver provides is not specified in the previous code fragments. It could be a complex calculation requiring large PL/SQL tables for which the overhead of having all users execute the calculation individually is too great. Or it could be connected to an external service routine of some kind. A fuller discussion of how to implement such service procedures using database pipes can be found in Chapter 3, Intersession Communication. Client programs call the calcman_available function to determine whether the server is executing and providing its computation services. The function attempts to get the lock and, if it succeeds, this indicates that the service is not available. The lock is requested in shared mode exclusive; as a consequence, concurrent calls to the get_lock_TF function from different sessions may all succeed and indicate unavailability. If the lock is requested in exclusive mode, there is a chance that simultaneous execution of the function by two users could falsely indicate to one user that the service is available. The calcman_available function also releases the lock immediately to keep it from interfering with the calcman_driver program, which is attempting to secure the lock. 3.2 DBMS_ALERT: Broadcasting Alerts to Users 4.2 DBMS_TRANSACTION: Interfacing to SQL Transaction Statements [Appendix A] What's on the Companion Disk? 4.1.4 DBMS_LOCK Examples 219 Copyright (c) 2000 O'Reilly & Associates. All rights reserved. [Appendix A] What's on the Companion Disk? 4.1.4 DBMS_LOCK Examples 220