promises-aplus / cancellation-spec

Discussion and drafts of a possible promise cancellation spec.
24 stars 5 forks source link

Interrupts #3

Open novemberborn opened 11 years ago

novemberborn commented 11 years ago

Quotes come from #1.

[@domenic] The main problem with cancellation that kriskowal and I have considered is that cancelling provides a communication channel going from the promise to its resolver, representing a capability leak. Before, you could hand out promises to mutually-suspicious parties. With a cancelable promise, each party can affect the other party's operation, which is bad.

I find this communication channel super useful, not just to signal "cancel me" but to actually send a message to the resolver, e.g. "the inbound request socket was closed".

[@skaegi] I sort of agree with the idea that canceling is like exception handling with similar unwinding the stack considerations for promise chains, but I think one difference is that the user initiating cancel is outside the stack. I'm not sure what the synchronous analog for that is? Thread.interrupt? [snip] It might be better if the underlying Deferred could optionally choose to react to the cancel request but that might just be an implementation detail.

I've been prototyping interrupts in Legendary, see the examples in https://github.com/novemberborn/legendary/tree/interrupts. This lets you send messages to a resolver which can then decide to ignore the interrupt, or fulfill or reject the promise. It lets you implement cancelation tokens, for instance.

Thoughts?

ForbesLindesay commented 11 years ago

You don't get one of the advantages of cancellation. You can't know that a promise will never be resolved after you cancel it if their optional, which means you have to look at the individual api you're calling to establish what the cancellation behaviour is and how prompt it is (current turn vs. next turn)

novemberborn commented 11 years ago

True, although it does solve the cancellation propagation problem by providing an official communication channel from promise to resolver without affecting the state of the promises along the chain.

Perhaps interrupts should be combined with a non-propagating .cancel() interface.

ForbesLindesay commented 11 years ago

A non-propagating cancel interface is pretty useless. I can do that myself at the moment just by extending my promise to have a .abort() method. If we're not going to propagate the cancellation then we're not adding anything worth specifying. Also, say I have a method which supports cancellation:

function get(url) {
  // return a promise that is resolved with the text body of
  // the response
}

Then I want to mutate the response in some way:

function getJSON(url) {
  return get(url).then(function (data) { return JSON.parse(data); });
}

If I don't care about cancellation then that's fine, if cancellation propagates that's fine, if cancellation doesn't propagate then I've lost the ability to cancel the underlying web request.

If cancellation doesn't propagate, people will still write code like that all the time, because most of the time you don't need cancellation, but then the person who does need cancellation can't use any of the nice libraries, because none of them will propagate that cancellation to the underlying asynchronous operation.

ForbesLindesay commented 11 years ago

Now in terms of why we need to get cancellation propagation correct:

function get(url) {
  // return a promise that is resolved with the text body of
  // the response.  reject if server returned status code other
  // than 200
}

function getWithFallback(url, fallback) {
  return get(url)
    .then(undefined, function (err) {
      return get(fallback); //oh dear
    });
}

If cancellation propagation still rejects and we cancel a getWithFallback promise then we'll cancel the first request, but make the second request to the fall-back URL. In the best case we just waste resources, in the worst case we said "if anything goes wrong, assume the worst and self destruct, blowing up the nuclear facility so it can't fall into the wrong hands" at which point our incorrect cancellation propagation blew up a nuke. (sorry, I'm getting a little melodramatic but you get the idea).

novemberborn commented 11 years ago

Yes. I've written substantial amounts of code using promises for concurrency control, which also uses cancelation. I've found that my primary use cases are to communicate with the resolver, not so much canceling a promise, though because the communication channel is implemented via cancel() that is a necessary side-effect. (See Dojo or my discontinued Promised-IO attempt.)

This experiment supports side-effect free communication with the resolver, which is rather interesting but doesn't necessarily have to be part of any spec.

In your example the onRejected handler is needlessly naive, assuming any error necessitates the fallback. It should either test what type of error was received (is its .name OperationCancelled?), or if that's not acceptable we need a different handler for when the promise transitions to a canceled state. Anyway, let's discuss such subtleties in #2.

ForbesLindesay commented 11 years ago

You're missing the point, consider the following:

function getPromised(url) {
  return get(url)
    .then(undefined, function (err) {
      log(err);
      throw err;
    });
}

It shouldn't need to do anything special to determine what type of error was thrown. It shouldn't need to do anything special to deal with cancellation because it doesn't do anything with cancellation. Cancellation should only need to be considered by the PromiseLibraries/Implementations, the person actually initiating/using cancellation and the person managing the underlying IO operation.

domenic commented 11 years ago

+1 to @ForbesLindesay's point; you cannot have such trivial transformations be subject to dealing with an extension like cancellation.