-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Cancelable requests AKA Context #2884
Comments
Discussion around cancellation in the promise community: |
Random experiment. it's not correct, just a draft. https://gist.github.com/jbenet/11671be17cbe91c67a00d9638d99a45d |
@diasdavid I can start doing some research and planning here, but I don't think it is realistic we can keep this in milestone 2 |
Expanding on this a bit... The main issue comes from the What this means, is that from the standpoint of the js-ipfs core caller, we can have: const doOperationP = new Promise((resolve, reject) => {
// do whatever it is supposed to do and call resolve when it is done
})
let cancel
const cancelP = new Promise((resolve, reject) => {
cancel = () => {
// Do the operations to rewind the operation
reject()
}
})
const timeoutP = new Promise((resolve, reject) => {
setTimeout(reject, constants.TIMEOUT)
})
Promise.race([
doOpP,
cancelP,
timeoutP
]) This way, only one of the operations is called, keeping the control within outside to compose what we need that fits our expectations. For an The way that we share this state is another rabbit hole, as it can be just a shared object/array, a queue or probably something like Go or Closure, a channel, so that when it is time to cancel, we have a clean perspective of what CIDs need to be canceled, this would inject yet another abstraction (not sure if we want to go there necessarily). Nevertheless, I also want to point out that we don't even need to use promises, fortunately (or apparently), everything you can do with Promises, you could do before just by using our favorite and already present throughout the codebase, async library, async. |
My 5 cents on this discussion: Since there's a lot of shared state that you may want to roll back, these contexts look a lot like traditional transactions, where for every action there is an "undo" action. Implementing these may not be trivial. For instance, even for canceling Nesting: like when canceling a About timeouts: I believe that it should be the client to decide about the timeout and cancel the action itself. This may mean extending the action interface all the way to the client, for them to be able to cancel an action (probably through a control channel if done remotely, for instance, through HTTP), which means that actions must immediately return (like a promise) and provide an action unique ID.. |
Had the realization the other day that probably what we need is just to add http://npmjs.com/continuation-local-storage. Need to investigate further :) |
@diasdavid should this maybe be part of OKRs? Currently there is (AFAIK) no way of doing timeouts in js-land... |
Any update here? I've been hearing people really would like to have nice timeouts in js-ipfs stuff. |
I've been playing with using As part of the async iterators endevour I proposed https://github.com/libp2p/interface-peer-discovery/issues/2 which uses |
ooh ipfsx looks very interesting, good to learn about it! would love to see it surfaced more |
What we really need is error returns, since the async / distributed nature of IPFS makes that (I understand) impossible, a timeout is an alternative, the how (from the app developer perspective) is less important, but the user experience of sitting waiting for something that never returns because for example the hash is invalid is really bad. We've implemented timeouts at the app level for fetch's, but the challenge (at the app level) is that a reasonable timeout for a small file, is too short for a long file, so IPFS is pretty much always going to be timed-out on anything that takes more than a few seconds to retrieve. For streams its much worse - since the jsipfs.files.catReadableStream call always succeeds, but on a bad, or unfindable, hash no data is received. This - along with a high error rate finding even valid hashes - was why we had to turn IPFS streams off for video & audio in the dweb.archive.org app, there were just too many cases of people sitting there waiting for the IPFS stream to start delivering the movie, when if there had been an error return we could have fallen back to HTTP. We switched the streams to WebTorrent because that does a graceful HTTP fallback. |
If you want a good example of why this is important ... take a look at https://dweb.me/arc/archive.org/details/etree and watch the console. Recently IPFS lost a lot of our data again go-ipfs#5765 and on this page you can see the result of each icon hanging during load - the createReadStream succeeds so its assumed IPFS will work, and the UI doesn't fall back to fetch over HTTP so the page looks awful if IPFS is enabled. (With IPFS turned off it looks like https://dweb.me/arc/archive.org/details/etree?paused=IPFS ) |
@alanshaw, @achingbrain, @vmx, @jacobheun & @vasco-santos, given that this is a |
Yes, I think #1670 should be a |
In regards to IPLD, that one is covered by the new APIs work. Those APIs will use async/await. |
@daviddias 👍 #1670 is already a P0 on the JS IPFS roadmap (search "Revamped APIs") and we have a P1 for working on it this quarter (behind the P0 of completing base32 v1 CIDs) - HTH |
One thing it is still not answered fully is how AbortController and the Async Iterators refactor will provide a way to cancel an action that spawned multiple actions, e.g.: When catting a file, Unixfs will ask for the blocks, Bitswap will create mutiple events to fetch its blocks and the DHT will create multiple crawls to find the nodes that then it has to connect to fetch the file. Aborting a request isn't just about saying "don't give me back the result because I don't want it", it is about being able to cancel all of this initiated actions that will be consuming memory, CPU and bandwidth. For contrast, the AbortController puts the responsibility on the Browser engine to "clean out" all HTTP requests, TCP sockets and so on, once a request is canceled. So far, I think the best approach is what go-ipfs does with Context, which the parallel in JS world is https://www.npmjs.com/package/continuation-local-storage, but I haven't seen it being used with async iterators. |
I think the approach is similar - the // in the application
const controller = new AbortController()
setTimeout(() => controller.abort(), 100)
try {
const res = await ipfs.cat('Qmfoo', {
signal: controller.signal
})
} catch (err) {
if (err instanceof AbortError) {
// request was aborted
}
} //.. meanwhile, in the unixfs exporter
for (let i = 0; i < node.Links.length; i++) {
// potentially a long running network operation
const child = await ipld.get(node.Links[i].Hash)
// were we aborted while fetching the next node?
if (options.signal.aborted) {
throw new AbortError()
}
// otherwise continue as normal
} |
Yes exactly this, the for reference: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal |
We need a way to be able to cancel calls that have to hit the network, either through a timeout or by a voluntary cancelation.
JavaScript doesn't provide a good interface for this natively, we need to research or build one our own.
For context, read https://blog.golang.org/context which is what we use in Go. (Another resource - http://bouk.co/blog/context/)
Notes from the sprint discussion:
The text was updated successfully, but these errors were encountered: