-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Cancellation #64
Comments
We need to assimilate cancellable WinJS promises at work so I'll probably be working on this over the next week or two. |
@domenic, @IgorMinar, I’ve updated the description to contain a breakdown of all the discussions we’ve had on the topic. |
What does WinJS do? I thought they just made their functions take a |
This sounds exactly like the same discussion happening with Perl's https://rt.cpan.org/Public/Bug/Display.html?id=96685 Essentially the conclusion seems to be "add a ->reuse method to mark that something else is using it, so require an extra ->cancel before it counts". |
In short, Q will not implement cancellation on promises. There is an opportunity for another library to introduce an alternative primitive that requires explicit forking and has a single-producer to single-consumer binding relationship. Such a library would have the same interface as Q but the implementation would be very different, and would compromise the unidirectional communication guarantees that Q provides. Q can be used for cancellation if you set up an API to creatively pass a cancellation resolver (express capability to cancel) as an argument and use the promise internally to observe the impatience or disinterest of the consumer. https://github.com/kriskowal/gtor/blob/master/cancelation.md |
I'm curious on thoughts about wanting to use q.race to run a number of intense computations simultaneously and give up on all the ones that don't finish first: q.race(expensive op 1, expensive op 2, etc). Here once you have your answer you don't want to be wasting resources any more. How could similar patterns be implemented in a compost or way without for example w.race just canceling the incomplete promises? |
@tolmasky There is an interface that is strongly analogous to promises that I tentatively call tasks. Tasks only differ from promises in that they are unicast (meaning single-consumer and explicitly forkable) and therefore cancelable. They have the same interface but differ in behavior. The implementation of https://github.com/kriskowal/gtor/blob/master/task.js |
Cancellation is tricky.
Mark Miller argues that we should not add cancellation at all. On the flip side, most people feel the need for it, including @domenic and @IgorMinar. If cancelation is a mistake, it is an oft-made mistake.
Consider a naïve cancelable promise.
There is a hazard in this case. Either
clientA
orclientB
might callpromise.cancel()
and thus interfere with the other client’s progress. With your face close to the page, it looks like the problem is that we need a way to count how many services depend on the promise. From a higher perspective, cancelation is inherently hazard prone.The philosophical reasoning behind banning cancelation outright is that a cancelable promise must necessarily have all of the power of a deferred, nullifying the (POLA) advantage of separating the
promise
part from theresolve
part. Anyone with a cancelable can call cancel(), which is effectively equivalent to preemptively rejecting the promise (resolving the promise with a rejection).So, Mark argues, if you want something to be cancelable, just return the whole deferred to make it clear that you’re granting both powers. “Similar things should either be very different or the same” — Mark Miller attributes this paraphrase to Alan Kay.
In this example, the promise can be cancelled by rejecting the deferred.
This at least demonstrates the critical insight that, regardless of how cancelation looks, this is certainly how it should work. It does not inherently solve the problem of tracking how many parties remain interested in the result.
However, there is a story on graceful migration that this does not address. Expressing that you are no longer interested in a result is orthogonal to expressing that you are able to cancel work if no one is interested any longer in the result. I posit, it should be possible to go through either side of this extra effort independently. That is, a cancelable should have the same interface as a promise, albeit all the powers of a deferred, but also, all promises should have the interface of a cancelable, even if they do not provide that power. As such, it would be possible for a service provider to add cancelability to a promise before or after a service consumer adds support for proactively canceling promises that they no longer need.
Down this track of thought, promises would have a
cancel()
method which would be a NOOP. Acancelable
would be a new type of object entirely, that has the interface of a promise and the powers of a deferred. A cancelable would have a functioningcancel()
method. All of the promise-like methods of a cancelable would return new, normal promises, and cancelation would not implicitly propagate. Cancelables could be constructed from deferreds, like Q.cancelable(deferred).The
cancelable
solution might not stick since it would obviate chaining. Each step of the promise chain would have to be replaced with an explicit mechanism for how to forward cancelation. We’d have to explore some code samples to see whether it would be worth dealing with, and whether we might find ways to make it easy to flag common policies.For either approach, using a deferred or a cancelable, there is the independent problem of tracking how many parties are still interested in the result of a promise and actually canceling only when that drops to 0.
Ideally, we could involve the garbage collector since it does this work already. We could use a
WeakRef
, or any mechanism that notifies the application after an object has been garbage collected. We could use this mechanism to implicitly cancel any promise for which there are provably no observers. However, we do not live in a world withWeakRef
, and such would only work if the promise library were to exist in a privileged context, possibly served to unprivileged contexts.In the real world, we would have to do reference counting in application-space. One thought would be to add a
fork()
function to a cancelable. The cancelable could contain an internal counter of how many forks exist and each fork could decrement that number exactly once. When they drop to zero, the cancelable could commit to stopping work.This should be an accurate summary of every discussion I’ve had on the topic as-yet. Please chime in.
The text was updated successfully, but these errors were encountered: