Bug 1372670 - part 3 - add spinEventLoopUntil to nsIThreadManager; r=erahm,florian

This commit is contained in:
Nathan Froyd 2017-06-21 12:59:28 -04:00
parent 2c70f522cc
commit 27c58cf89f
45 changed files with 318 additions and 168 deletions

View File

@ -28,8 +28,7 @@ function awaitPromise(promise) {
value = val;
});
while (resolved === null)
Services.tm.mainThread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => resolved !== null);
if (resolved === true)
return value;

View File

@ -5175,9 +5175,9 @@ function server(port, basePath)
srv.registerContentType("sjs", SJS_TYPE);
srv.start(port);
gThreadManager.spinEventLoopUntil(() => srv.isStopped());
var thread = gThreadManager.currentThread;
while (!srv.isStopped())
thread.processNextEvent(true);
// get rid of any pending requests
while (thread.hasPendingEvents())

View File

@ -5175,9 +5175,9 @@ function server(port, basePath)
srv.registerContentType("sjs", SJS_TYPE);
srv.start(port);
gThreadManager.spinEventLoopUntil(() => srv.isStopped());
var thread = gThreadManager.currentThread;
while (!srv.isStopped())
thread.processNextEvent(true);
// get rid of any pending requests
while (thread.hasPendingEvents())

View File

@ -5176,9 +5176,9 @@ function server(port, basePath)
srv.registerContentType("sjs", SJS_TYPE);
srv.start(port);
gThreadManager.spinEventLoopUntil(() => srv.isStopped());
var thread = gThreadManager.currentThread;
while (!srv.isStopped())
thread.processNextEvent(true);
// get rid of any pending requests
while (thread.hasPendingEvents())

View File

@ -5175,9 +5175,9 @@ function server(port, basePath)
srv.registerContentType("sjs", SJS_TYPE);
srv.start(port);
gThreadManager.spinEventLoopUntil(() => srv.isStopped());
var thread = gThreadManager.currentThread;
while (!srv.isStopped())
thread.processNextEvent(true);
// get rid of any pending requests
while (thread.hasPendingEvents())

View File

@ -5176,9 +5176,9 @@ function server(port, basePath)
srv.registerContentType("sjs", SJS_TYPE);
srv.start(port);
gThreadManager.spinEventLoopUntil(() => srv.isStopped());
var thread = gThreadManager.currentThread;
while (!srv.isStopped())
thread.processNextEvent(true);
// get rid of any pending requests
while (thread.hasPendingEvents())

View File

@ -915,10 +915,10 @@ class Buffer extends PP_Resource {
class Flash_MessageLoop extends PP_Resource {
run() {
this._running = true;
let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
while (this._running) {
thread.processNextEvent(true);
}
let tm = Cc["@mozilla.org/thread-manager;1"].getService();
tm.spinEventLoopUntil(() => {
return !this._running;
});
}
quit() {
this._running = false;

View File

@ -139,10 +139,10 @@ DevToolsStartup.prototype = {
if (pauseOnStartup) {
// Spin the event loop until the debugger connects.
let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
while (!devtoolsThreadResumed) {
thread.processNextEvent(true);
}
let tm = Cc["@mozilla.org/thread-manager;1"].getService();
tm.spinEventLoopUntil(() => {
return devtoolsThreadResumed;
});
}
if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {

View File

@ -627,9 +627,9 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.enterModalState();
while (!setupFinished) {
tm.currentThread.processNextEvent(true);
}
tm.spinEventLoopUntil(() => {
return setupFinished;
});
utils.leaveModalState();
};

View File

@ -163,9 +163,9 @@ function tunnelToInnerBrowser(outer, inner) {
// The constructor of the new XBL binding is run asynchronously and there is no
// event to signal its completion. Spin an event loop to watch for properties that
// are set by the contructor.
while (!outer._remoteWebNavigation) {
Services.tm.currentThread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => {
return outer._remoteWebNavigation;
});
// Replace the `webNavigation` object with our own version which tries to use
// mozbrowser APIs where possible. This replaces the webNavigation object that the

View File

@ -20,11 +20,11 @@ function setTestPluginEnabledState(newEnabledState, pluginName) {
return;
}
var plugin = getTestPlugin(pluginName);
while (plugin.enabledState != newEnabledState) {
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.currentThread.processNextEvent(true);
}
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.spinEventLoopUntil(() => {
return plugin.enabledState == newEnabledState;
});
SimpleTest.registerCleanupFunction(function() {
SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
});

View File

@ -425,20 +425,19 @@ BrowserElementChild.prototype = {
win.modalDepth++;
let origModalDepth = win.modalDepth;
let thread = Services.tm.currentThread;
debug("Nested event loop - begin");
while (win.modalDepth == origModalDepth && !this._shuttingDown) {
Services.tm.spinEventLoopUntil(() => {
// Bail out of the loop if the inner window changed; that means the
// window navigated. Bail out when we're shutting down because otherwise
// we'll leak our window.
if (this._tryGetInnerWindowID(win) !== innerWindowID) {
debug("_waitForResult: Inner window ID changed " +
"while in nested event loop.");
break;
return true;
}
thread.processNextEvent(/* mayWait = */ true);
}
return win.modalDepth !== origModalDepth || this._shuttingDown;
});
debug("Nested event loop - finish");
if (win.modalDepth == 0) {

View File

@ -26,9 +26,9 @@ function* testSteps()
let transaction2;
let comp = this.window ? SpecialPowers.wrap(Components) : Components;
let thread = comp.classes["@mozilla.org/thread-manager;1"]
.getService(comp.interfaces.nsIThreadManager)
.currentThread;
let tm = comp.classes["@mozilla.org/thread-manager;1"]
.getService(comp.interfaces.nsIThreadManager);
let thread = tm.currentThread;
let eventHasRun;
@ -38,9 +38,7 @@ function* testSteps()
transaction2 = db.transaction("foo");
}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
while (!eventHasRun) {
thread.processNextEvent(false);
}
tm.spinEventLoopUntil(() => eventHasRun);
ok(transaction2, "Non-null transaction2");

View File

@ -28,11 +28,11 @@ function getTestPlugin(pluginName) {
function setTestPluginEnabledState(newEnabledState, pluginName) {
var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
var plugin = getTestPlugin(pluginName);
while (plugin.enabledState != newEnabledState) {
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.currentThread.processNextEvent(true);
}
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.spinEventLoopUntil(() => {
return plugin.enabledState == newEnabledState;
});
SimpleTest.registerCleanupFunction(function() {
SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
});

View File

@ -19,11 +19,9 @@ function setTestPluginEnabledState(newEnabledState, pluginName) {
return;
}
var plugin = getTestPlugin(pluginName);
while (plugin.enabledState != newEnabledState) {
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.currentThread.processNextEvent(true);
}
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.spinEventLoopUntil(() => plugin.enabledState == newEnabledState);
SimpleTest.registerCleanupFunction(function() {
SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
});

View File

@ -179,9 +179,7 @@ FilePicker.prototype = {
this._promptActive = true;
this._sendMessage();
let thread = Services.tm.currentThread;
while (this._promptActive)
thread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => !this._promptActive);
delete this._promptActive;
if (this._domWin) {

View File

@ -71,9 +71,7 @@ NSSDialogs.prototype = {
});
// Spin this thread while we wait for a result
let thread = Services.tm.currentThread;
while (response === null)
thread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => response != null);
return response;
},

View File

@ -200,9 +200,7 @@ InternalPrompt.prototype = {
});
// Spin this thread while we wait for a result
let thread = Services.tm.currentThread;
while (retval == null)
thread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => retval != null);
if (this._domWin) {
let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);

View File

@ -56,10 +56,7 @@ TabSource.prototype = {
});
// Spin this thread while we wait for a result.
let thread = Services.tm.currentThread;
while (result == null) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => result != null);
if (result == -1) {
return null;

View File

@ -460,10 +460,7 @@ PromptDelegate.prototype = {
this.asyncShowPrompt(aMsg, res => result = res);
// Spin this thread while we wait for a result
let thread = Services.tm.currentThread;
while (result === undefined) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => result !== undefined);
} finally {
this._changeModalState(/* aEntering */ false);
}

View File

@ -838,9 +838,7 @@ JavaBridge.prototype = {
// spin the event loop, but here we're in a test and our API
// specifies a synchronous call, so we spin the loop to wait for
// the call to finish.
while (this._repliesNeeded > initialReplies) {
thread.processNextEvent(true);
}
this._Services.tm.spinEventLoopUntil(() => this._repliesNeeded <= initialReplies);
},
/**

View File

@ -387,10 +387,7 @@ function waitForAResponse(outputList) {
// From <https://developer.mozilla.org/en/XPConnect/xpcshell/HOWTO>
var threadManager = Cc["@mozilla.org/thread-manager;1"]
.getService(Ci.nsIThreadManager);
var mainThread = threadManager.currentThread;
while (outputList.length == 0) {
mainThread.processNextEvent(true);
}
threadManager.spinEventLoopUntil(() => outputList.length != 0);
}
function readCurrentList(filename) {

View File

@ -91,12 +91,10 @@ this.Async = {
*/
waitForSyncCallback: function waitForSyncCallback(callback) {
// Grab the current thread so we can make it give up priority.
let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
let tm = Cc["@mozilla.org/thread-manager;1"].getService();
// Keep waiting until our callback is triggered (unless the app is quitting).
while (Async.checkAppReady() && callback.state == CB_READY) {
thread.processNextEvent(true);
}
tm.spinEventLoopUntil(() => !Async.checkAppReady || callback.state != CB_READY);
// Reset the state of the callback to prepare for another call.
let state = callback.state;

View File

@ -591,14 +591,15 @@ Assert.prototype = {
var timeoutInterval = hwindow.setInterval(wait, interval);
var thread = Services.tm.currentThread;
while (self.result !== true && !self.timeIsUp) {
thread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => {
let type = typeof(self.result);
if (type !== 'boolean')
if (type !== 'boolean') {
throw TypeError("waitFor() callback has to return a boolean" +
" instead of '" + type + "'");
}
}
return self.result === true || self.timeIsUp;
});
hwindow.clearInterval(timeoutInterval);

View File

@ -5341,9 +5341,9 @@ function server(port, basePath)
srv.identity.setPrimary("http", "localhost", port);
srv.start(port);
gThreadManager.spinEventLoopUntil(() => srv.isStopped());
var thread = gThreadManager.currentThread;
while (!srv.isStopped())
thread.processNextEvent(true);
// get rid of any pending requests
while (thread.hasPendingEvents())

View File

@ -210,11 +210,7 @@ function sleep(milliseconds) {
var timeup = false;
hwindow.setTimeout(function () { timeup = true; }, milliseconds);
var thread = Services.tm.currentThread;
while (!timeup) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => timeup);
broker.pass({'function':'utils.sleep()'});
}

View File

@ -73,10 +73,8 @@ function asyncCleanup() {
print("*** Storage Tests: Trying to asyncClose!");
getOpenedDatabase().asyncClose(function() { closed = true; });
let curThread = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
while (!closed)
curThread.processNextEvent(true);
let tm = Cc["@mozilla.org/thread-manager;1"].getService();
tm.spinEventLoopUntil(() => closed);
// we need to null out the database variable to get a new connection the next
// time getOpenedDatabase is called

View File

@ -135,10 +135,8 @@ function execAsync(aStmt, aOptions, aResults) {
if ("cancel" in aOptions && aOptions.cancel)
pending.cancel();
let curThread = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
while (!completed && !_quit)
curThread.processNextEvent(true);
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
tm.spinEventLoopUntil(() => completed || _quit);
return pending;
}

View File

@ -215,11 +215,10 @@ function _do_main() {
_testLogger.info("running event loop");
var thr = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
var tm = Components.classes["@mozilla.org/thread-manager;1"].getService();
var thr = tm.currentThread;
while (!_quit)
thr.processNextEvent(true);
tm.spinEventLoopUntil(() => _quit);
while (thr.hasPendingEvents())
thr.processNextEvent(true);
@ -479,12 +478,14 @@ function _initDebugging(port) {
listener.open();
// spin an event loop until the debugger connects.
let thr = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
while (!initialized) {
let tm = Components.classes["@mozilla.org/thread-manager;1"].getService();
tm.spinEventLoopUntil(() => {
if (initialized) {
return true;
}
do_print("Still waiting for debugger to connect...");
thr.processNextEvent(true);
}
return false;
});
// NOTE: if you want to debug the harness itself, you can now add a 'debugger'
// statement anywhere and it will stop - but we've already added a breakpoint
// for the first line of the test scripts, so we just continue...
@ -614,11 +615,8 @@ function _execute_test() {
}
_cleanupFunctions = [];
}).catch(reportCleanupError).then(() => complete = true);
let thr = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
while (!complete) {
thr.processNextEvent(true);
}
let tm = Components.classes["@mozilla.org/thread-manager;1"].getService();
tm.spinEventLoopUntil(() => complete);
// Restore idle service to avoid leaks.
_fakeIdleService.deactivate();

View File

@ -3565,10 +3565,7 @@ PlacesEditBookmarkKeywordTransaction.prototype = {
.then(() => done = true);
// TODO: Until we can move to PlacesTransactions.jsm, we must spin the
// events loop :(
let thread = Services.tm.currentThread;
while (!done) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => done);
},
undoTransaction: function EBKTXN_undoTransaction() {
@ -3590,10 +3587,9 @@ PlacesEditBookmarkKeywordTransaction.prototype = {
.then(() => done = true);
// TODO: Until we can move to PlacesTransactions.jsm, we must spin the
// events loop :(
let thread = Services.tm.currentThread;
while (!done) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => {
return done;
});
}
};

View File

@ -8,7 +8,7 @@ Cu.import("resource://gre/modules/PlacesUtils.jsm");
// This function "waits" for a promise to resolve by spinning a nested event
// loop.
function waitForPromise(promise) {
let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
let tm = Cc["@mozilla.org/thread-manager;1"].getService();
let finalResult, finalException;
@ -19,9 +19,8 @@ function waitForPromise(promise) {
});
// Keep waiting until our callback is triggered (unless the app is quitting).
while (!finalResult && !finalException) {
thread.processNextEvent(true);
}
tm.spinEventLoopUntil(() => finalResult || finalException);
if (finalException) {
throw finalException;
}

View File

@ -415,9 +415,7 @@ function openTabPrompt(domWin, tabPrompt, args) {
// there's other stuff in nsWindowWatcher::OpenWindowInternal
// that we might need to do here as well.
let thread = Services.tm.currentThread;
while (args.promptActive)
thread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => !args.promptActive);
delete args.promptActive;
if (args.promptAborted)
@ -489,10 +487,7 @@ function openRemotePrompt(domWin, args, tabPrompt) {
messageManager.sendAsyncMessage("Prompt:Open", args, {});
let thread = Services.tm.currentThread;
while (!closed) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => closed);
}
function ModalPrompter(domWin) {

View File

@ -605,10 +605,7 @@ function dbClose(aShutdown) {
_dbConnection.asyncClose(() => closed = true);
if (!aShutdown) {
let thread = Services.tm.currentThread;
while (!closed) {
thread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => closed);
}
}

View File

@ -89,11 +89,11 @@ function setTestPluginEnabledState(newEnabledState, pluginName) {
return;
}
var plugin = getTestPlugin(pluginName);
while (plugin.enabledState != newEnabledState) {
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.currentThread.processNextEvent(true);
}
// Run a nested event loop to wait for the preference change to
// propagate to the child. Yuck!
SpecialPowers.Services.tm.spinEventLoopUntil(() => {
return plugin.enabledState == newEnabledState;
});
SimpleTest.registerCleanupFunction(function() {
SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
});

View File

@ -3,14 +3,13 @@
// Let the event loop process a bit before crashing.
if (shouldDelay) {
let shouldCrashNow = false;
let thr = Components.classes["@mozilla.org/thread-manager;1"]
.getService().currentThread;
let tm = Components.classes["@mozilla.org/thread-manager;1"]
.getService();
let thr = tm.currentThread;
thr.dispatch({ run: () => { shouldCrashNow = true; } },
Components.interfaces.nsIThread.DISPATCH_NORMAL);
while (!shouldCrashNow) {
thr.processNextEvent(true);
}
tm.spinEventLoopUntil(() => shouldCrashNow);
}
// now actually crash

View File

@ -24,9 +24,7 @@ function setup_crash() {
terminator.observe(null, "profile-before-change", null);
dump("Waiting (actively) for the crash\n");
while (true) {
Services.tm.currentThread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => false);
}

View File

@ -114,9 +114,7 @@ this.PromiseTestUtils = {
PromiseDebugging.addUncaughtRejectionObserver(observer);
Promise.reject(this._ensureDOMPromiseRejectionsProcessedReason);
while (!observed) {
Services.tm.mainThread.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => observed);
PromiseDebugging.removeUncaughtRejectionObserver(observer);
},
_ensureDOMPromiseRejectionsProcessedReason: {},

View File

@ -922,10 +922,7 @@ tests.push(
let shouldExitNestedEventLoop = false;
function event_loop() {
let thr = Services.tm.mainThread;
while (!shouldExitNestedEventLoop) {
thr.processNextEvent(true);
}
Services.tm.spinEventLoopUntil(() => shouldExitNestedEventLoop);
}
// I wish there was a way to cancel xpcshell do_timeout()s

View File

@ -401,8 +401,7 @@ var AddonTestUtils = {
done = true;
});
while (!done)
Services.tm.mainThread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => done);
if (error !== undefined)
throw error;

View File

@ -1012,10 +1012,7 @@ function syncLoadManifestFromFile(aFile, aInstallLocation) {
result = val
});
let thread = Services.tm.currentThread;
while (success === undefined)
thread.processNextEvent(true);
Services.tm.spinEventLoopUntil(() => success !== undefined);
if (!success)
throw result;

View File

@ -60,12 +60,10 @@ var WindowWatcher = {
// The dialog is meant to be opened modally and the install operation can be
// asynchronous, so we must spin an event loop (like the modal window does)
// until the install is complete
let thr = AM_Cc["@mozilla.org/thread-manager;1"].
getService(AM_Ci.nsIThreadManager).
mainThread;
let tm = AM_Cc["@mozilla.org/thread-manager;1"].
getService(AM_Ci.nsIThreadManager);
while (!installed || !updated)
thr.processNextEvent(false);
tm.spinEventLoopUntil(() => installed && updated);
},
QueryInterface(iid) {

View File

@ -0,0 +1,164 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIThreadManager.h"
#include "nsCOMPtr.h"
#include "nsXPCOM.h"
#include "nsThreadUtils.h"
#include "mozilla/Atomics.h"
#include "gtest/gtest.h"
using mozilla::Atomic;
using mozilla::Runnable;
class WaitCondition final : public nsINestedEventLoopCondition
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
WaitCondition(Atomic<uint32_t>& aCounter, uint32_t aMaxCount)
: mCounter(aCounter)
, mMaxCount(aMaxCount)
{
}
NS_IMETHODIMP IsDone(bool* aDone) override
{
*aDone = (mCounter == mMaxCount);
return NS_OK;
}
private:
~WaitCondition() = default;
Atomic<uint32_t>& mCounter;
const uint32_t mMaxCount;
};
NS_IMPL_ISUPPORTS(WaitCondition, nsINestedEventLoopCondition)
class SpinRunnable final : public Runnable
{
public:
explicit SpinRunnable(nsINestedEventLoopCondition* aCondition)
: mCondition(aCondition)
, mResult(NS_OK)
{
}
NS_IMETHODIMP Run()
{
nsCOMPtr<nsIThreadManager> threadMan =
do_GetService("@mozilla.org/thread-manager;1");
mResult = threadMan->SpinEventLoopUntil(mCondition);
return NS_OK;
}
nsresult SpinLoopResult()
{
return mResult;
}
private:
~SpinRunnable() = default;
nsCOMPtr<nsINestedEventLoopCondition> mCondition;
Atomic<nsresult> mResult;
};
class CountRunnable final : public Runnable
{
public:
explicit CountRunnable(Atomic<uint32_t>& aCounter)
: mCounter(aCounter)
{
}
NS_IMETHODIMP Run()
{
mCounter++;
return NS_OK;
}
private:
Atomic<uint32_t>& mCounter;
};
TEST(ThreadManager, SpinEventLoopUntilSuccess)
{
const uint32_t kRunnablesToDispatch = 100;
nsresult rv;
mozilla::Atomic<uint32_t> count(0);
nsCOMPtr<nsINestedEventLoopCondition> condition =
new WaitCondition(count, kRunnablesToDispatch);
RefPtr<SpinRunnable> spinner = new SpinRunnable(condition);
nsCOMPtr<nsIThread> thread;
rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner);
ASSERT_TRUE(NS_SUCCEEDED(rv));
nsCOMPtr<nsIRunnable> counter = new CountRunnable(count);
for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) {
rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL);
ASSERT_TRUE(NS_SUCCEEDED(rv));
}
rv = thread->Shutdown();
ASSERT_TRUE(NS_SUCCEEDED(rv));
ASSERT_TRUE(NS_SUCCEEDED(spinner->SpinLoopResult()));
}
class ErrorCondition final : public nsINestedEventLoopCondition
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
ErrorCondition(Atomic<uint32_t>& aCounter, uint32_t aMaxCount)
: mCounter(aCounter)
, mMaxCount(aMaxCount)
{
}
NS_IMETHODIMP IsDone(bool* aDone) override
{
if (mCounter == mMaxCount) {
return NS_ERROR_ILLEGAL_VALUE;
}
return NS_OK;
}
private:
~ErrorCondition() = default;
Atomic<uint32_t>& mCounter;
const uint32_t mMaxCount;
};
NS_IMPL_ISUPPORTS(ErrorCondition, nsINestedEventLoopCondition)
TEST(ThreadManager, SpinEventLoopUntilError)
{
const uint32_t kRunnablesToDispatch = 100;
nsresult rv;
mozilla::Atomic<uint32_t> count(0);
nsCOMPtr<nsINestedEventLoopCondition> condition =
new ErrorCondition(count, kRunnablesToDispatch);
RefPtr<SpinRunnable> spinner = new SpinRunnable(condition);
nsCOMPtr<nsIThread> thread;
rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner);
ASSERT_TRUE(NS_SUCCEEDED(rv));
nsCOMPtr<nsIRunnable> counter = new CountRunnable(count);
for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) {
rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL);
ASSERT_TRUE(NS_SUCCEEDED(rv));
}
rv = thread->Shutdown();
ASSERT_TRUE(NS_SUCCEEDED(rv));
ASSERT_TRUE(NS_FAILED(spinner->SpinLoopResult()));
}

View File

@ -44,6 +44,7 @@ UNIFIED_SOURCES += [
'TestTArray2.cpp',
'TestTaskQueue.cpp',
'TestTextFormatter.cpp',
'TestThreadManager.cpp',
'TestThreadPool.cpp',
'TestThreadPoolListener.cpp',
'TestThreads.cpp',

View File

@ -11,6 +11,15 @@
interface nsIRunnable;
interface nsIThread;
[scriptable, function, uuid(039a227d-0cb7-44a5-a8f9-dbb7071979f2)]
interface nsINestedEventLoopCondition : nsISupports
{
/**
* Returns true if the current nested event loop should stop spinning.
*/
bool isDone();
};
/**
* An interface for creating and locating nsIThread instances.
*/
@ -85,6 +94,17 @@ interface nsIThreadManager : nsISupports
*/
void dispatchToMainThread(in nsIRunnable event);
/**
* Enter a nested event loop on the current thread, waiting on, and
* processing events until condition.isDone() returns true.
*
* If condition.isDone() throws, this function will throw as well.
*
* C++ code should not use this function, instead preferring
* mozilla::SpinEventLoopUntil.
*/
void spinEventLoopUntil(in nsINestedEventLoopCondition condition);
/**
* This queues a runnable to the main thread's idle queue.
*

View File

@ -338,6 +338,32 @@ nsThreadManager::GetCurrentThread(nsIThread** aResult)
return NS_OK;
}
NS_IMETHODIMP
nsThreadManager::SpinEventLoopUntil(nsINestedEventLoopCondition* aCondition)
{
nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
nsresult rv = NS_OK;
if (!mozilla::SpinEventLoopUntil([&]() -> bool {
bool isDone = false;
rv = condition->IsDone(&isDone);
// JS failure should be unusual, but we need to stop and propagate
// the error back to the caller.
if (NS_FAILED(rv)) {
return true;
}
return isDone;
})) {
// We stopped early for some reason, which is unexpected.
return NS_ERROR_UNEXPECTED;
}
// If we exited when the condition told us to, we need to return whether
// the condition encountered failure when executing.
return rv;
}
uint32_t
nsThreadManager::GetHighestNumberOfThreads()
{