Skip to content

LockBox - Collection structure for managing multiple locks #5

@CMCDragonkai

Description

@CMCDragonkai

Specification

Both EFS and PK are currently using collections of locks structure. This is necessary when dealing with multiple locks, and some tricks to eliminate deadlocks.

One of the things is to have locks identified by string keys. Sorting these string keys deterministically, so that multiple locks are always locked in the same order. It's also important to filter these locks by uniqueness, so the request of the same lock twice is prevented. Thus the locks requested is always an "ordered set".

As an extension, it's also possible to introduce structured locks. This enables a more detailed fine-grained locking to avoid https://en.wikipedia.org/wiki/Giant_lock problems. And by this I mean that you can lock ['a', 'b'] and ['a'], where 'a' is on top of ['a', 'b']. Think of a prefix trie, where each element of the array identifies a level. This can be applied to nested structures for example where you may have a level identifying a file, and within each file, there are blocks. You can lock specific blocks, or you can lock the entire file. To do this you now have a vertical axis to consider as well. To prevent deadlocks, you need to sort them by shortest-prefix and remove duplicates, but also remove longer-prefixes.

For example:

lock(['a', 'b', 'c'], ['a', 'b'])

Is equivalent to:

lock(['a', 'b'])

There's no need to lock ['a', 'b', 'c'] if you're already locking ['a', 'b'].

This would require implementing a prefix trie structure, it's bit more complex, so we will add this as an extension later.

The first part is just flat LockBox based on what we already have implemented in EFS and PK.

The LockBox should be generic, or it can deal with all of the lock classes that we already have. If generic, a lockbox can only contain one specific type of locks. If heterogenous, we would need to have a type specifier on what kind of lock to use. We could do something like:

class LockBox<T> {
  public async lock(c: T)
}

const lockBox = new LockBox<Lock | RWLockWriter>;

But the exact "acquisition" of the lock resource is different. In RWLockReader, there's read and write. While Lock only has lock. You would also need to pass in the the method:

acquire: (l: T) => ResourceAcquire<T>

Along with T so that LockBox knows how to lock it.

Given that this would make the API quite verbose like:

lockBox.lock(Lock, (l) => l.lock())
lockBox.lock(RWLockWriter, (l) => l.read())

One could instead pre-program the lockbox to have the necessary utilities ahead of time and have it default.

Later DB could inherit this, or programs can combine all of these together with js-resources and js-db.

Additional context

Tasks

  1. Integrate ideas used in EFS and PK
  2. Cross-domain usage combined with withF
  3. Filter duplicate locking to avoid deadlocks
  4. Add in deadlock tests to demonstrate
  5. Make it generic to the type of lock being used with existential types

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions