Bug 1283924 - Implement AsyncTask hooks for worker thread JSContexts (r=bkelly)

MozReview-Commit-ID: CU4tiYaREik

--HG--
extra : rebase_source : 35a1017de6844af78ec124ef84f355727f19cd80
This commit is contained in:
Luke Wagner 2016-08-19 14:00:53 -05:00
Родитель ce850ee396
Коммит cc5c2c3ffc
2 изменённых файлов: 227 добавлений и 1 удалений

Просмотреть файл

@ -65,10 +65,55 @@ function compileManySuccess() {
);
}
function compileInWorker() {
var w = new Worker(`data:text/plain,
onmessage = e => {
WebAssembly.compile(e.data).then(m => {
var i = new WebAssembly.Instance(m);
if (i.exports.foo() !== 42)
throw "bad i.exports.foo() result";
postMessage("ok");
close();
}).catch(err => { throw err });
}
`);
w.postMessage(fooModuleCode);
w.onmessage = e => {
ok(e.data === "ok", "worker test");
runTest();
}
}
function terminateCompileInWorker() {
var w = new Worker(`data:text/plain,
var fooModuleCode;
function spawnWork() {
const N = 100;
var arr = [];
for (var i = 0; i < N; i++)
arr.push(WebAssembly.compile(fooModuleCode));
Promise.all(arr).then(spawnWork);
}
onmessage = e => {
fooModuleCode = e.data;
spawnWork();
postMessage("ok");
}
`);
w.postMessage(fooModuleCode);
w.onmessage = e => {
ok(e.data === "ok", "worker finished first step");
w.terminate();
runTest();
}
}
var tests = [ propertiesExist,
compileFail,
compileSuccess,
compileManySuccess
compileManySuccess,
compileInWorker,
terminateCompileInWorker
];
function runTest() {

Просмотреть файл

@ -713,6 +713,185 @@ AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
aSize, aMemory, aHandle);
}
class AsyncTaskWorkerHolder final : public WorkerHolder
{
bool Notify(Status aStatus) override
{
// The async task must complete in bounded time and there is not (currently)
// a clean way to cancel it. Async tasks do not run arbitrary content.
return true;
}
public:
WorkerPrivate* Worker() const
{
return mWorkerPrivate;
}
};
template <class RunnableBase>
class AsyncTaskBase : public RunnableBase
{
UniquePtr<AsyncTaskWorkerHolder> mHolder;
// Disable the usual pre/post-dispatch thread assertions since we are
// dispatching from some random JS engine internal thread:
bool PreDispatch(WorkerPrivate* aWorkerPrivate) override
{
return true;
}
void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{ }
protected:
explicit AsyncTaskBase(UniquePtr<AsyncTaskWorkerHolder> aHolder)
: RunnableBase(aHolder->Worker(),
WorkerRunnable::WorkerThreadUnchangedBusyCount)
, mHolder(Move(aHolder))
{
MOZ_ASSERT(mHolder);
}
~AsyncTaskBase()
{
MOZ_ASSERT(!mHolder);
}
void DestroyHolder()
{
MOZ_ASSERT(mHolder);
mHolder.reset();
}
public:
UniquePtr<AsyncTaskWorkerHolder> StealHolder()
{
return Move(mHolder);
}
};
class AsyncTaskRunnable final : public AsyncTaskBase<WorkerRunnable>
{
JS::AsyncTask* mTask;
~AsyncTaskRunnable()
{
MOZ_ASSERT(!mTask);
}
void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{
// For the benefit of the destructor assert.
if (!aDispatchResult) {
mTask = nullptr;
}
}
public:
AsyncTaskRunnable(UniquePtr<AsyncTaskWorkerHolder> aHolder,
JS::AsyncTask* aTask)
: AsyncTaskBase<WorkerRunnable>(Move(aHolder))
, mTask(aTask)
{
MOZ_ASSERT(mTask);
}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext());
MOZ_ASSERT(mTask);
AutoJSAPI jsapi;
jsapi.Init();
mTask->finish(mWorkerPrivate->GetJSContext());
mTask = nullptr; // mTask may delete itself
DestroyHolder();
return true;
}
nsresult Cancel() override
{
MOZ_ASSERT(mTask);
AutoJSAPI jsapi;
jsapi.Init();
mTask->cancel(mWorkerPrivate->GetJSContext());
mTask = nullptr; // mTask may delete itself
DestroyHolder();
return WorkerRunnable::Cancel();
}
};
class AsyncTaskControlRunnable final
: public AsyncTaskBase<WorkerControlRunnable>
{
public:
explicit AsyncTaskControlRunnable(UniquePtr<AsyncTaskWorkerHolder> aHolder)
: AsyncTaskBase<WorkerControlRunnable>(Move(aHolder))
{ }
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
// See comment in FinishAsyncTaskCallback.
DestroyHolder();
return true;
}
};
static bool
StartAsyncTaskCallback(JSContext* aCx, JS::AsyncTask* aTask)
{
WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
worker->AssertIsOnWorkerThread();
auto holder = MakeUnique<AsyncTaskWorkerHolder>();
if (!holder->HoldWorker(worker, Status::Closing)) {
return false;
}
// Matched by a UniquePtr in FinishAsyncTaskCallback which, by
// interface contract, must be called in the future.
aTask->user = holder.release();
return true;
}
static bool
FinishAsyncTaskCallback(JS::AsyncTask* aTask)
{
// May execute either on the worker thread or a random JS-internal helper
// thread.
// Match the release() in StartAsyncTaskCallback.
UniquePtr<AsyncTaskWorkerHolder> holder(
static_cast<AsyncTaskWorkerHolder*>(aTask->user));
RefPtr<AsyncTaskRunnable> r = new AsyncTaskRunnable(Move(holder), aTask);
// WorkerRunnable::Dispatch() can fail during worker shutdown. In that case,
// report failure back to the JS engine but make sure to release the
// WorkerHolder on the worker thread using a control runnable. Control
// runables aren't suitable for calling AsyncTask::finish() since they are run
// via the interrupt callback which breaks JS run-to-completion.
if (!r->Dispatch()) {
RefPtr<AsyncTaskControlRunnable> cr =
new AsyncTaskControlRunnable(r->StealHolder());
MOZ_ALWAYS_TRUE(cr->Dispatch());
return false;
}
return true;
}
class WorkerJSRuntime;
class WorkerThreadContextPrivate : private PerThreadAtomCache
@ -808,6 +987,8 @@ InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSContext* aWorkerCx)
};
JS::SetAsmJSCacheOps(aWorkerCx, &asmJSCacheOps);
JS::SetAsyncTaskCallbacks(aWorkerCx, StartAsyncTaskCallback, FinishAsyncTaskCallback);
if (!JS::InitSelfHostedCode(aWorkerCx)) {
NS_WARNING("Could not init self-hosted code!");
return false;