-
Notifications
You must be signed in to change notification settings - Fork 0
Create Monitor to support js-contexts
@monitored
decorator
#26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Should the Right now The |
As a minimal prototype, I've extracted the logic out of However it of course doesn't fit the We shall see. |
So it turns out that
So we have an opportunity to either bring in the I think in this case it makes more sense for |
I integrated The problem is that currently due to DirtyHairy/async-mutex#64, when we attempt to race between That means in this example: const lock = new Lock();
const [release] = await lock.lock()();
// Ok now so this should fail after 500 ms
try {
const [release2] = await lock.lock({
timer: new Timer({ delay: 500 })
})();
} catch (e) {
console.log('TIMED OUT', e instanceof errors.ErrorAsyncLocksTimeout);
}
await release();
console.log(lock.isLocked()); We will end up with A proper solution is to ensure that we can cancel just a specific pending lock. If we use Because of issue 63, we may have to reimplement our own async-mutex underneath. It shouldn't be too difficult since we did most of the work already. |
A quick hack is to add this:
To immediately chain the release when it does acquire it. But that seems redundant, and also I'm not sure how that would work with the Plus it's probably not that efficient. |
Another thing is that I should change |
Ok so basically we're going need to build out the async-mutex with the ability to cancel a specific one. |
Ok so everything is actually built on top of a semaphore construct. In the In which case I have reimplemented the async mutex's semaphore inside It's actually improved in the sense that it supports "weighted semaphores", I had look at the current Not only that, it now supports prioritised weighted semaphores. This means that by default the priority is set to false. Which means it biases towards fairness where a if there is a semaphore of limit 3 with an existing lock of weight 1, if locking attempts respectively try to lock a weight of 3 and 2, the 3 will block the 2. If the So the semaphore now:
All existing tests pass, and I did add a few new tests into the semaphore to try it out. Now we can proceed to adapt It's important to also test whether we still need
Primarily because the releasing functions is actually just synchronous now. But I remember there was some ordering problems with Oh and I had to get 1.1.1 of |
The addition of weights means |
Everything is built on top of the |
I've fixed up a bug in semaphore. Basically both lockers and waiters can use the same queue, it doesn't require separate queues unlike what chatgpt said. This keeps everything ordered properly and fixed the Additionally for |
Removed micro yields, so far everything is working. I'm noting the fact that I didn't await on the releasing functions in |
The |
Just realised that I could have just wrapped all the async await in a |
The So right now we're having quite a flexible bunch of parameters that has to be propagated. |
Looks like all existing concurrency control mechanisms have been ported over and now support cancellability. Now it's time to get back to the |
I've had to change the API for
|
Monitor due to wrapping the Lockbox behaves differently when we synchronously attempt to lock a key then ask it whether that key is locked. The problem is because Monitor wraps lockbox and its own In order to maintain consistent behaviour, the solution is to extend the Something like: protected _locks: Map<
string,
{ status: 'acquiring', type: 'read' | 'write' } |
{ status: 'acquired', type: 'read' | 'write'; lock: RWLock; release: ResourceRelease; }
> = new Map(); We can then use An important realisation is that At the same time, it is important that the pending lock being set up above must happen after the This ensures that by the time the pending lock is setup in |
The deadlock detection system is working. I'm putting the last test cases for the |
Ok all deadlock tests pass now. The deadlock detection revealed a very tricky race condition when using Basically, when creating derived promises, Usually it is simple "chain" the signal cancellation. For example this is the public lock(ctx?: Partial<ContextTimedInput>): ResourceAcquireCancellable<Lock> {
const acquire = this.semaphore.lock(1, ctx);
return () => {
const acquireP = acquire();
return acquireP.then(
([release]) => [release, this],
undefined,
(signal) => {
// Propagate cancellation to `acquireP`
signal.addEventListener(
'abort',
() => {
acquireP.cancel(signal.reason);
},
{ once: true }
);
}
);
};
}; This works since it can be guaranteed that However in other places I'm using a pattern where I create a Imagine instead the above function had to wait for 2 promises, 2 separate blocking operations that may require cancellation. This is what happened in In this case, a possible race condition occurs when you call The solution to this problem is to not do this. It's not a good idea basically. Instead one should re-use the So the only time we should use the pattern of I've added tests to both |
Beware of the above @amydevs @tegefaulkes when using the |
This is ready to be squashed. |
e7cdfc3
to
4b91c7e
Compare
js-contexts
@monitored
decoratorjs-contexts
@monitored
decorator
… timers * replaced `async-mutex` with native `Semaphore` * integrated `PromiseCancellable` * integrated `ContextTimerInput` allow timers and abortion * created `Monitor` which builds on top of `LockBox` to provide PCC transactions * changed to using `swc` for testing
4b91c7e
to
61482a6
Compare
Had to update to 1.1.1 for js-timer as there was a bug there. That needs to propagate to all other libraries @tegefaulkes @amydevs |
Description
I want to create a
Monitor
class that resembles howDBTransaction
works by creating a "re-entrant" object that can be used to lock things.Combined with a
@monitored
decorator injs-contexts
, we can enableContextMonitored
that enables declarative mutual exclusion contexts for arbitrary concurrent state mutation.This can be useful for
js-quic
and other projects which require concurrency control but don't have any use ofjs-db
which is the only thing that provides this construct.This is a proper realisation of the
js-async-monitor
idea first explained here: MatrixAI/js-async-monitor#1 and obviates that project, since you wouldn't need it.Issues Fixed
js-contexts
andjs-async-cancellable
#21Tasks
Monitor
class that resemblesDBTransaction
- 0.5 dayContextTimedInput
toLock
ContextTimedInput
toSemaphore
ContextTimedInput
toBarrier
ContextTimedInput
toRWLockWriter
ContextTimedInput
toRWLockReader
ContextTimedInput
toLockBox
Monitor
similar toLockBox
- 0.5 dayMonitor
deadlock detection, cancellation and time expiry - 0.5 dayMinimal testing will be done for
RWLockReader
as this is not something we use in PK, but it was there for completeness.These 2 tasks will be part of
js-contexts
:js-contexts
@monitored
decorator andmonitored
HOF injs-contexts
Should go into the
js-contexts
PR.Final checklist