Implement logical application locks

Introduction

SmartLocks provide a method to emulate actual database record locks in situations where a physical record lock is not possible – or not demanded. SmartLocks will allow a state-less web application, for instance, to acquire the lock to a resource (represented by a record identifier or a unique resource identifier) for a given time in such a way that other sessions may not be able to acquire that lock at the same time.

Locked resources may be logical resources such as the planning board for a certain period or an order consisting of a head record and multiple levels of child records.

SmartLocks work in any possible session type: A fat client (GUI) session, an AppServer session connected by an ABL or .NET/Java Proxy session, a REST session or a session serving a web request through a PASOE web handler. A new universal API has been added to the SessionManager class to return a suitable SessionId from any session.

SmartLocks should not be acquired from an AppServer client directly. Instead they will be requested by any required backend functionality called by the client. 

Database Design

Locks are stored in a single database table named “SmartLock”. The design of this table is:

Fields

Field name

Data type

Field name

Data type

LockGuid (The unique identified of a SmartLock, created by the create-trigger)

CHARACTER, x(36)

LockSessionId (The identifier of the session that holds the lock).

CHARACTER, x(70)

UserGuid (as provided by the SessionManager DetermineUserGuid method)

CHARACTER, x(36)

TableGuid (reference to SmartTable.TableGuid)

CHARACTER, x(36), INITIAL ?

KeyValues, for instance BufferHelper:UniqueKeyValues

CHARACTER, x(70), INITIAL ?

LockResourceIdentifier

CHARACTER, x(70), INITIAL ?

LockCreated

DATETIME-TZ, INTIAL NOW

LockRefreshed

DATETIME-TZ, INITIAL NOW

LockExpires

DATETIME-TZ, INITIAL ?

Indexes

  • LockGuid (PRIMARY UNIQUE)

  • LockSessionId

  • UserGuid

  • TableGuid, KeyValues (UNIQUE)

  • LockResourceIdentifier (UNIQUE)

ISmartLockService functionality

AcquireLock

METHOD PUBLIC LOGICAL AcquireLock (pcTableGUID, pcKeyValues, piLockDuration, plThrowOnAlreadyLocked) METHOD PUBLIC LOGICAL AcquireLock (pcResourceIdentifier, piLockDuration, plThrowOnAlreadyLocked)

In the implementation, both methods are just facades for the protected method:

METHOD PROTECTED LOGICAL AcquireLock (pcTableGUID, pcKeyValues, pcResourceIdentifier, piLockDuration, plThrowOnAlreadyLocked)

When AcquireLock is called, the system tries to fetch a SmartLock record based on TableGUID and KeyValues OR the LockResourceIdentifier field – depending on what values are provided.

When the lock record is not available, a lock record is created with the following values:

LockGuid

Created by Trigger

LockSessionId

The new Session Manager’s SessionId

UserGuid

The GUID of the current user

TableGuid

pcTableGuid

KeyValues

pcKeyValues

LockResourceIdentifier

pcResourceIdentifier

LockCreated

NOW

LockRefreshed

NOW

LockExpires

NOW + piLockDuration (in seconds)

When the lock record is available and the lock record’s session ID matches the current session ID, the lock is refreshed, assigning the following fields.

LockRefreshed

NOW

LockExpires

NOW + piLockDuration (in seconds)

When the SmartLock record is available but expired (LockExpires < NOW), the current session will “hijack” the lock. The following assignment will be made:

LockSessionId

The new Session Manager’s SessionId

UserGuid

The GUID of the current user

LockCreated

NOW

LockRefreshed

NOW

LockExpires

NOW + piLockDuration (in seconds)

When the SmartLock record is available, but from a different session and not yet expired, the method returns FALSE or throws a ResourceLockedException (with properties for the name of the user that holds the lock, the time when the lock was created, the TableGuid, KeyValues and LockIdentifier) – based on the plThrowOnAlreadyLocked parameter.

When the SmartLock is initially not available, but we receive an unique key values error when saving the SmartLock record through the Business Entity we also either return FALSE or THROW the exception. A different session will have been faster in acquiring the lock after we checked it was not there.

When, while saving the SmartLock, we receive the error message that the record has been modified by another user, we re-fetch the SmartLock record and verify if the SessionId matches our session Id. If that’s the case, another AppServer process for the same client session may have just acquired the lock. We consider that ok and return TRUE. If it’s a different session id, we return FALSE or THROW the exception.

Locks must either be created for a TableGUID AND KeyValues OR a ResourceIdentifier. When TableGUID and KeyValues are <> ? the ResourceIdentifier must be ? and when the RecourceIdentifier is <> ? then TableGUID AND KeyValues must be ?.

Release Lock

METHOD PUBLIC LOGICAL ReleaseLock (pcTableGUID, pcKeyValues) METHOD PUBLIC LOGICAL ReleaseLock (pcResourceIdentifier)

In the implementation, both methods are just facades for the protected method:

The method removes a lock record when it exists and the Session ID matches the current session ID. In this case the method returns TRUE.

If not, the method simply returns FALSE.

ReleaseAllSessionLocks

The method ReleaseAllSessionLocks fetches all lock records of the current session and deletes them.

The as_disconnect method will call into this method when the ISmartLockService is loaded. If not, it does nothing (does not throw any error).

SmartLockMaintenance scheduler task functionality

The SmartLockMaintenance scheduler task periodically deletes all SmartLock records where the expire date is < NOW.