-
Notifications
You must be signed in to change notification settings - Fork 0
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
WIP: Integrate Monitor
into a @monitored
context
#2
base: staging
Are you sure you want to change the base?
Conversation
This brings the 4.0.0 of |
Note that having this eventually means that |
This creates |
@tegefaulkes I think given the complexity of this problem, I'm thinking that js-quic should just move ahead with directly using the It would work the same as how It does mean calls should take the
Like we currently do with Then when this is perfected, we can migrate towards that in the future. |
We will leave this till later. We only need to cherry pick the changes that enable a literal timer into staging and release a new |
d308a41
to
16c1549
Compare
Literal timer logic extracted out, will be merged in #3. This has been rebased on top of staging. |
Some design notes regarding transaction decorator syntax too that is related to how we expect monitors to work: MatrixAI/js-db#51 |
Staging now has the It's a little inefficient though to always generating a new monitor on every root of mutually exclusive callgraphs. I'm not sure if this really can be solved right now, one way might be to optimise the creation of the monitor as much as possible, since they need to be independent in order to enforce mutual exclusion. The selection of the key should really be a constant though @tegefaulkes, rather than a instance property. Such keys could be used in the |
Description
This will make it easier to use the
Monitor
in various class methods such as in `js-quic.This creates a HOF called
monitored
as well as class method decorator called@monitored
.We can imagine a class like this:
And equivalent for the
monitored
HOF.There are several decision decisions we have to make here.
LockBox<RWLock>
stay? Consider that the@monitored
would require the ability to point to a shared lock box object. We can imagine a few places where it can exist. The first place would as a symbol property of the instantiated class. This is doable just through an additional class decorator, but it is also possible to lazily instantiate this just through the@monitored()
decorator call. Furthermore it should also be possible to inject a lock box at that call in case the@monitored
decorator can use separate share lockboxes.lockConstructor: new () => RWLock
. I think this can be defaulted toRWLockWriter
, in which case thelockBox
defaults toLockBox<RWLockWriter>
.Monitor
also doesn't specify it, and it does slow down the monitor usage, and it's best used during prototyping and testing, and once it's confirmed that no deadlocks are possible, it can be dropped from the production code.@monitored
could imply@cancellable
due to the existence ofPromiseCancellable<void>
. Furthermore, we support timers in ourMonitor.lock
method which means actually using@monitored
could imply@timedCancellable
too.@timed
is automatic for@monitored
, what does this actually mean? Especially for calls toctx.monitor.lock()
? Normally@timed
provides both actx.signal
andctx.timer
, however it does not do an automatic rejection like@cancellable
does. When we are doing@monitored
, we may not actually want to be able to do automatic rejection, it feels like a separate thing.@monitored
to be combined with@timedCancellable
, and the order of wrapping does matter here too. Is it@monitored
first and then@timedCancellable
second or the other way around? Furthermore is there efficiency gained from combining it into a single decorator like@monitoredTimedCancellable
or@timedCancellableMonitored
?@timed
or@cancellable
is combined with@monitored
, then it must be that thectx
must automatically be passed into anyctx.monitor.lock()
orctx.monitor.withF
orctx.monitor.waitForUnlock
... etc calls. This is because there's an expectation that ctx is passed down the call graph, but one could easily forget to do this when using thectx.monitor.lock
call. And it just seems that if I wanted to either time out the entire operation, then it should time out any locking operations, and if I want to cancel the whole call, then it also makes sense that I would want to cancel any locking operations.ctx.monitor
, is the fullMonitor
API? If so, then one has to actually doawait ctx.monitor.lock('a')()
or use it as part of thewithF(ctx.monitor.lock('a'), async () => { ... }
. This isn't that bad, but it does require a little more effort then theDBTransaction.lock
call. The main reason is to align the API similar to other resources.@monitored
is used, there needs to be a way to "construct" a monitor from scratch. For example in@timed
, one can create anew Timer
, and for@cancellable
, one can create anew AbortController
, but one cannot create anew Monitor
without being able to refer to the shared lock box some how. I think if the lock box was exposed through a symbol property likeobj[lockBox]
, then it would be possible to construct a new monitor likeobj.method({ monitor: new Monitor(obj[lockBox], obj[lockConstructor], obj[locksPending]) }
. The only issue here is that there's automatic destruction of the monitor, if you do this, you need to close the monitor somehow at the end withawait monitor.unlockAll()
. This is usually why we have a convenience function calledwithF()
orwithMonitorF
orwithTransactionF
such as in theDB
. This isn't necessary for theTimer
orAbortController
, because they can be garbage collected, but now I realise it does actually make sense to do so... to get around this it may make sense for the user to provide a method to create these resources and then combine it withwithF
. Even theTimer
requires automatic cancellation...Right now this what you'd do similar to
new Timer
ornew AbortController
.Alternatively:
Alternatively:
To prevent nameclashes, you'd have to use a symbol method:
Note that this pattern is called a "mix-in" pattern. We are mixing-in the ability to create specific resources and bracketing methods for the object. This is primarily useful when we expect the "unit of atomicity" to be any method with the
@monitored
decorator applied. However if the unit of atomicity is expanded beyond one single class, then you wouldn't want to use those mixins, since they imply a single-class unit. This is why@monitored
should not automatically mixin those methods. There should also be a class decorator that supports the mixins if you want.It might look like:
All of it does add a bit of verbosity though.
So if we were to combine
@timedCancellable
, it seems that it would make sense to decorate@timedCancellable
first. I think the order of decorators mathematical.This should create a situation where the
@timedCancellable
is the last wrapper, and returns a function that will takeContextTimedInput
. This will produceContextTimed
internally. Then whenmonitored
function takes theContextTimed
, it can make use of thectx.timer
andctx.signal
and autopropagate them into thectx.monitor.lock()
calls.That would require:
So with the
signal
andtimer
optional, the idea is that these will be auto sent in.Alternatively we can not bother with automatic injection, less magic, and just expect the user to pass the ctx into
ctx.monitor.lock
call. Something like:Of course we should also upgrade our existing
setup*
functions to support the literal timer as a number as we have done so injs-async-locks
.There's quite a few things here to explore...
Issues Fixed
js-contexts
andjs-async-cancellable
js-async-locks#21Tasks
js-contexts
@monitored
decorator andmonitored
HOF injs-contexts
timer
option as we have injs-async-locks
, it should maketimer
usage a bit easier tooFinal checklist