Locks
Principle of Operation
The system has a mechanism for locking a record or an entire function. Locking data is stored in a specific object in the process memory; however, everything is prepared to move this object, for example, to Redis, to allow running multiple system instances with unified locking information.
Record Locking
A developer can place a lock on a specific record of a certain entity; then, when attempts are made to modify or delete it, the system will look for a lock key in the request object, and if not found, in the parent request, and so on to the top.
Locks are automatically placed on modification and deletion methods. A developer can also lock a record at the beginning of a custom method, which allows implementing various logic (e.g., checks) without fear that the initial data might be changed while they are in progress.
If the key is found, the operation is permitted. Searching in parent request objects allows child methods to manipulate the record, enabling logic to be freely moved to separate methods without manually passing the lock key to them.
If not (some other method is trying to change the same record), there are two modes:
In the first mode (default), the request attempts to re-acquire the lock at short intervals (the lock is called from a custom method if the developer wrote it there, or directly in a modification or deletion operation automatically by the system).
Attempts continue until the record is released or the time allocated for attempts expires. By default, this is 2 minutes, but when calling the lock, the developer can specify a different time using the “lockTime” parameter (in milliseconds).
In the second mode, if the “reject”=true parameter is passed when calling the lock (if the developer calls it manually), and if the record is occupied by another lock, the system will immediately reject the request without repeated attempts at short intervals.
A lock call looks like this:
const res = await lock(r, 'order_', id)
if (res.code) {
return new MyError('Failed to lock order for Order_.calcAmounts')
}
Parameters: request object, entity name, ID of the record to lock, and a fourth optional parameter—an object with locking parameters.
Function Locking
Sometimes it’s necessary to lock an entire method so that it cannot be called again until the current call is completed. This is done similarly to record locking: the first parameter is also the request object, the second parameter can be any string (e.g., function name), the third is null, and the fourth is an optional object with parameters.
res = await lock(r, this.name, 'syncBJ', null, {reject:true})
if (res.code) {
return new UserOk('The previous iteration of syncBJ is still running. Skipping this run.')
}
Releasing a Lock
A lock is released automatically when the method in which it was set completes. This occurs in the api module, through which any method call is made.
A developer can also manually release a lock by calling unlock:
unlock(name:string, id:number|string, key:string): IError
Monitoring and Management
In practice, I have never had to use these methods, but I will mention them. There are two methods, showLocks and unlockRec, which allow viewing the list of current locks and releasing any lock. These methods belong to CoreClass, meaning they are available to any class.
The unlockRec method does not allow releasing a lock on an entire function. If such a method is needed, it can be further developed.