G-8410
🆓Error
Always use application locks to ensure a program unit is only running once at a given time.
Reason
This technique allows us to have locks across transactions as well as a proven way to clean up at the end of the session.
The alternative using a table where a “Lock-Row” is stored has the disadvantage that in case of an error a proper cleanup has to be done to "unlock" the program unit.
Example
Non-Compliant Example
/* Example */ create or replace package body lock_up is -- manage locks in a dedicated table created as follows: -- CREATE TABLE app_locks ( -- lock_name VARCHAR2(128 CHAR) NOT NULL primary key -- ); procedure request_lock(in_lock_name in varchar2) is co_lock_name constant app_locks.lock_name%type := in_lock_name; begin -- raises dup_val_on_index insert into app_locks (lock_name) values (co_lock_name); end request_lock; procedure release_lock(in_lock_name in varchar2) is co_lock_name constant app_locks.lock_name%type := in_lock_name; begin delete from app_locks where lock_name = co_lock_name; end release_lock; end lock_up; / /* Call bad example */ declare co_lock_name constant app_locks.lock_name%type := 'APPLICATION_LOCK'; begin lock_up.request_lock(in_lock_name => co_lock_name); -- processing lock_up.release_lock(in_lock_name => co_lock_name); exception when dup_val_on_index then -- expected exception lock_up.release_lock(in_lock_name => co_lock_name); raise; when others then -- unexpected exception, logging is recommended lock_up.release_lock(in_lock_name => co_lock_name); raise; end; /
Issues
Line | Column | Message |
---|---|---|
12 | 7 | |
18 | 7 |
★★★★★
Compliant Solution -
/* Example */ create or replace package body lock_up is function request_lock( -- NOSONAR G-7460: non-deterministic in_lock_name in varchar2 ,in_release_on_commit in boolean default false ) return varchar2 is co_lock_name constant type_up.lock_name := in_lock_name; co_release_on_commit constant boolean := in_release_on_commit; l_lock_handle type_up.lock_handle; begin sys.dbms_lock.allocate_unique( lockname => co_lock_name ,lockhandle => l_lock_handle ,expiration_secs => constants_up.co_one_week ); if sys.dbms_lock.request( lockhandle => l_lock_handle ,lockmode => sys.dbms_lock.x_mode ,timeout => sys.dbms_lock.maxwait ,release_on_commit => co_release_on_commit ) > 0 then raise err.e_lock_request_failed; end if; return l_lock_handle; end request_lock; procedure release_lock(in_lock_handle in varchar2) is co_lock_type constant type_up.lock_handle := in_lock_handle; begin if sys.dbms_lock.release(lockhandle => co_lock_type) > 0 then raise err.e_lock_request_failed; end if; end release_lock; end lock_up; / /* Call good example */ declare l_handle type_up.lock_handle; co_lock_name constant type_up.lock_name := 'APPLICATION_LOCK'; begin l_handle := lock_up.request_lock(in_lock_name => co_lock_name); -- processing lock_up.release_lock(in_lock_handle => l_handle); exception when err.e_lock_request_failed then -- expected exception lock_up.release_lock(in_lock_name => co_lock_name); raise; when others then -- unexpected exception, logging is recommended lock_up.release_lock(in_lock_name => co_lock_name); raise; end; /
References
- same as Trivadis G-8410