Concept
This document describes the concept, definitions and workflow of Locktopus and in a user-friendly manner.
Lock
The common definition of lock can be found on Wikipedia. What we want to know in context of this doc, is that the lock is the access (exclusive or shared) to a set of resources (one or more).
Resource
A lock resource is an abstraction of what should be locked. It is represented by path consisting of string segments. For example, a referrence to user foo.bar@fizz.buzz
in some abstract IT
department can be expressed with path ["user", "department", "IT", "foo.bar@fizz.buzz"]
. A resource is not required to specify concrete entity, e.g. if we want to access all users in the department, we then specify the path to be ["user", "department", "IT"]
. If we want to lock access for all the departments, we can use the next path: ["user", "department"]
. For all users:["user"]
. When we want to lock the whole namespace, our single option to do that is to use an empty path []
.
Notes:
Locktopus implements tree locking: parent paths respect child path and vice-versa. E.g. locking
["A", "B"]
for write interferes with either["A", "B", "C"]
and["A"]
.range locks are not implemented, e.g. using asterisk for locking
*@fizz.buzz
is not possible. The path segment is threated as a specific string token. However, range locks might be implemented on the application level. In this specific case, we can logically group users by domain and use this form of path:["user", "department", "IT", "domain/fizz.buzz", "foo.bar"]
for locking the specific user, and this path["user", "department", "IT", "domain/fizz.buzz"]
for locking the whole domainfizz.buzz
. But then we need to be sure to use only this hierarchy of path all over the application, since using it alongside with the former one will end up in data races.It does not matter whether plural on singular form of resources is used (
user
vsusers
). It does not practically affect the performance/memory either. But be sure to have the same form across your applicaiton.Trying to lock an empty set of resources is forbidden.
Locking redundant resources (shadowed by other resources within the lock) is allowed. For example, we can lock all users for write and a specific one for read (or write):
WRITE:
["user"]
<-- to lock all users for writeREAD:
["user", "department", "IT", "foo.bar@fizz.buzz"]
<-- to lock a specific user for read. Actually, we dont need this, since we already lock all users for write. This resource is meant to be shadowed by the first one.But doing that vice-versa pretty has sence and is not a redundant locking:
READ:
["user"]
<-- to lock all users for readWRITE:
["user", "department/IT", "foo.bar@fizz.buzz"]
<-- to lock a specific user for write. This is not shadowed by the first resource
Lock Type
Each resource in the lock should be specified with the lock type: write or read. Conceptually, it implements Go's RWMutex.
- WRITE: exclusive access to a resource. When the lock is acquired, other clients cannot acquire their locks to the same resource until it is released by the locker. And vice-versa, the resource cannot be acquired for write if it is acquired by other clients.
- READ: shared access to a resource. Can be acquired when there are no write locks to the resource.
Namespace
Namespaces are used to separate locks path's from each other. Working in different namespaces of a server is logically the same as working with different servers. Specifying the namespace is required by clients. A namespace is created as soon as the first client connects to it.
Connection Lifecycle and Workflow
The communication is stateful and synchronous. It begins with establishing a connection to the server. The connection is reusable after the lock is released. There can be only one lock managed by connection at a given moment.
A client can perform either of two commands: LOCK or RELEASE depending on in which of the three states it is: READY, ENQUEUED or ACQUIRED.
The states are as follows:
State | Description |
---|---|
READY | The client is ready to perform locking. This is the default state of a connection. LOCK is permitted |
ENQUEUED | The lock has been enqueued but not acquired because some of the resources are acquired by another client. In this state you can just wait until it is ACQUIRED. Premature RELEASE is permitted |
ACQUIRED | The lock has been completely taken by the client. RELEASE is permitted |
Client commands are as follows:
- LOCK: Try locking a set of resources. The server will immediately respond with a state indicating whether the lock has been ENQUEUED or ACQUIRED. If enqueued, the client will be then notified as soon as the lock is acquired.
- RELEASE: Release all the resources locked/enqueued by the precedent LOCK command. The server will immediately respond with the state READY.
Notes:
- If connection has been closed (for any reason), there is no way to restore the lock but to reconnect and lock the resources again.
Abandon Timeout
If the connection has been closed without releasing the lock, the server will release it after the timeout (abandon lock timeout
). The timeout value can be specified when making the connection, otherwise the default value for the server is used. The value should be as big as it is needed for stopping working with the resources locked plus the delay to determine the connection is closed on the client side.