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
Trang 1,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);
Trang 2BEGIN
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
Trang 34.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
Trang 4END 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
Trang 5Copyright (c) 2000 O'Reilly & Associates All rights reserved.