gecko-dev/toolkit/components/osfile/tests/xpcshell/test_osfile_kill.js
Kris Maglione e930b89c34 Bug 1514594: Part 3 - Change ChromeUtils.import API.
***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8

This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:

  ChromeUtils.import("resource://gre/modules/Services.jsm");

is approximately the same as the following, in the new model:

  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs

This was done using the followng script:

https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16750

--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
2019-01-17 10:18:31 -08:00

94 lines
4.6 KiB
JavaScript

"use strict";
const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
// We want the actual global to get at the internals since Scheduler is not
// exported.
var AsyncFrontGlobal = ChromeUtils.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(async 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.
await OS.File.exists("foo.foo");
info("issuing first request");
let firstRequest = OS.File.exists("foo.bar"); // eslint-disable-line no-unused-vars
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() {
info("issuing second request");
secondRequest = OS.File.exists("foo.baz");
secondRequest.then(function() {
secondResolved = true;
});
});
info("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...
await 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.
await OS.File.exists("foo.goz");
ok(secondResolved,
"The second request was resolved so we avoided the bug. Victory!");
});