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

[service workers] Reduce reliance on global state #5206

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jugglinmike
Copy link
Contributor

@jugglinmike jugglinmike commented Mar 22, 2017

The Service Worker specification allows user agents to terminate workers
at any time. This makes global state highly volatile. Tests which rely
on persistence between invocations of service worker handlers are
therefore subject to sporadic failure according to the browser's
internal worker termination heuristic.

Update tests that previously relied on global service worker state to
instead store state in a persistent IndexedDB object store. Introduce a
set of utility functions to simplify interaction with this object store.

@ehsan @mkruisselbrink @mattto See
w3c/ServiceWorker#1087 for an ongoing discussion
regarding worker termination and reliance on global state (and please feel free
to weigh in with your own perspective).

An alternative to using IndexedDB would be to send event data from the worker
to the client via the clients
interface
.
This might be simpler in some ways, but I'm reluctant to use the Service Worker
API to test the Service Worker API.

Finally, there are still more tests that rely on global state. I am submitting
this patch in order to solicit feedback on a viable workaround prior to fully
addressing the problem.

@wpt-pr-bot
Copy link
Collaborator

Notifying @ehsan, @mattto, and @mkruisselbrink. (Learn how reviewing works.)

@ghost
Copy link

ghost commented Mar 22, 2017

View the complete job log.

Firefox (nightly channel)

Testing web-platform-tests at revision 2cdb30d
Using browser at version BuildID 20170330100130; SourceStamp 60d7a0496a3673450ddbc37ec387525148c32604
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception PASS
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() PASS
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. FAIL assert_unreached: unexpected rejection: assert_equals: The base URL while loading the images referred from CSS must be the request URL of CSS. expected "https://web-platform.test:8443/service-workers/service-worker/resources/dummy.png" but got "https://www1.web-platform.test:8443/service-workers/service-worker/resources/dummy.png" Reached unreachable code
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted PASS

@ghost
Copy link

ghost commented Mar 22, 2017

View the complete job log.

Chrome (unstable channel)

Testing web-platform-tests at revision 2cdb30d
Using browser at version 59.0.3053.3 dev
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception PASS
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() PASS
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. PASS
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted PASS

@ghost
Copy link

ghost commented Mar 22, 2017

View the complete job log.

Firefox (nightly channel)

Testing web-platform-tests at revision efc99c4
Using browser at version BuildID 20170321110237; SourceStamp ca4ae502156eaea6fffb296bb9c3b3930af8ab58
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception PASS
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() PASS
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. FAIL assert_unreached: unexpected rejection: assert_equals: The base URL while loading the images referred from CSS must be the request URL of CSS. expected "https://web-platform.test:8443/service-workers/service-worker/resources/dummy.png" but got "https://www1.web-platform.test:8443/service-workers/service-worker/resources/dummy.png" Reached unreachable code
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted PASS

@ghost
Copy link

ghost commented Mar 22, 2017

View the complete job log.

Chrome (unstable channel)

Testing web-platform-tests at revision efc99c4
Using browser at version 59.0.3047.0 dev
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception PASS
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() PASS
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. PASS
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted PASS

@jugglinmike
Copy link
Contributor Author

@ehsan @mkruisselbrink @mattto any thoughts on this?


self.addEventListener('fetch', function(event) {
setTimeout(function() {
try {
event.respondWith(new Response());
result = 'FAIL: did not throw';
ServiceWorkerRecorder.worker.save('FAIL: did not throw');
Copy link
Contributor

Choose a reason for hiding this comment

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

here (and pretty much everywhere else) you probably want to waitUntil the result of save?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely. This was an issue in multiple places, but correcting it in this
file in particular required a slightly more involved change.

get.onsuccess = function() {
var cursor = get.result;
if (!cursor) {
Promise.all(deleteRequests)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I wonder if Promise.all is really the behavior you want here in case one of the delete requests rejects (Promise.all doesn't wait for all promises if any of them reject)? Although in practice if any of the delete requests fail you have bigger issues anyway, so it probably doesn't matter.

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 think it is. If one of these requests fail, the test is most likely doomed.

@@ -0,0 +1,204 @@
'use strict';
self.ServiceWorkerRecorder = (function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe some comments explaining what ServiceWorkerRecorder is and what you would use it for could be helpful? Also might want to make it explicit that despite the need to pass a "worker" to the various client methods, all the workers will record to the same database, with no way to distinguish records from different workers (or maybe it would make sense to key of "scope" as well to at least get some support for multiple recording workers?)

Also I'm not sure what the benefit is of directing all client requests through the service worker rather than just directly accessing IDB? Is that just to get a clear linearalized order of operations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe some comments explaining what ServiceWorkerRecorder is and what you
would use it for could be helpful?

Sure thing.

Also might want to make it explicit that despite the need to pass a "worker"
to the various client methods, all the workers will record to the same
database, with no way to distinguish records from different workers (or maybe
it would make sense to key of "scope" as well to at least get some support
for multiple recording workers?)

This sounds good to me.

Also I'm not sure what the benefit is of directing all client requests
through the service worker rather than just directly accessing IDB? Is that
just to get a clear linearalized order of operations?

Directing client requests in this way allows for keying on the service worker's
location and (in light of your previous comment) scope.

<script>
promise_test(function(t) {
var script = 'resources/fetch-event-async-respond-with-worker.js';
var scope = 'resources/simple.html';

return service_worker_unregister_and_register(t, script, scope)
.then(function(registration) {
return wait_for_state(t, registration.installing, 'activated');
return wait_for_state(t, registration.installing, 'activated')
.then(function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the nested promise chains here (and in some other places)? You could just list this then up to the main promise chain. (i.e. register.then(wait for state).then(clear).then(with iframe)...)

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've structured the chains in this way whenever one callback requires a value
that is only available in the inner function's scope. I could leak that value
to the outer scope in order to keep the chain flat, but for cases like the
registration object, that introduces a reference that may be invalidated by
a future unregister/register cycle. So although it may be a little harder to
read, I prefer nesting closures here for safety's sake.

But you're the maintainer here, so I'm happy to defer to your preference if
that isn't convincing to you :)

@ghost
Copy link

ghost commented Mar 28, 2017

These tests are now available on w3c-test.org

@jugglinmike
Copy link
Contributor Author

Thanks for the review, @mkruisselbrink!

@jugglinmike
Copy link
Contributor Author

Due to unrelated problems with the build process, I had to rebase this branch before the automated tests would pass. My apologies about that. In case it's helpful, I've made the original version available here:

master...bocoup:sw-recording-prebase

@jugglinmike
Copy link
Contributor Author

@mkruisselbrink This utility would help me write some tests around client claiming at various stages of the lifecycle (see this comment in the Chromium test suite https://chromium.googlesource.com/chromium/src.git/+/92c1a4cf87397d9a63c6e1bc0bb6ecc141a0979c/third_party/WebKit/LayoutTests/http/tests/serviceworker/waiting.html#33 ). Is there anything more I can do to help this land?

@wpt-pr-bot wpt-pr-bot requested review from ehsan and mfalken May 26, 2017 18:54
The Service Worker specification allows user agents to terminate workers
at any time. This makes global state highly volatile. Tests which rely
on persistence between invocations of service worker handlers are
therefore subject to sporadic failure according to the browser's
internal worker termination heuristic.

Update tests that previously relied on global service worker state to
instead store state in a persistent IndexedDB object store. Introduce a
set of utility functions to simplify interaction with this object store.
@jugglinmike
Copy link
Contributor Author

Recent changes in master have introduced a merge conflict with this branch. I've rebased and force-pushed the result; the original version of this branch is available here:

master...bocoup:sw-recording-old

@ghost
Copy link

ghost commented Jun 2, 2017

View the complete job log.

Firefox (nightly)

Testing web-platform-tests at revision 258521d
Using browser at version BuildID 20170602100143; SourceStamp aeb3d0ca558f034cbef1c5a68bd07dd738611494
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception PASS
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() PASS
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. FAIL assert_unreached: unexpected rejection: assert_equals: The base URL while loading the images referred from CSS must be the request URL of CSS. expected "https://web-platform.test:8443/service-workers/service-worker/resources/dummy.png" but got "https://www1.web-platform.test:8443/service-workers/service-worker/resources/dummy.png" Reached unreachable code
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted PASS

@ghost
Copy link

ghost commented Jun 2, 2017

View the complete job log.

Sauce (safari)

Testing web-platform-tests at revision 258521d
Using browser at version 10.0
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception FAIL undefined is not an object (evaluating 'navigator.serviceWorker.getRegistration')
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() FAIL undefined is not an object (evaluating 'navigator.serviceWorker.getRegistration')
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. FAIL undefined is not an object (evaluating 'navigator.serviceWorker.getRegistration')
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted FAIL undefined is not an object (evaluating 'navigator.serviceWorker.getRegistration')

@ghost
Copy link

ghost commented Jun 2, 2017

View the complete job log.

Chrome (unstable)

Testing web-platform-tests at revision 258521d
Using browser at version 60.0.3112.10 dev
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception PASS
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() PASS
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. PASS
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted PASS

@ghost
Copy link

ghost commented Jun 2, 2017

View the complete job log.

Sauce (MicrosoftEdge)

Testing web-platform-tests at revision 258521d
Using browser at version 14.14393
Starting 10 test iterations
All results were stable

All results

4 tests ran
/service-workers/service-worker/fetch-event-async-respond-with.https.html
Subtest Results Messages
OK
Calling respondWith asynchronously throws an exception FAIL Unable to get property 'getRegistration' of undefined or null reference
/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html
Subtest Results Messages
OK
respondWith() invokes stopImmediatePropagation() FAIL Unable to get property 'getRegistration' of undefined or null reference
/service-workers/service-worker/fetch-request-css-base-url.https.html
Subtest Results Messages
OK
CSS's base URL must be the request URL even when fetched from other URL. FAIL Unable to get property 'getRegistration' of undefined or null reference
/service-workers/service-worker/websocket-traffic-no-fetch.https.html
Subtest Results Messages
OK
Verify WebSocket handshake channel does not get intercepted FAIL Unable to get property 'getRegistration' of undefined or null reference

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

Successfully merging this pull request may close these issues.

3 participants