-
Notifications
You must be signed in to change notification settings - Fork 0
WIP: Integrate Monitor into a @monitored context
#2
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
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
Monitorin various class methods such as in `js-quic.This creates a HOF called
monitoredas well as class method decorator called@monitored.We can imagine a class like this:
And equivalent for the
monitoredHOF.There are several decision decisions we have to make here.
LockBox<RWLock>stay? Consider that the@monitoredwould 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@monitoreddecorator can use separate share lockboxes.lockConstructor: new () => RWLock. I think this can be defaulted toRWLockWriter, in which case thelockBoxdefaults toLockBox<RWLockWriter>.Monitoralso 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.@monitoredcould imply@cancellabledue to the existence ofPromiseCancellable<void>. Furthermore, we support timers in ourMonitor.lockmethod which means actually using@monitoredcould imply@timedCancellabletoo.@timedis automatic for@monitored, what does this actually mean? Especially for calls toctx.monitor.lock()? Normally@timedprovides both actx.signalandctx.timer, however it does not do an automatic rejection like@cancellabledoes. When we are doing@monitored, we may not actually want to be able to do automatic rejection, it feels like a separate thing.@monitoredto be combined with@timedCancellable, and the order of wrapping does matter here too. Is it@monitoredfirst and then@timedCancellablesecond or the other way around? Furthermore is there efficiency gained from combining it into a single decorator like@monitoredTimedCancellableor@timedCancellableMonitored?@timedor@cancellableis combined with@monitored, then it must be that thectxmust automatically be passed into anyctx.monitor.lock()orctx.monitor.withForctx.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.lockcall. 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 fullMonitorAPI? 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.lockcall. The main reason is to align the API similar to other resources.@monitoredis 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 Monitorwithout 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()orwithMonitorForwithTransactionFsuch as in theDB. This isn't necessary for theTimerorAbortController, 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 theTimerrequires automatic cancellation...Right now this what you'd do similar to
new Timerornew 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
@monitoreddecorator 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@monitoredshould 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@timedCancellablefirst. I think the order of decorators mathematical.This should create a situation where the
@timedCancellableis the last wrapper, and returns a function that will takeContextTimedInput. This will produceContextTimedinternally. Then whenmonitoredfunction takes theContextTimed, it can make use of thectx.timerandctx.signaland autopropagate them into thectx.monitor.lock()calls.That would require:
So with the
signalandtimeroptional, 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.lockcall. 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-contextsandjs-async-cancellablejs-async-locks#21Tasks
js-contexts@monitoreddecorator andmonitoredHOF injs-contextstimeroption as we have injs-async-locks, it should maketimerusage a bit easier tooFinal checklist