matrixai / js-async-locks Goto Github PK
View Code? Open in Web Editor NEWAsynchronous locking utilities
Home Page: https://polykey.com
License: Apache License 2.0
Asynchronous locking utilities
Home Page: https://polykey.com
License: Apache License 2.0
Locking functions that have a possibility of being deadlocked should be able to take ContextTimed
contexts. Like: await lock.waitForUnlock(ctx);
.
Where ctx: { timer, signal }
.
This is where we can apply our context decorators like @timed
and @cancellable
.
This will be a major breaking API, but most users won't need to adjust, since they are not even using the timeout parameter atm.
signalPromise
utility when doing a Promise.race
with the underlying async-mutex
/**
* Promise constructed from signal
* This rejects when the signal is aborted
*/
function signalPromise(signal: AbortSignal): Promise<void> {
return new Promise<void>((_, reject) => {
if (signal.aborted) {
reject(signal.reason);
return;
}
signal.addEventListener('abort', () => {
reject(signal.reason);
});
});
}
try {
await Promise.race([this.queuingLock.waitForUnlock(), abortP]);
} catch (e) {
if (e === abortQueuingLoopReason) {
break;
} else {
throw e;
}
}
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
.
withF
If we need to be able to "retry" locking due to deadlocks MatrixAI/Polykey#294 (comment).
Then we would need to integrate the withTimeout
and tryAcquire
decorators from async-mutex
. They can be represented with parameters to our acquireRead
and acquireWrite
. But that would also mean we have to lambda-abstract them.
public read(timeout?: number): ResourceAcquire<RWLockWriter> {
// by default timeout: undefined
// but if 0, use tryAcquire
// if above 0, use withTimeout
return async () => {
// same contents as this.acquireRead()
};
}
Note that when timed out, an exception is thrown. We don't actually retry here. It is up to the user of the lock to attempt to call again, in case they need some random jitter delay.
We can keep the acquireRead
method if we want to preserve the API. Or expect users to just do this.read()
and this.write()
with the relevant timeouts.
withTimeout
and tryAcquire
from async-mutex
read
and write
methods that have a timeout
parameterwithF([rwLock.read(1000)], async ([lock]) => ...)
withRead
and withWrite
variants too as the second parameter after f
and g
for function and generator variants.A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.