зеркало из https://github.com/mozilla/gecko-dev.git
101 строка
4.6 KiB
JavaScript
101 строка
4.6 KiB
JavaScript
"use strict";
|
|
|
|
Components.utils.import("resource://gre/modules/osfile.jsm");
|
|
|
|
// We want the actual global to get at the internals since Scheduler is not
|
|
// exported.
|
|
var AsyncFrontGlobal = Components.utils.import(
|
|
"resource://gre/modules/osfile/osfile_async_front.jsm",
|
|
null);
|
|
var Scheduler = AsyncFrontGlobal.Scheduler;
|
|
|
|
/**
|
|
* Verify that Scheduler.kill() interacts with other OS.File requests correctly,
|
|
* and that no requests are lost. This is relevant because on B2G we
|
|
* auto-kill the worker periodically, making it very possible for valid requests
|
|
* to be interleaved with the automatic kill().
|
|
*
|
|
* This test is being created with the fix for Bug 1125989 where `kill` queue
|
|
* management was found to be buggy. It is a glass-box test that explicitly
|
|
* re-creates the observed failure situation; it is not guaranteed to prevent
|
|
* all future regressions. The following is a detailed explanation of the test
|
|
* for your benefit if this test ever breaks or you are wondering what was the
|
|
* point of all this. You might want to skim the code below first.
|
|
*
|
|
* OS.File maintains a `queue` of operations to be performed. This queue is
|
|
* nominally implemented as a chain of promises. Every time a new job is
|
|
* OS.File.push()ed, it effectively becomes the new `queue` promise. (An
|
|
* extra promise is interposed with a rejection handler to avoid the rejection
|
|
* cascading, but that does not matter for our purposes.)
|
|
*
|
|
* The flaw in `kill` was that it would wait for the `queue` to complete before
|
|
* replacing `queue`. As a result, another OS.File operation could use `push`
|
|
* (by way of OS.File.post()) to also use .then() on the same `queue` promise.
|
|
* Accordingly, assuming that promise was not yet resolved (due to a pending
|
|
* OS.File request), when it was resolved, both the task scheduled in `kill`
|
|
* and in `post` would be triggered. Both of those tasks would run until
|
|
* encountering a call to worker.post().
|
|
*
|
|
* Re-creating this race is not entirely trivial because of the large number of
|
|
* promises used by the code causing control flow to repeatedly be deferred. In
|
|
* a slightly simpler world we could run the follwing in the same turn of the
|
|
* event loop and trigger the problem.
|
|
* - any OS.File request
|
|
* - Scheduler.kill()
|
|
* - any OS.File request
|
|
*
|
|
* However, we need the Scheduler.kill task to reach the point where it is
|
|
* waiting on the same `queue` that another task has been scheduled against.
|
|
* Since the `kill` task yields on the `killQueue` promise prior to yielding
|
|
* on `queue`, however, some turns of the event loop are required. Happily,
|
|
* for us, as discussed above, the problem triggers when we have two promises
|
|
* scheduled on the `queue`, so we can just wait to schedule the second OS.File
|
|
* request on the queue. (Note that because of the additional then() added to
|
|
* eat rejections, there is an important difference between the value of
|
|
* `queue` and the value returned by the first OS.File request.)
|
|
*/
|
|
add_task(function* test_kill_race() {
|
|
// Ensure the worker has been created and that SET_DEBUG has taken effect.
|
|
// We have chosen OS.File.exists for our tests because it does not trigger
|
|
// a rejection and we absolutely do not care what the operation is other
|
|
// than it does not invoke a native fast-path.
|
|
yield OS.File.exists('foo.foo');
|
|
|
|
do_print('issuing first request');
|
|
let firstRequest = OS.File.exists('foo.bar');
|
|
let secondRequest;
|
|
let secondResolved = false;
|
|
|
|
// As noted in our big block comment, we want to wait to schedule the
|
|
// second request so that it races `kill`'s call to `worker.post`. Having
|
|
// ourselves wait on the same promise, `queue`, and registering ourselves
|
|
// before we issue the kill request means we will get run before the `kill`
|
|
// task resumes and allow us to precisely create the desired race.
|
|
Scheduler.queue.then(function() {
|
|
do_print('issuing second request');
|
|
secondRequest = OS.File.exists('foo.baz');
|
|
secondRequest.then(function() {
|
|
secondResolved = true;
|
|
});
|
|
});
|
|
|
|
do_print('issuing kill request');
|
|
let killRequest = Scheduler.kill({ reset: true, shutdown: false });
|
|
|
|
// Wait on the killRequest so that we can schedule a new OS.File request
|
|
// after it completes...
|
|
yield killRequest;
|
|
// ...because our ordering guarantee ensures that there is at most one
|
|
// worker (and this usage here should not be vulnerable even with the
|
|
// bug present), so when this completes the secondRequest has either been
|
|
// resolved or lost.
|
|
yield OS.File.exists('foo.goz');
|
|
|
|
ok(secondResolved,
|
|
'The second request was resolved so we avoided the bug. Victory!');
|
|
});
|
|
|
|
function run_test() {
|
|
run_next_test();
|
|
}
|