-- -- $Header: owb_lock.pkb 23-apr-2008.02:13:20 dwhittin Exp $ -- -- owb_lock.pkb -- -- Copyright (c) 2006, 2008, Oracle. All rights reserved. -- -- NAME -- owb_lock.pkb - This package provides locking routines for OWB product. -- -- DESCRIPTION -- This package provides locking routines for OWB product. -- -- ALLOCATE_UNIQUE is a wrapper around DBMS_LOCK.ALLOCATE_UNIQUE, -- but it executes it in an autonomous transaction. -- -- LOCK_WORKSPACE and UNLOCK_WORKSPACE can be used to lock/unlock the -- access to a workspace. -- -- LOCK_USER and UNLOCK_USER can be used to lock/unlock the -- access to a user. -- -- In general, any object can be locked in one of the folowing three modes: -- EXCLUSIVE_MODE - this lock mode conflicts with any other mode. -- WRITE_MODE - this mode conflicts with EXCLUSIVE_MODE and WRITE_MODE. -- SHARED_MODE - this mode conflicts only with EXCLUSIVE_MODE. -- The full lock compatibility table is shown below. -- When another process holds "held", an attempt to get "get" does -- the following: -- -- held get-> SHARED_MODE WRITE_MODE EXCLUSIVE_MODE -- SHARED_MODE success success failure -- WRITE_MODE success failure failure -- EXCLUSIVE_MODE failure failure failure -- -- MODIFIED (MM/DD/YY) -- dwhittin 05/06/08 - XbranchMerge dwhittin_bug-6944914 from tokyo -- dwhittin 04/23/08 - Added new REQUEST function to handle existing lock -- rvelisar 08/21/06 - Creation -- CREATE OR REPLACE PACKAGE BODY OWB_LOCK AS ----------------------------------------------------------------------------- -- Types and package variables ----------------------------------------------------------------------------- -- This type represents the current state of the locking counts for the locks -- requested for an object. TYPE lock_state_bucket_type IS RECORD ( shared_count INTEGER := 0, write_count INTEGER := 0, exclusive_count INTEGER := 0); -- This type defines a map of: id -> lock_state_bucket TYPE lock_state_bucket_list_type IS TABLE OF lock_state_bucket_type INDEX BY BINARY_INTEGER; -- This is a package variable containing the map of all object ids to their -- state buckets lock_state_bucket_list lock_state_bucket_list_type; ----------------------------------------------------------------------------- -- Internal functions and procedures ----------------------------------------------------------------------------- FUNCTION GET_CURRENT_LOCK_STATE( id IN INTEGER) RETURN INTEGER IS current_state lock_state_bucket_type; BEGIN IF lock_state_bucket_list.EXISTS(id) THEN current_state := lock_state_bucket_list(id); ELSE RETURN NONE_MODE; END IF; IF current_state.exclusive_count > 0 THEN RETURN EXCLUSIVE_MODE; ELSIF current_state.write_count > 0 THEN RETURN WRITE_MODE; ELSIF current_state.shared_count > 0 THEN RETURN SHARED_MODE; ELSE RETURN NONE_MODE; END IF; END GET_CURRENT_LOCK_STATE; FUNCTION GET_SHARED_LOCK_COUNT( id IN INTEGER) RETURN INTEGER IS current_state lock_state_bucket_type; BEGIN IF lock_state_bucket_list.EXISTS(id) THEN current_state := lock_state_bucket_list(id); RETURN current_state.shared_count; ELSE RETURN 0; END IF; END GET_SHARED_LOCK_COUNT; FUNCTION GET_WRITE_LOCK_COUNT( id IN INTEGER) RETURN INTEGER IS current_state lock_state_bucket_type; BEGIN IF lock_state_bucket_list.EXISTS(id) THEN current_state := lock_state_bucket_list(id); RETURN current_state.write_count; ELSE RETURN 0; END IF; END GET_WRITE_LOCK_COUNT; FUNCTION GET_EXCLUSIVE_LOCK_COUNT( id IN INTEGER) RETURN INTEGER IS current_state lock_state_bucket_type; BEGIN IF lock_state_bucket_list.EXISTS(id) THEN current_state := lock_state_bucket_list(id); RETURN current_state.exclusive_count; ELSE RETURN 0; END IF; END GET_EXCLUSIVE_LOCK_COUNT; PROCEDURE UPGRADE_LOCK_STATE( id IN INTEGER, lock_mode IN INTEGER) IS current_state lock_state_bucket_type; BEGIN IF lock_state_bucket_list.EXISTS(id) THEN current_state := lock_state_bucket_list(id); END IF; IF lock_mode = EXCLUSIVE_MODE THEN current_state.exclusive_count := current_state.exclusive_count + 1; ELSIF lock_mode = WRITE_MODE THEN current_state.write_count := current_state.write_count + 1; ELSIF lock_mode = SHARED_MODE THEN current_state.shared_count := current_state.shared_count + 1; ELSE raise_application_error(-20061, 'Invalid locking mode passed to function OWB_LOCK.UPGRADE_LOCK_STATE'); END IF; lock_state_bucket_list(id) := current_state; END UPGRADE_LOCK_STATE; PROCEDURE DOWNGRADE_LOCK_STATE( id IN INTEGER, lock_mode IN INTEGER) IS current_state lock_state_bucket_type; BEGIN IF lock_state_bucket_list.EXISTS(id) THEN current_state := lock_state_bucket_list(id); ELSE RETURN; -- or throw some exception? END IF; IF lock_mode = EXCLUSIVE_MODE AND current_state.exclusive_count > 0 THEN current_state.exclusive_count := current_state.exclusive_count - 1; ELSIF lock_mode = WRITE_MODE AND current_state.write_count > 0 THEN current_state.write_count := current_state.write_count - 1; ELSIF lock_mode = SHARED_MODE AND current_state.shared_count > 0 THEN current_state.shared_count := current_state.shared_count - 1; ELSE raise_application_error(-20062, 'Invalid locking mode passed to function OWB_LOCK.DOWNGRADE_LOCK_STATE'); END IF; IF current_state.exclusive_count > 0 OR current_state.write_count > 0 OR current_state.shared_count > 0 THEN lock_state_bucket_list(id) := current_state; ELSE lock_state_bucket_list.DELETE(id); END IF; END DOWNGRADE_LOCK_STATE; ---------------------------------------------------------------------------- function REQUEST( lockhandle IN VARCHAR2, lockmode IN INTEGER DEFAULT DBMS_LOCK.X_MODE, timeout IN INTEGER DEFAULT DBMS_LOCK.MAXWAIT, release_on_commit IN BOOLEAN DEFAULT FALSE) RETURN INTEGER IS request_result INTEGER; BEGIN request_result := DBMS_LOCK.REQUEST(lockhandle, lockmode, timeout, release_on_commit); if request_result = 4 then request_result := DBMS_LOCK.CONVERT(lockhandle, lockmode, timeout); if request_result != 0 then request_result := DBMS_LOCK.RELEASE(lockhandle); request_result := DBMS_LOCK.REQUEST(lockhandle, lockmode, timeout, release_on_commit); end if; end if; return request_result; END REQUEST; ---------------------------------------------------------------------------- FUNCTION GET_FD_LOCKNAME( workspace_id IN INTEGER, object_id IN INTEGER) RETURN VARCHAR2 IS BEGIN RETURN 'OWB$FD$' || workspace_id || '$' || object_id; END GET_FD_LOCKNAME; FUNCTION GET_WR_LOCKNAME( workspace_id IN INTEGER, object_id IN INTEGER) RETURN VARCHAR2 IS BEGIN RETURN 'OWB$WR$' || workspace_id || '$' || object_id; END GET_WR_LOCKNAME; ----------------------------------------------------------------------------- FUNCTION LOCK_SHARED ( workspace_id IN INTEGER, object_id IN INTEGER) RETURN BOOLEAN IS fd_lockhandle VARCHAR2(128); request_result INTEGER; current_mode INTEGER; BEGIN current_mode := GET_CURRENT_LOCK_STATE(object_id); IF current_mode = NONE_MODE THEN ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); request_result := REQUEST(fd_lockhandle, SHARED_MODE, 0, FALSE); IF request_result = 0 THEN UPGRADE_LOCK_STATE(object_id, SHARED_MODE); RETURN TRUE; ELSE RETURN FALSE; END IF; ELSE UPGRADE_LOCK_STATE(object_id, SHARED_MODE); RETURN TRUE; END IF; END LOCK_SHARED; FUNCTION LOCK_EXCLUSIVE ( workspace_id IN INTEGER, object_id IN INTEGER) RETURN BOOLEAN IS fd_lockhandle VARCHAR2(128); request_result INTEGER; current_mode INTEGER; BEGIN current_mode := GET_CURRENT_LOCK_STATE(object_id); IF current_mode = EXCLUSIVE_MODE THEN UPGRADE_LOCK_STATE(object_id, EXCLUSIVE_MODE); RETURN TRUE; ELSE ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); IF current_mode = NONE_MODE THEN request_result := REQUEST( fd_lockhandle, EXCLUSIVE_MODE, 0, FALSE); ELSE request_result := DBMS_LOCK.CONVERT( fd_lockhandle, EXCLUSIVE_MODE, 0); END IF; IF request_result = 0 THEN UPGRADE_LOCK_STATE(object_id, EXCLUSIVE_MODE); RETURN TRUE; ELSE RETURN FALSE; END IF; END IF; END LOCK_EXCLUSIVE; FUNCTION LOCK_WRITE ( workspace_id IN INTEGER, object_id IN INTEGER) RETURN BOOLEAN IS fd_lockhandle VARCHAR2(128); wr_lockhandle VARCHAR2(128); request_result INTEGER; release_result INTEGER; current_mode INTEGER; BEGIN current_mode := GET_CURRENT_LOCK_STATE(object_id); IF current_mode = WRITE_MODE THEN UPGRADE_LOCK_STATE(object_id, WRITE_MODE); RETURN TRUE; ELSIF current_mode = NONE_MODE THEN ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); request_result := REQUEST( fd_lockhandle, SHARED_MODE, 0, FALSE); IF request_result != 0 THEN RETURN FALSE; END IF; ALLOCATE_UNIQUE(GET_WR_LOCKNAME(workspace_id, object_id), wr_lockhandle); request_result := REQUEST( wr_lockhandle, EXCLUSIVE_MODE, 0, FALSE); IF request_result !=0 THEN release_result := DBMS_LOCK.RELEASE(fd_lockhandle); IF release_result != 0 THEN raise_application_error(-20056, 'Could not release the lock ' || GET_FD_LOCKNAME(workspace_id, object_id)); END IF; RETURN FALSE; END IF; UPGRADE_LOCK_STATE(object_id, WRITE_MODE); RETURN TRUE; ELSIF current_mode = SHARED_MODE THEN ALLOCATE_UNIQUE(GET_WR_LOCKNAME(workspace_id, object_id), wr_lockhandle); request_result := REQUEST( wr_lockhandle, EXCLUSIVE_MODE, 0, FALSE); IF request_result =0 THEN UPGRADE_LOCK_STATE(object_id, WRITE_MODE); RETURN TRUE; ELSE RETURN FALSE; END IF; ELSIF current_mode = EXCLUSIVE_MODE THEN IF GET_WRITE_LOCK_COUNT(object_id) > 0 THEN UPGRADE_LOCK_STATE(object_id, WRITE_MODE); RETURN TRUE; ELSE ALLOCATE_UNIQUE(GET_WR_LOCKNAME(workspace_id, object_id), wr_lockhandle); request_result := REQUEST( wr_lockhandle, EXCLUSIVE_MODE, 0, FALSE); IF request_result = 0 THEN UPGRADE_LOCK_STATE(object_id, WRITE_MODE); RETURN TRUE; ELSE -- should not happen since we already have the exclusive lock raise_application_error(-20054, 'Could not obtain lock ' || GET_WR_LOCKNAME(workspace_id, object_id) || ' in exclusive mode. Possible locks name conflict.'); END IF; END IF; END IF; END LOCK_WRITE; FUNCTION LOCK_OBJECT ( workspace_id IN INTEGER, object_id IN INTEGER, lock_mode IN INTEGER) RETURN BOOLEAN IS BEGIN CASE lock_mode WHEN SHARED_MODE THEN RETURN LOCK_SHARED(workspace_id, object_id); WHEN EXCLUSIVE_MODE THEN RETURN LOCK_EXCLUSIVE(workspace_id, object_id); WHEN WRITE_MODE THEN RETURN LOCK_WRITE(workspace_id, object_id); ELSE raise_application_error(-20053, 'Invalid locking mode passed to function OWB_LOCK.LOCK_OBJECT'); END CASE; END LOCK_OBJECT; ---------------------------------------------------------------------------- PROCEDURE UNLOCK_SHARED ( workspace_id IN INTEGER, object_id IN INTEGER) IS fd_lockhandle VARCHAR2(128); BEGIN IF GET_SHARED_LOCK_COUNT(object_id) > 0 THEN DOWNGRADE_LOCK_STATE(object_id, SHARED_MODE); IF GET_CURRENT_LOCK_STATE(object_id) = NONE_MODE THEN ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); IF DBMS_LOCK.RELEASE(fd_lockhandle) != 0 THEN raise_application_error(-20056, 'Could not release the lock ' || GET_FD_LOCKNAME(workspace_id, object_id)); END IF; END IF; ELSE -- we don't have the shared lock, throw exception raise_application_error(-20057, 'Could not unlock object with id ' || object_id || '. You do not have the SHARED lock on this object.'); END IF; END UNLOCK_SHARED; PROCEDURE UNLOCK_EXCLUSIVE ( workspace_id IN INTEGER, object_id IN INTEGER) IS fd_lockhandle VARCHAR2(128); current_mode INTEGER; BEGIN IF GET_EXCLUSIVE_LOCK_COUNT(object_id) > 0 THEN DOWNGRADE_LOCK_STATE(object_id, EXCLUSIVE_MODE); current_mode := GET_CURRENT_LOCK_STATE(object_id); IF current_mode = NONE_MODE THEN ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); IF DBMS_LOCK.RELEASE(fd_lockhandle) != 0 THEN raise_application_error(-20056, 'Could not release the lock ' || GET_FD_LOCKNAME(workspace_id, object_id)); END IF; ELSIF current_mode = SHARED_MODE OR current_mode = WRITE_MODE THEN ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); IF DBMS_LOCK.CONVERT(fd_lockhandle, SHARED_MODE, 0) != 0 THEN raise_application_error(-20058, 'Could not convert the lock ' || GET_FD_LOCKNAME(workspace_id, object_id) || 'into SHARED mode.'); END IF; END IF; ELSE -- we don't have the exclusive lock, throw exception raise_application_error(-20059, 'Could not unlock object with id ' || object_id || '. You do not have the EXCLUSIVE lock on this object.'); END IF; END UNLOCK_EXCLUSIVE; PROCEDURE UNLOCK_WRITE ( workspace_id IN INTEGER, object_id IN INTEGER) IS fd_lockhandle VARCHAR2(128); wr_lockhandle VARCHAR2(128); BEGIN IF GET_WRITE_LOCK_COUNT(object_id) > 0 THEN DOWNGRADE_LOCK_STATE(object_id, WRITE_MODE); IF GET_WRITE_LOCK_COUNT(object_id) <= 0 THEN ALLOCATE_UNIQUE(GET_WR_LOCKNAME(workspace_id, object_id), wr_lockhandle); IF DBMS_LOCK.RELEASE(wr_lockhandle) != 0 THEN raise_application_error(-20056, 'Could not release the lock ' || GET_WR_LOCKNAME(workspace_id, object_id)); END IF; IF GET_CURRENT_LOCK_STATE(object_id) = NONE_MODE THEN ALLOCATE_UNIQUE(GET_FD_LOCKNAME(workspace_id, object_id), fd_lockhandle); IF DBMS_LOCK.RELEASE(fd_lockhandle) != 0 THEN raise_application_error(-20056, 'Could not release the lock ' || GET_FD_LOCKNAME(workspace_id, object_id)); END IF; END IF; END IF; ELSE raise_application_error(-20060, 'Could not unlock object with id ' || object_id || '. You do not have the WRITE lock on this object.'); END IF; END UNLOCK_WRITE; PROCEDURE UNLOCK_OBJECT ( workspace_id IN INTEGER, object_id IN INTEGER, lock_mode IN INTEGER) IS BEGIN CASE lock_mode WHEN SHARED_MODE THEN UNLOCK_SHARED(workspace_id, object_id); WHEN EXCLUSIVE_MODE THEN UNLOCK_EXCLUSIVE(workspace_id, object_id); WHEN WRITE_MODE THEN UNLOCK_WRITE(workspace_id, object_id); ELSE raise_application_error(-20055, 'Invalid locking mode passed to function OWB_LOCK.UNLOCK_OBJECT'); END CASE; END UNLOCK_OBJECT; ----------------------------------------------------------------------------- -- Public functions and procedures ----------------------------------------------------------------------------- -- Procedure ALLOCATE_UNIQUE is a wrapper around DBMS_LOCK.ALLOCATE_UNIQUE, -- but it executes it in an autonomous transaction. The parameters are the -- same as for DBMS_LOCK.ALLOCATE_UNIQUE. PROCEDURE ALLOCATE_UNIQUE ( lockname IN VARCHAR2, lockhandle OUT VARCHAR2, expiration_secs IN INTEGER DEFAULT 864000) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN DBMS_LOCK.ALLOCATE_UNIQUE(lockname, lockhandle, expiration_secs); END ALLOCATE_UNIQUE; -- Function LOCK_WORKSPACE can be used to request a lock for a workspace, -- with a given mode. -- Input parameters: -- workspace_id - the id (elementid) of the workspace to be locked -- lock_mode - the mode the lock is requested. Valid modes are: -- SHARED_MODE, WRITE_MODE and EXCLUSIVE_MODE. -- Return value: -- true - if the lock was obtained -- false - otherwise FUNCTION LOCK_WORKSPACE ( workspace_id IN INTEGER, lock_mode IN INTEGER) RETURN BOOLEAN IS BEGIN RETURN LOCK_OBJECT(workspace_id, workspace_id, lock_mode); END LOCK_WORKSPACE; -- Procedure UNLOCK_WORKSPACE can be used to release a lock for a workspace, -- you already hold with a given mode. If you don't hold that lock, the -- procedure will raise an exception. -- Input parameters: -- workspace_id - the id (elementid) of the workspace to be unlocked -- lock_mode - the mode the lock was obtained. Valid modes are: -- SHARED_MODE, WRITE_MODE and EXCLUSIVE_MODE. PROCEDURE UNLOCK_WORKSPACE ( workspace_id IN INTEGER, lock_mode IN INTEGER) IS BEGIN UNLOCK_OBJECT(workspace_id, workspace_id, lock_mode); END UNLOCK_WORKSPACE; -- Function LOCK_USER can be used to request a lock for a user, -- with a given mode. -- Input parameters: -- workspace_id - the id (elementid) of the workspace containing the user. -- user_id - the id (elementid) of the user to be locked. -- lock_mode - the mode the lock is requested. Valid modes are: -- WRITE_MODE and EXCLUSIVE_MODE, and they both behave the -- same. -- Return value: -- true - if the lock was obtained -- false - otherwise FUNCTION LOCK_USER ( workspace_id IN INTEGER, user_id IN INTEGER, lock_mode IN INTEGER) RETURN BOOLEAN IS wksp_result BOOLEAN; user_result BOOLEAN; BEGIN IF lock_mode = WRITE_MODE OR lock_mode = EXCLUSIVE_MODE THEN -- first get the SHARED lock on the workspace wksp_result := LOCK_OBJECT(workspace_id, workspace_id, SHARED_MODE); IF NOT wksp_result THEN RETURN FALSE; END IF; -- then the lock on the user user_result := LOCK_OBJECT(workspace_id, user_id, WRITE_MODE); IF NOT user_result THEN UNLOCK_OBJECT(workspace_id, workspace_id, SHARED_MODE); END IF; RETURN user_result; ELSE raise_application_error(-20051, 'Invalid locking mode passed to function OWB_LOCK.LOCK_USER'); END IF; END LOCK_USER; -- Procedure UNLOCK_USER can be used to release a lock for a user, -- you already hold with a given mode. If you don't hold that lock, the -- procedure will raise an exception. -- Input parameters: -- workspace_id - the id (elementid) of the workspace containing the user. -- user_id - the id (elementid) of the user to be unlocked. -- lock_mode - the mode the lock was obtained. Valid modes are: -- WRITE_MODE and EXCLUSIVE_MODE. PROCEDURE UNLOCK_USER ( workspace_id IN INTEGER, user_id IN INTEGER, lock_mode IN INTEGER) IS BEGIN IF lock_mode = WRITE_MODE OR lock_mode = EXCLUSIVE_MODE THEN UNLOCK_OBJECT(workspace_id, user_id, WRITE_MODE); UNLOCK_OBJECT(workspace_id, workspace_id, SHARED_MODE); ELSE raise_application_error(-20052, 'Invalid locking mode passed to function OWB_LOCK.UNLOCK_USER'); END IF; END UNLOCK_USER; END OWB_LOCK;