-
Notifications
You must be signed in to change notification settings - Fork 337
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
Odd format for fetch callbacks #536
Comments
I don't think that's the case. What makes you think that? |
|
I guess you're asking for a more explicit link between the call to fetch and the tasks that end up being queued as a result. It seems somewhat reasonable to link them directly in the way you suggest. They're not customization points however as these steps happen on different threads/processes and are not accessible from each other. |
Ah, you have a more precise definition of "customization point" than I do. I just meant that they appear to be parameters that can be selected by the caller to customize how Fetch works, like other kinds of arguments. |
I have a somewhat similar problem. In background fetch I make fetches in parallel, but I want to read the response body as it arrives from the network. The synchronous flag is closest to what I want, but it waits for the response body before returning. "process response" is second closest, but it wants to queue a task on an event loop. I just want to process the response in prose. I may not have an event loop at this point. |
I think the callbacks such as "process response" should be algorithmic callbacks, like we did with signal abort.
The "ongoing fetch" part is a bit meh. This came up with aborting too. Ideally it should be easier to get a reference to the fetch record, and operations can be called against that. |
Maybe we should make fetch return a fetch record? And then have "synchronous fetch" wrap that or some such to just return a response? |
Yeah, I think that would make it much easier to talk about actions relating to a particular fetch. |
In retrospect, I wish fetching sync didn't wait for the body, and spec authors had to explicitly wait if needed. Is it too late to make a change like this? |
@jakearchibald you mean we would simply not offer a synchronous mode in Fetch? I think refactoring synchronous callers is reasonable still. I actually think refactoring all callers is still reasonable, as there's not that many and not all have even integrated yet to begin with. |
I think sync fetching is useful in in-parallel steps, but I'm running into cases where I want to do something like (excuse the hand-waving):
I can't do the above, as step one involves waiting for the whole body, so the aborting happens too late, and the streaming is kinda pointless. If a sync fetch returned before waiting for the body, it'd work fine. In the cases where APIs needed the whole body (like sync XHR), they could do:
|
Why can't you use the process response callback? In any event, I think if we improve the basics first, as suggested in OP, we can then build better higher-level functionality on top. |
It queues a task, and I have no reason to get back onto the main thread. In the background fetch case I may not have an event loop to queue into. |
I see. I wonder if we can untangle the task queueing somehow so that there's a lower-level hook you can use if you're already in parallel. |
Yeah, I guess you could remove the task queuing from fetch and add it to all APIs that need to be back on the main thread. |
Have you though of defining a parallel queue, and then instead of queuing tasks, queue steps on the parallel queue, and define that those steps would call into callbacks(still "in-parallel"), such as So for example the current definition of
Whereas in the background-fetch use case, the callbacks simply operate on the "local" data of the in-parallel algorithm that started the fetch, so there is no need to queue a task(so the spec could simply remove the note to this issue, and it would work "as-is" really). It would be somewhat a weird use of a parallel-queue, although it could make sense if one thinks about it as having parallel steps "register callbacks" with this parallel queue, and then fetch queuing the steps on it that would invoke those callbacks. Since data like the "request" are passed around those callbacks, and I think multiple specs could register callbacks, we'd have to be somewhat careful not to introduce race condition(for example the "transmitted bytes" member of body is written to by fetch, then read by background fetch in the callback, which is fine and could get interesting if multiple callbacks were to be registered by different specs, and some would for some reason write to the variable as well). |
It's still not entirely obvious to me what abstraction would work well here. Some thoughts:
@gterzian the reason to use a parallel queue I guess is to ensure that you do not process the response body before the response headers? It might work I suppose, but it would have to be flushed out a bit to ensure the right information is passed around to be able to queue tasks from there and such. (Tasks need a bit more information these days.) And I guess to support synchronous fetching we could use "Wait" (which we should probably define somewhere) in the main thread that looks for some variable shared with in parallel to flip. |
To be clear, I think that's problematic, as it means the CSP policy gets enforced from in parallel and it isn't clear at what point it is copied to make that deterministic. (In part this is a separate issue, but I find it useful to think about as it influences what kind of algorithms we should be offering to standards.) I guess since the callbacks take a set of steps you could indeed give them any kind of variables they might need to take hold of. It does seem like that approach might work quite well. I'd love it if one or more of @yutakahirano, @jyasskin, @jakearchibald, @domenic, and maybe @jungkees could take a look as well. (I suspect that for most standards we want to provide a wrapper algorithm that preserves the status quo more or less, whereby you invoke "fetch with tasks" or some such and it continues queuing tasks for the callback steps.) |
Yes I think the background-fetch provides a nice example of that, since it defines a DOM available method, which when called creates a bunch of "non-DOM" data, like a https://wicg.github.io/background-fetch/#background-fetch-record, and then this data holds enough information to later queue tasks back on the event-loop to communicate various progress. Also, the DOM method is called from the event-loop, and later the actual fetch is started from parallel steps started by the DOM method(similar to how DOM global fetch calls fetch from parallel steps, see step 9)
Yes, the ordering of the callbacks is something I hadn't even thought of, and I was mostly thinking about the fact that those callbacks are asynchronous also from the perspective of fetch(currently as tasks are queued, and this would be preserved if steps were enqueued on a parallel queue). And with regards to "the right information is passed around to be able to queue tasks from there", I think the bgfetch spec again provides a nice example, for example see https://wicg.github.io/background-fetch/#update-background-fetch-instances, which is called into from the So I essentially think that by defining the "fetch callbacks" as tasks, they are tied to running on an event-loop, whereas the background-fetch spec is a good example where those are rather callbacks running on other in-parallel steps(which then subsequently queue tasks, based on their own locally owned data). |
Ok, thanks for pointing that out (and sorry I deleted my comment, because I wanted to actually edit it and re-post, but your response beat me to it). |
I think this could provide a concrete example of a problem with the current approach around "process response" and similar tasks(if the issue is correct, that is): w3c/ServiceWorker#1517 |
I keep coming back to this as it's such a fundamental issue:
@jakearchibald @domenic @jyasskin @ricea @gterzian thoughts? |
Where will those callbacks run? In parallel or on the main thread? |
The callbacks mentioned in 3 would run on the main thread in tasks queued on the networking task source. That's basically what happens today, except that this provides a clearer coupling with the fetch algorithm (which also means it would not have to queue tasks if they are not provided and such). |
Hm, it's the task queuing that's caused issues for me. Eg step 11 of https://wicg.github.io/background-fetch/#complete-a-record |
Right, for in parallel callers (which I think Background Fetch is, otherwise main thread should be fine) 4 applies. Though looking at what you wrote down there you might have to go in parallel again for the fetch itself so the overall abort can still come through. In parallel doesn't support anything like callbacks as in JavaScript so you have to build that up yourself. |
Fair enough, but…
I don't see why it couldn't. It kinda feels like parallel queues already do. |
I'm not sure I understand what the suggestion is. Would the caller supply a parallel queue? Would fetch return one somehow when asked? Wouldn't that still have to run in parallel from the thread dealing with the "abort all flag" to ensure that's not blocked? Is that fundamentally different from waiting for some bits of a response to become non-null or is it just better ergonomics? |
I mean just allow prose like step 7 of https://wicg.github.io/background-fetch/#complete-a-record, but in a way that doesn't bounce back to the main thread. I think it's fine to go in parallel from parallel steps. |
That's certainly feasible, but it means that fetch itself doesn't block. That seems fine though and in fact it might be a good idea to remove the synchronous flag and have callers "wait" instead when they need something like that (and synchronous XMLHttpRequest could request the parallel queue style callbacks and wait on them on the main thread). I guess I'll first work on these "callback" parameters and figure out the possible return value with terminate operation later on. |
Everything in #536 (comment) makes general sense to me. In particular, I'd love to be able to write spec text that's like the following:
I think it'd be somewhat tempting to be able to write text like the following too:
but on balance I think this is inadvisable, as then you can easily get into trouble if you accidentally use "return" or "throw" from those non-idented steps. I'm thinking how this (indented) pattern would look if we carried it over to https://html.spec.whatwg.org/#fetching-scripts, and I think it would be reasonable. We'd define an explicit callback parameter (e.g. "process the module script") for those, and thread everything through with some extra indenting, instead of using the current "Asynchronously complete this algorithm with X". The most complex it would get would be in https://html.spec.whatwg.org/#fetch-the-descendants-of-a-module-script step 8, which could be formalized nicely with infrastructure like this, at the cost of some complexity. (It could also allow us to explicitly terminate ongoing fetches if one of the fetches fail.) On an editorial side, for some reason callbacks namedLikeThis seem ickier, so if we made them real optional paramters I'm not sure I'd want to update their names, but that doesn't matter much.
Exposing a byte sequence seems critical. I think getting the byte sequence should consume the body though, so this would be a byte sequence variant of the spec's current "consume body" algorithm. It could still operate on streams, with relevant asserts. I guess it would need to be called from the main thread, so it'd be called from "process response" hooks?
What would this do besides expose terminate? How would one use it? |
I haven't made progress on the body question yet. I don't have real uses for the return value at this point other than exposing terminate, which I think is a plus as it's a shared handle between the fetch algorithm and the caller, which is a much clearer interface than what we have now. (I was thinking we could put other things there too, such as the response or response body as byte sequence, but having worked on this refactor I think I'd like to wait a little bit before adding multiple ways to get at things.) |
This makes some rather big changes: * Requests no longer have a synchronous flag. Blocking a thread is now up to the caller. * Fetch therefore no longer returns a response directly. In parallel callers will need to pass in "callbacks" that are either invoked on a parallel queue or on an event loop. * To hold onto these callbacks as well as some other information a fetch params struct is now passed around the various fetch algorithms. This will allow for cleanup around termination and aborting in the future. Potentially some bookkeeping state from request can move there going forward. * There's a dedicated navigate-redirect fetch algorithm for HTML as the HTTP-redirect fetch algorithm now wants a fetch params instance. * Some allowance for aborting early on in fetch was removed as all that is run synchronously with the code that invoked fetch to begin with. Closes #1164. * Algorithms that needed to be adjusted were changed to use the new Infra patterns for parameters. I also tried to improve naming, e.g., makeCORSPreflight rather than CORS-preflight flag. * "process response done" was removed as it seemed redundant with "response response end-of-body"; possible leftover from trailers. * Removed duplicate task queueing at the end of main fetch for request's body. Fixes #536.
This makes some rather big changes: * Requests no longer have a synchronous flag. Blocking a thread is now up to the caller. * Fetch therefore no longer returns a response directly. In parallel callers will need to pass in "callbacks" that are either invoked on a parallel queue or on an event loop. * To hold onto these callbacks as well as some other information a fetch params struct is now passed around the various fetch algorithms. This will allow for cleanup around termination and aborting in the future. Potentially some bookkeeping state from request can move there going forward. * There's a dedicated navigate-redirect fetch algorithm for HTML as the HTTP-redirect fetch algorithm now wants a fetch params instance. This also returns a response which fixes a problem where HTTP-redirect fetch would return a network error that was then ignored. * Some allowance for aborting early on in fetch was removed as all that is run synchronously with the code that invoked fetch to begin with. Closes #1164. * Algorithms that needed to be adjusted were changed to use the new Infra patterns for parameters. I also tried to improve naming, e.g., makeCORSPreflight rather than CORS-preflight flag. * "process response done" was removed as it seemed redundant with "response response end-of-body"; possible leftover from trailers. * Removed duplicate task queuing at the end of main fetch for request's body. Fixes #536.
https://fetch.spec.whatwg.org/#fetch-method has:
"process response" and "process response done" are defined as "Tasks that are queued by this standard are annotated as one of: ...", but they actually appear to be customization points within the Fetch algorithm for which the caller can provide an implementation. Assuming that's right, then:
The text was updated successfully, but these errors were encountered: