Skip to content
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

Handing fetch termination #1178

Merged
merged 3 commits into from
Nov 16, 2017
Merged

Handing fetch termination #1178

merged 3 commits into from
Nov 16, 2017

Conversation

jakearchibald
Copy link
Contributor

@jakearchibald jakearchibald commented Aug 7, 2017

Is this a valid way of handling this, in terms of whatwg/fetch#523.

It seems too easy.

(I'll add the same to foreign fetch if this is 👍 )


Preview | Diff

docs/index.bs Outdated
@@ -2996,6 +2997,9 @@ spec: webappsec-referrer-policy; urlPrefix: https://w3c.github.io/webappsec-refe
1. If |e|'s [=FetchEvent/respond-with error flag=] is set, set |handleFetchFailed| to true.
1. Else, set |response| to |e|'s [=FetchEvent/potential response=].
1. If |e|'s <a>canceled flag</a> is set, set |eventCanceled| to true.
1. Run the following steps [=in parallel=]:
1. Wait for the fetch to become [=fetch/terminated=].
1. [=Queue a task=] to [=AbortSignal/signal abort=] on |requestObject|'s [=Request/signal=].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this algorithm has a chance to never finish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I figured "GC" would take care of it.

What's the alternative? "Wait for the fetch to become [=fetch/terminated=], or (something that signal successful completion)"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to think about this a bit and the way XMLHttpRequest seems to approach is that you always get a response from the fetch process, but it might be a network error annotated with a termination reason. And for the request/response bodies we got the stream that errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could wait until on of the following:

  • Fetch is terminated.
  • "Process response" for fetch occurs, and the response type is "error".
  • "Process response done" for fetch occurs.

Then, only signal abort if fetch is terminated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I was saying is that currently "Fetch is terminated." is not really a signal we expose to the outside (other than the response having a termination reason or the stream erroring).

annevk pushed a commit to whatwg/fetch that referenced this pull request Sep 20, 2017
This makes the following changes:

* Integrates DOM's `AbortSignal`.
* Specifies abort points and how to handle termination.
* Replaces termination reason with an aborted flag.

Tests: web-platform-tests/wpt#6484.

Service worker integration follow-up: w3c/ServiceWorker#1178.

Fixes #447. Closes #563.
@jakearchibald jakearchibald changed the title WIP: Handing fetch termination Sep 28, 2017
@jakearchibald
Copy link
Contributor Author

jakearchibald commented Sep 28, 2017

@annevk re:

"Fetch is terminated." is not really a signal we expose to the outside (other than the response having a termination reason or the stream erroring).

I agree, but this feels like a special case since fetch called me, I didn't call fetch.

In the fetch spec I've tried to keep termination handling as close to the leaf end as I can. HTTP fetch doesn't have a "If terminates" break clause around the call to service workers, so it feels "handle fetch" should be handling the termination.

Alternatively, I could add a break clause the call to "handle fetch" in "main fetch". Then, in "handle fetch" I can somehow "handle response" for the outer fetch, and see if its abort flag is set. But this will be really messy, as the fetch spec is waiting for a response from the service worker spec, but the service worker spec is also waiting for a response from the fetch spec. I'd also end up looping the service worker response back into the service worker spec via the fetch spec, and I'd need to ignore that somehow.

I've updated the PR a little, but it seems to express the behaviour pretty clearly. It's a bit hand-wavey around getting an instance of the fetch, but I think that's part of whatwg/fetch#536 (comment).

docs/index.bs Outdated
1. Initialize |e|’s {{Event/type}} attribute to {{fetch!!event}}.
1. Initialize |e|’s {{Event/cancelable}} attribute to true.
1. Initialize |e|’s {{FetchEvent/request}} attribute to a new {{Request}} object associated with |request| and a new associated {{Headers}} object whose [=guard=] is "`immutable`".
1. Initialize |e|’s {{FetchEvent/request}} attribute to |requestObject|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a trailing dot.

@annevk
Copy link
Member

annevk commented Sep 28, 2017

Yeah, I guess we need to fix that issue at some point. Not looking forward to updating all the callers again.

@jakearchibald
Copy link
Contributor Author

@annevk Are you ok with:

Let |fetchInstance| be the instance of the [=/fetch=] algorithm representing the ongoing fetch.

And

If |fetchInstance| is [=fetch/terminated=], then…

Here?

If so I'll write some tests and get this merged.

@annevk
Copy link
Member

annevk commented Sep 28, 2017

Yeah, as you said there's not really any better way to do it at the moment. Maybe note these cases in the Fetch issue so we make sure they get addressed by whatever we come up with?

@jakearchibald
Copy link
Contributor Author

Copy link
Collaborator

@jungkees jungkees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I defer to @annevk's review for this.

@jungkees
Copy link
Collaborator

@jakearchibald, it seems this is ready to be merged?

@jakearchibald jakearchibald merged commit 5474d11 into master Nov 16, 2017
@jakearchibald
Copy link
Contributor Author

Done. I need to take another pass at the tests. Just minor stuff though.

@annevk annevk deleted the fetch-abort branch November 16, 2017 15:41
@ItalyPaleAle
Copy link

Hi all, sorry for resurrecting this old thread. Wondering if this behavior that I'm noticing is the one as intended according to the spec, as I'm noticing the same thing on all major browsers (Firefox, Chromium, Safari).

It seems that this doesn't catch situations where the browser itself is terminating a request.

Example:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test fetch</title>
</head>
<body>

<button onclick="getVideo()">Get video</button>
<button onclick="stop()">Stop</button>
<div id="out"></div>

<script>
;(async function() {
    await navigator.serviceWorker.register('sw.js')
    console.info('Service worker registered')
    await navigator.serviceWorker.ready
    console.log('Service worker installed')
    document.getElementById('out').innerText = 'Ready'
})()

function getVideo() {
    document.getElementById('out').innerHTML = '<video autoplay controls  muted="muted" src="video.mp4" style="width: 400px; height: auto;"></video>'
}

function stop() {
    document.getElementById('out').innerHTML = ''
}
</script>

</body>
</html>

sw.js

self.addEventListener('install', () => {
    self.skipWaiting()
})

self.addEventListener('fetch', (event) => {
    console.log('fetch', event)
    event.request.signal.addEventListener('abort', (event) => {
        console.log('aborted', event)
    })
})

self.addEventListener('activate', () => self.clients.claim())

Description

Place a video file in the same folder called video.mp4, ideally a large-r file (say, 100MB+). Start a server (e.g. docker run --rm --name some-nginx -v $PWD:/usr/share/nginx/html:ro -p 8080:80 nginx) and navigate to the page (http://localhost:8080).

Start playing the video (hint: it helps to throttle the network), then press the "stop" button before the video is done loading. Alternatively, skip forward in the video to a part that hasn't been buffered yet.

In the dev tools, you'll see the browser interrupted the old request (and made a new one if necessary).

However, the abort event is never triggered. And, in the console, even after the request is done, the signal objected nested into event still reports the request as not aborted.

Is this how it's supposed to behave according to specs? Or is it a bug in the browsers?

Why am I doing this? I have a SW that intercepts a request and makes a new one instead. I need to be able to understand if the browser has canceled the "original" request to cancel the new one too.

@wanderview
Copy link
Member

I recently raised the same issue on #1544.

I think the intent was to abort the signal here, but no browser implements it yet. Also, as I mention in the issue above there may be a spec problem that also makes it not do what is intended.

@ItalyPaleAle
Copy link

Thanks, @wanderview ! I'll follow the other thread then. If you need more details on what I'm trying to do, to add to your case on #1544, please let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants