- All Implemented Interfaces:
- org.neo4j.kernel.impl.locking.Locks, org.neo4j.kernel.lifecycle.Lifecycle
public class ForsetiLockManager
extends org.neo4j.kernel.lifecycle.LifecycleAdapter
implements org.neo4j.kernel.impl.locking.Locks
Forseti, the Nordic god of justice
Forseti is a lock manager using the dreadlocks deadlock detection algorithm, which means
deadlock detection does not require complex RAG traversal and can be found in O(1).
In the best case, Forseti acquires a lock in one CAS instruction, and scales linearly with the number of cores.
However, since it uses a shared-memory approach, it will most likely degrade in use cases where there is high
contention and a very large number of sockets running the database.
As such, it is optimized for servers with up to, say, 16 cores across 2 sockets. Past that other strategies such
as centralized lock services using message passing may yield better results.
Locking algorithm
Forseti is used by acquiring clients, which act as agents on behalf of whoever wants to grab locks. The clients
have access to a central map of locks.
To grab a lock, a client must insert itself into the holder list of the lock it wants. The lock may either be a
shared lock or an exclusive lock. In the case of a shared lock, the client simply appends itself to the holder list.
In the case of an exclusive lock, the client has it's own unique exclusive lock, which it must put into the lock map
using a CAS operation.
Once the client is in the holder list, it has the lock.
Deadlock detection
Each Client maintains a waiting-for list, which by default always contains the client itself. This list indicates
which other clients are blocking our progress. By default, then, if client A is waiting for no-one, its waiting-for
list will contain only itself:
A.waitlist = [A]
Once the client is blocked by someone else, it will copy this someones entire wait list into it's own. Assuming A
becomes blocked by B, and B has a wait list of:
B.waitlist = [B]
Then A will modify is's wait list as:
A.waitlist = [A] U [B] => [A,B]
It will do this in a loop, continiously figuring out the union of wait lists for all clients it waits for. The magic
then happens whenever one of those clients become blocked on client A. Assuming client B now has to wait for A,
it will also perform a union of A's wait list (which is [A,B] at this point):
B.waitlist = [B] U [A,B]
As it performs this union, B will find itself in A's waiting list, and when it does, it has detected a deadlock.
Future work
We have at least one type of lock (SchemaLock) that can be held concurrently by several hundred transactions. It may
be worth investigating fat locks, or in any case optimize the current way SharedLock adds and removes clients from
its holder list.
The maps used by forseti should be replaced by faster concurrent maps, perhaps a striped hopscotch map or something
similar.