W3C home > Mailing lists > Public > public-script-coord@w3.org > January to March 2015

Re: Cancellation architectural observations

From: Marius Gundersen <gundersen@gmail.com>
Date: Mon, 2 Mar 2015 12:26:04 +0100
Message-ID: <CAE-f-gcPWJgP6ziv6ea20qZ+wBCNmWOmGo9Ac8ozQn8ZSbmMtA@mail.gmail.com>
To: Salvador de la Puente González <salva@unoyunodiez.com>
Cc: Dean Tribble <tribble@e-dean.com>, public-script-coord@w3.org, es-discuss <es-discuss@mozilla.org>
I think a better term than cancel would be ignore. If the promise has side
effects then it is impossible for a cancel method to guarantee that the
side effects do not happen. For example a POST request to the server that
updates the server state can be cancelled, but it is not possible to know
if it happens before or after the server has updated its state. It is
therefore dangerous to think of it as cancelling. A better concept is to
ignore the results of the promise. Even if the promise has been completed,
the results can be ignored. Depending on what job the promise is doing it
can decide if it wants to stop the task or not. A simple async task might
not need to have a way to be cancelled; the result of the task can just be
ignored. But a long running task that is ignored might decide to stop what
it is doing to save processing power/battery.

```js
//this promise has a way to stop what it is doing
var p1 = new Promise(function(resolve, reject, ignored){
  var task = setTimeout(resolve.bind(null, "the result"), 1000);
  ignored(() => clearTimeout(task));
});

//ignore the result from the above promise, triggers the ignored listener
p1.ignore();
p1.then(result => "this is never called");

//this promise cannot be cancelled, but the result can be ignored
var p2 = Promise.resolve("the result");

//the callback is not called until next event loop cycle
p2.then(result => "this is never called");

//the result is ignored, the above callback is never run
p2.ignore();
```

Maybe calling ignore on a rejected or resolved promise should throw.

Marius Gundersen

On Mon, Mar 2, 2015 at 8:59 AM, Salvador de la Puente González <
salva@unoyunodiez.com> wrote:

> I think this did not reach the mailing list to the W3:
> https://esdiscuss.org/topic/cancelable-promises#content-9
>
> Actually the result cancellation is different from promise cancellation.
> First is full of implementation details, thus you are talking about smart
> cancellation. It is not smart, is the only way to provide control, thus,
> the cancellationToken.
>
> In the other side you're talking about "don't care" what is actually
> related with the flow control. It turns out, after a timeout, you are no
> longer interested in the result so you don't use it because you don't care.
>
> I insist, two concepts: one for cancelling a control flow, another for an
> specific operation of the implementation.
> El 02/03/2015 08:06, "Dean Tribble" <tribble@e-dean.com> escribió:
>
>> Another thread here brought up the challenge of supporting cancellation
>> in an async environment. I spent some time on that particular challenge a
>> few years ago, and it turned out to be bigger and more interesting than it
>> appeared on the surface. In the another thread, Ron Buckton pointed at the
>> .Net approach and it's use in JavaScript:
>>
>>
>>> AsyncJS (http://github.com/rbuckton/asyncjs) uses a separate
>>> abstraction for cancellation based on the .NET
>>> CancellationTokenSource/CancellationToken types. You can find more
>>> information about this abstraction in the MSDN documentation here:
>>> https://msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
>>>
>>
>> It's great that asyncjs already has started using it. I was surprised at
>> how well the cancellationToken approach worked in both small applications
>> and when extended to a very large async system. I'll summarize some of the
>> architectural observations, especially from extending it to async:
>>
>> *Cancel requests, not results*
>> Promises are like object references for async; any particular promise
>> might be returned or passed to more than one client. Usually, programmers
>> would be surprised if a returned or passed in reference just got ripped out
>> from under them *by another client*. this is especially obvious when
>> considering a library that gets a promise passed into it. Using "cancel" on
>> the promise is like having delete on object references; it's dangerous to
>> use, and unreliable to have used by others.
>>
>> *Cancellation is heterogeneous*
>> It can be misleading to think about canceling a single activity. In most
>> systems, when cancellation happens, many unrelated tasks may need to be
>> cancelled for the same reason. For example, if a user hits a stop button on
>> a large incremental query after they see the first few results, what should
>> happen?
>>
>>    - the async fetch of more query results should be terminated and the
>>    connection closed
>>    - background computation to process the remote results into
>>    renderable form should be stopped
>>    - rendering of not-yet rendered content should be stopped. this might
>>    include retrieval of secondary content for the items no longer of interest
>>    (e.g., album covers for the songs found by a complicated content search)
>>    - the animation of "loading more" should be stopped, and should be
>>    replaced with "user cancelled"
>>    - etc.
>>
>> Some of these are different levels of abstraction, and for any
>> non-trivial application, there isn't a single piece of code that can know
>> to terminate all these activities. This kind of system also requires that
>> cancellation support is consistent across many very different types of
>> components. But if each activity takes a cancellationToken, in the above
>> example, they just get passed the one that would be cancelled if the user
>> hits stop and the right thing happens.
>>
>> *Cancellation should be smart*
>> Libraries can and should be smart about how they cancel. In the case of
>> an async query, once the result of a query from the server has come back,
>> it may make sense to finish parsing and caching it rather than just
>> reflexively discarding it. In the case of a brokerage system, for example,
>> the round trip to the servers to get recent data is the expensive part.
>> Once that's been kicked off and a result is coming back, having it
>> available in a local cache in case the user asks again is efficient. If the
>> application spawned another worker, it may be more efficient to let the
>> worker complete (so that you can reuse it) rather than abruptly terminate
>> it (requiring discarding of the running worker and cached state).
>>
>> *Cancellation is a race*
>> In an async system, new activities may be getting continuously scheduled
>> by asks that are themselves scheduled but not currently running. The act of
>> cancelling needs to run in this environment. When cancel starts, you can
>> think of it as a signal racing out to catch up with all the computations
>> launched to achieve the now-cancelled objective. Some of those may choose
>> to complete (see the caching example above). Some may potentially keep
>> launching more work before that work itself gets signaled (yeah it's a bug
>> but people write buggy code). In an async system, cancellation is not
>> prompt. Thus, it's infeasible to ask "has cancellation finished?" because
>> that's not a well defined state. Indeed, there can be code scheduled that
>> should and does not get cancelled (e.g., the result processor for a pub/sub
>> system), but that schedules work that will be cancelled (parse the
>> publication of an update to the now-cancelled query).
>>
>> *Cancellation is "don't care"*
>> Because smart cancellation sometimes doesn't stop anything and in an
>> async environment, cancellation is racing with progress, it is at most
>> "best efforts". When a set of computations are cancelled, the party
>> canceling the activities is saying "I no longer care whether this
>> completes". That is importantly different from saying "I want to prevent
>> this from completing". The former is broadly usable resource reduction. The
>> latter is only usefully achieved in systems with expensive engineering
>> around atomicity and transactions. It was amazing how much simpler
>> cancellation logic becomes when it's "don't care".
>>
>> *Cancellation requires separation of concerns*
>> In the pattern where more than one thing gets cancelled, the source of
>> the cancellation is rarely one of the things to be cancelled. It would be a
>> surprise if a library called for a cancellable activity (load this image)
>> cancelled an unrelated server query just because they cared about the same
>> cancellation event. I find it interesting that the separation between
>> cancellation token and cancellation source mirrors that separation between
>> a promise and it's resolver.
>>
>> *Cancellation recovery is transient*
>> As a task progresses, the cleanup action may change. In the example
>> above, if the data table requests more results upon scrolling, it's
>> cancellation behavior when there's an outstanding query for more data is
>> likely to be quite different than when it's got everything it needs
>> displayed for the current page. That's the reason why the "register" method
>> returns a capability to unregister the action.
>>
>>
>> I don't want to derail the other threads on the topic, but thought it
>> useful to start articulating some of the architectural background for a
>> consistent async cancellation architecture.
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss@mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
> _______________________________________________
> es-discuss mailing list
> es-discuss@mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
Received on Thursday, 5 March 2015 12:17:18 UTC

This archive was generated by hypermail 2.3.1 : Thursday, 5 March 2015 12:17:26 UTC