Mutex
This project implements mutual exclusion logic that allows a resource to be locked per-user. This feature is used to block more than one user from editing the same document draft at the same time. Note that since the lock is per user, it won’t stop performing the same action in two separate browser tabs while being logged in to the same account. Single user can also hold multiple locks (to multiple documents).
Resources to lock are typically identified by their UID (unique identified). The uid field is present in all models extending BaseModel.
The lock information is stored in Redis database determined by REDIS_DB_MUTEX.
Locks have a timeout. The lock needs to be renewed by acquiring it again to maintain the resource lock. When the user has finished with the object, the lock can be explicitly released, or it will be release automatically after the timeout period.
Note
Note that the locking an object does not block it from being read or written. It signals that the object is in use to other users who attempt to acquire a lock.
Python code
mutex = Mutex(object.uid, timedelta(minutes=1), user)
try:
mutex.acquire()
print("You have a lock on the object for the next minute. Please Re-acquire the lock if you need to extend it, or release when no longer needed.")
except ResourceLocked as e:
print(f"Resource already in use by {e.user}. Please try again in 1 minute")
...
# lock can be released whether it was acquired or not
mutex.release()
- exception utils.mutex.MutexError
- exception utils.mutex.RedisNotSetUp
Exception for when redis connection is not set-up (host string is not set). Depending on the context, handlers of this error can show the user an error message, and default to the same behaviour as if the resource was already locked, or let the user proceed with caution.
In practice this error is only expected in development, when redis is not set-up.
- exception utils.mutex.ResourceLocked(resource_id: str, value: str)
Exception raised when a resource is already locked by someone else
- property user_id: int
ID of the user holding the lock
- profile_id
Profile ID (Auditor) of the user holding the lock
- class utils.mutex.Mutex(resource_uid: Any, timeout: timedelta, user: User)
Implements mutual exclusion (Semaphore) for arbitrary resources by using Redis as its backend.
Wraps Redis in commands to acquire and release lock methods that raise
ResourceLockedif the resource is already locked.- Parameters:
resource_uid – UID of the object to lock
timeout – timeout after which the lock will expire automatically
user – the user who acquires the lock
- check_lock() bool
Checks the status of the resource lock, whether it’s locked by current user, someone else, or not locked.
- Returns:
True if resource is locked by current user, or False if it’s not locked at all.
- Raises:
ResourceLocked – if resource is locked by someone else
RedisNotSetUp – if redis connection required by Mutex is not set-up
- acquire()
Acquires or refreshes the lock.
- Raises:
ResourceLocked – if resource is already locked by someone else
RedisNotSetUp – if redis connection required by Mutex is not set-up
- release()
Releases the lock if acquired by the current user. Does not raise any exceptions if resource is locked by someone else. Fails silently if Redis is not setup.
REST API
Lock can be acquired and released by making a POST (acquire) or DELETE (release) request to the API endpoint:
/api/lock/{resource-uid}/
- POST
Acquires lock and responds with
200status code if successful, or423if resource is already locked by another user (the response will contain the lock details).If the resource is already locked by the same user, the lock is renewed and
200status code returned.Response json if lock acquisition has failed.{ "message": "Resource is already in use by another user", "user_id": 1 }
- DELETE
Releases the lock if the current user has it. Always responds with a successful
204status.