mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1357958 - Move the JS shell's Promise job handling into the engine to be used as a default implementation. r=jandem
The shell has a very basic implementation of Promise job queue handling. This patch moves it into the engine, exposed through friendapi functions. The motivation is that I want to write JSAPI tests for streams, which requires Promise handling. The test harness would need essentially a copy of the shell's Promise handling, which isn't nice. To be clear, the default implementation isn't used automatically: the embedding has to explicitly request it using js::UseInternalJobQueues. MozReview-Commit-ID: 6bZ5VG5mJKV
This commit is contained in:
parent
cdc033857b
commit
be699bc7bf
4
js/src/jit-test/tests/promise/stopdrainingjobqueue.js
Normal file
4
js/src/jit-test/tests/promise/stopdrainingjobqueue.js
Normal file
@ -0,0 +1,4 @@
|
||||
Promise.resolve()
|
||||
.then(()=>quit(0));
|
||||
Promise.resolve()
|
||||
.then(()=>crash("Must not run any more promise jobs after quitting"));
|
@ -89,3 +89,81 @@ BEGIN_TEST(testPromise_RejectPromise)
|
||||
return true;
|
||||
}
|
||||
END_TEST(testPromise_RejectPromise)
|
||||
|
||||
static bool thenHandler_called = false;
|
||||
|
||||
static bool
|
||||
PromiseThenHandler(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
#endif // DEBUG
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
|
||||
thenHandler_called = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool catchHandler_called = false;
|
||||
|
||||
static bool
|
||||
PromiseCatchHandler(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
#endif // DEBUG
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
|
||||
catchHandler_called = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
BEGIN_TEST(testPromise_PromiseThen)
|
||||
{
|
||||
RootedObject promise(cx, CreatePromise(cx));
|
||||
if (!promise)
|
||||
return false;
|
||||
|
||||
RootedFunction thenHandler(cx, JS_NewFunction(cx, PromiseThenHandler, 1, 0, "thenHandler"));
|
||||
if (!thenHandler)
|
||||
return false;
|
||||
RootedFunction catchHandler(cx, JS_NewFunction(cx, PromiseCatchHandler, 1, 0, "catchHandler"));
|
||||
if (!catchHandler)
|
||||
return false;
|
||||
JS::AddPromiseReactions(cx, promise, thenHandler, catchHandler);
|
||||
|
||||
RootedValue result(cx);
|
||||
result.setInt32(42);
|
||||
JS::ResolvePromise(cx, promise, result);
|
||||
js::RunJobs(cx);
|
||||
|
||||
CHECK(thenHandler_called);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testPromise_PromiseThen)
|
||||
|
||||
BEGIN_TEST(testPromise_PromiseCatch)
|
||||
{
|
||||
RootedObject promise(cx, CreatePromise(cx));
|
||||
if (!promise)
|
||||
return false;
|
||||
|
||||
RootedFunction thenHandler(cx, JS_NewFunction(cx, PromiseThenHandler, 1, 0, "thenHandler"));
|
||||
if (!thenHandler)
|
||||
return false;
|
||||
RootedFunction catchHandler(cx, JS_NewFunction(cx, PromiseCatchHandler, 1, 0, "catchHandler"));
|
||||
if (!catchHandler)
|
||||
return false;
|
||||
JS::AddPromiseReactions(cx, promise, thenHandler, catchHandler);
|
||||
|
||||
RootedValue result(cx);
|
||||
result.setInt32(42);
|
||||
JS::RejectPromise(cx, promise, result);
|
||||
js::RunJobs(cx);
|
||||
|
||||
CHECK(catchHandler_called);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testPromise_PromiseCatch)
|
||||
|
@ -18,6 +18,7 @@ bool JSAPITest::init()
|
||||
cx = createContext();
|
||||
if (!cx)
|
||||
return false;
|
||||
js::UseInternalJobQueues(cx);
|
||||
if (!JS::InitSelfHostedCode(cx))
|
||||
return false;
|
||||
JS_BeginRequest(cx);
|
||||
|
@ -208,6 +208,20 @@ js::ResumeCooperativeContext(JSContext* cx)
|
||||
cx->runtime()->setActiveContext(cx);
|
||||
}
|
||||
|
||||
static void
|
||||
FreeJobQueueHandling(JSContext* cx)
|
||||
{
|
||||
if (!cx->jobQueue)
|
||||
return;
|
||||
|
||||
cx->jobQueue->reset();
|
||||
FreeOp* fop = cx->defaultFreeOp();
|
||||
fop->delete_(cx->jobQueue.ref());
|
||||
cx->getIncumbentGlobalCallback = nullptr;
|
||||
cx->enqueuePromiseJobCallback = nullptr;
|
||||
cx->enqueuePromiseJobCallbackData = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
js::DestroyContext(JSContext* cx)
|
||||
{
|
||||
@ -224,6 +238,8 @@ js::DestroyContext(JSContext* cx)
|
||||
// zone group. See HelperThread::handleIonWorkload.
|
||||
CancelOffThreadIonCompile(cx->runtime());
|
||||
|
||||
FreeJobQueueHandling(cx);
|
||||
|
||||
if (cx->runtime()->cooperatingContexts().length() == 1) {
|
||||
// Destroy the runtime along with its last context.
|
||||
cx->runtime()->destroyRuntime();
|
||||
@ -1094,6 +1110,184 @@ JSContext::recoverFromOutOfMemory()
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
InternalEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job,
|
||||
JS::HandleObject allocationSite,
|
||||
JS::HandleObject incumbentGlobal, void* data)
|
||||
{
|
||||
MOZ_ASSERT(job);
|
||||
return cx->jobQueue->append(job);
|
||||
}
|
||||
|
||||
static bool
|
||||
InternalStartAsyncTaskCallback(JSContext* cx, JS::AsyncTask* task)
|
||||
{
|
||||
task->user = cx;
|
||||
|
||||
ExclusiveData<InternalAsyncTasks>::Guard asyncTasks = cx->asyncTasks.lock();
|
||||
asyncTasks->outstanding++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
InternalFinishAsyncTaskCallback(JS::AsyncTask* task)
|
||||
{
|
||||
JSContext* cx = (JSContext*)task->user;
|
||||
|
||||
ExclusiveData<InternalAsyncTasks>::Guard asyncTasks = cx->asyncTasks.lock();
|
||||
MOZ_ASSERT(asyncTasks->outstanding > 0);
|
||||
asyncTasks->outstanding--;
|
||||
return asyncTasks->finished.append(task);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class MOZ_STACK_CLASS ReportExceptionClosure : public ScriptEnvironmentPreparer::Closure
|
||||
{
|
||||
public:
|
||||
explicit ReportExceptionClosure(HandleValue exn)
|
||||
: exn_(exn)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator()(JSContext* cx) override
|
||||
{
|
||||
cx->setPendingException(exn_);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
HandleValue exn_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
js::UseInternalJobQueues(JSContext* cx)
|
||||
{
|
||||
// Internal job queue handling must be set up very early. Self-hosting
|
||||
// initialization is as good a marker for that as any.
|
||||
MOZ_RELEASE_ASSERT(!cx->runtime()->hasInitializedSelfHosting(),
|
||||
"js::UseInternalJobQueues must be called early during runtime startup.");
|
||||
MOZ_ASSERT(!cx->jobQueue);
|
||||
auto* queue = cx->new_<PersistentRooted<JobQueue>>(cx, JobQueue(SystemAllocPolicy()));
|
||||
if (!queue)
|
||||
return false;
|
||||
|
||||
cx->jobQueue = queue;
|
||||
|
||||
JS::SetEnqueuePromiseJobCallback(cx, InternalEnqueuePromiseJobCallback);
|
||||
JS::SetAsyncTaskCallbacks(cx, InternalStartAsyncTaskCallback, InternalFinishAsyncTaskCallback);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(void)
|
||||
js::StopDrainingJobQueue(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->jobQueue);
|
||||
cx->stopDrainingJobQueue = true;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(void)
|
||||
js::RunJobs(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->jobQueue);
|
||||
|
||||
if (cx->drainingJobQueue || cx->stopDrainingJobQueue)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
// Wait for any outstanding async tasks to finish so that the
|
||||
// finishedAsyncTasks list is fixed.
|
||||
while (true) {
|
||||
AutoLockHelperThreadState lock;
|
||||
if (!cx->asyncTasks.lock()->outstanding)
|
||||
break;
|
||||
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
|
||||
}
|
||||
|
||||
// Lock the whole time while copying back the asyncTasks finished queue
|
||||
// so that any new tasks created during finish() cannot racily join the
|
||||
// job queue. Call finish() only thereafter, to avoid a circular mutex
|
||||
// dependency (see also bug 1297901).
|
||||
Vector<JS::AsyncTask*, 0, SystemAllocPolicy> finished;
|
||||
{
|
||||
ExclusiveData<InternalAsyncTasks>::Guard asyncTasks = cx->asyncTasks.lock();
|
||||
finished = Move(asyncTasks->finished);
|
||||
asyncTasks->finished.clear();
|
||||
}
|
||||
|
||||
for (JS::AsyncTask* task : finished)
|
||||
task->finish(cx);
|
||||
|
||||
// It doesn't make sense for job queue draining to be reentrant. At the
|
||||
// same time we don't want to assert against it, because that'd make
|
||||
// drainJobQueue unsafe for fuzzers. We do want fuzzers to test this,
|
||||
// so we simply ignore nested calls of drainJobQueue.
|
||||
cx->drainingJobQueue = true;
|
||||
|
||||
RootedObject job(cx);
|
||||
JS::HandleValueArray args(JS::HandleValueArray::empty());
|
||||
RootedValue rval(cx);
|
||||
|
||||
// Execute jobs in a loop until we've reached the end of the queue.
|
||||
// Since executing a job can trigger enqueuing of additional jobs,
|
||||
// it's crucial to re-check the queue length during each iteration.
|
||||
for (size_t i = 0; i < cx->jobQueue->length(); i++) {
|
||||
// A previous job might have set this flag. E.g., the js shell
|
||||
// sets it if the `quit` builtin function is called.
|
||||
if (cx->stopDrainingJobQueue)
|
||||
break;
|
||||
|
||||
job = cx->jobQueue->get()[i];
|
||||
|
||||
// It's possible that queue draining was interrupted prematurely,
|
||||
// leaving the queue partly processed. In that case, slots for
|
||||
// already-executed entries will contain nullptrs, which we should
|
||||
// just skip.
|
||||
if (!job)
|
||||
continue;
|
||||
|
||||
cx->jobQueue->get()[i] = nullptr;
|
||||
AutoCompartment ac(cx, job);
|
||||
{
|
||||
if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval)) {
|
||||
// Nothing we can do about uncatchable exceptions.
|
||||
if (!cx->isExceptionPending())
|
||||
continue;
|
||||
RootedValue exn(cx);
|
||||
if (cx->getPendingException(&exn)) {
|
||||
/*
|
||||
* Clear the exception, because
|
||||
* PrepareScriptEnvironmentAndInvoke will assert that we don't
|
||||
* have one.
|
||||
*/
|
||||
cx->clearPendingException();
|
||||
ReportExceptionClosure reportExn(exn);
|
||||
PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx->drainingJobQueue = false;
|
||||
|
||||
if (cx->stopDrainingJobQueue) {
|
||||
cx->stopDrainingJobQueue = false;
|
||||
break;
|
||||
}
|
||||
|
||||
cx->jobQueue->clear();
|
||||
|
||||
// It's possible a job added an async task, and it's also possible
|
||||
// that task has already finished.
|
||||
{
|
||||
ExclusiveData<InternalAsyncTasks>::Guard asyncTasks = cx->asyncTasks.lock();
|
||||
if (asyncTasks->outstanding == 0 && asyncTasks->finished.length() == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JS::Error JSContext::reportedError;
|
||||
JS::OOM JSContext::reportedOOM;
|
||||
|
||||
@ -1204,6 +1398,10 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options)
|
||||
getIncumbentGlobalCallback(nullptr),
|
||||
enqueuePromiseJobCallback(nullptr),
|
||||
enqueuePromiseJobCallbackData(nullptr),
|
||||
jobQueue(nullptr),
|
||||
drainingJobQueue(false),
|
||||
stopDrainingJobQueue(false),
|
||||
asyncTasks(mutexid::InternalAsyncTasks),
|
||||
promiseRejectionTrackerCallback(nullptr),
|
||||
promiseRejectionTrackerCallbackData(nullptr)
|
||||
{
|
||||
|
@ -68,6 +68,25 @@ struct AutoResolving;
|
||||
|
||||
struct HelperThread;
|
||||
|
||||
using JobQueue = GCVector<JSObject*, 0, SystemAllocPolicy>;
|
||||
|
||||
class AutoLockForExclusiveAccess;
|
||||
|
||||
/*
|
||||
* Used for engine-internal handling of async tasks, as currently
|
||||
* enabled in the js shell and jsapi tests.
|
||||
*/
|
||||
struct InternalAsyncTasks
|
||||
{
|
||||
explicit InternalAsyncTasks()
|
||||
: outstanding(0),
|
||||
finished()
|
||||
{}
|
||||
|
||||
size_t outstanding;
|
||||
Vector<JS::AsyncTask*, 0, SystemAllocPolicy> finished;
|
||||
};
|
||||
|
||||
void ReportOverRecursed(JSContext* cx, unsigned errorNumber);
|
||||
|
||||
/* Thread Local Storage slot for storing the context for a thread. */
|
||||
@ -907,6 +926,14 @@ struct JSContext : public JS::RootingContext,
|
||||
js::ThreadLocalData<JSEnqueuePromiseJobCallback> enqueuePromiseJobCallback;
|
||||
js::ThreadLocalData<void*> enqueuePromiseJobCallbackData;
|
||||
|
||||
// Queue of pending jobs as described in ES2016 section 8.4.
|
||||
// Only used if internal job queue handling was activated using
|
||||
// `js::UseInternalJobQueues`.
|
||||
js::ThreadLocalData<JS::PersistentRooted<js::JobQueue>*> jobQueue;
|
||||
js::ThreadLocalData<bool> drainingJobQueue;
|
||||
js::ThreadLocalData<bool> stopDrainingJobQueue;
|
||||
js::ExclusiveData<js::InternalAsyncTasks> asyncTasks;
|
||||
|
||||
js::ThreadLocalData<JSPromiseRejectionTrackerCallback> promiseRejectionTrackerCallback;
|
||||
js::ThreadLocalData<void*> promiseRejectionTrackerCallbackData;
|
||||
|
||||
|
@ -382,6 +382,33 @@ SetSourceHook(JSContext* cx, mozilla::UniquePtr<SourceHook> hook);
|
||||
extern JS_FRIEND_API(mozilla::UniquePtr<SourceHook>)
|
||||
ForgetSourceHook(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Use the runtime's internal handling of job queues for Promise jobs.
|
||||
*
|
||||
* Most embeddings, notably web browsers, will have their own task scheduling
|
||||
* systems and need to integrate handling of Promise jobs into that, so they
|
||||
* will want to manage job queues themselves. For basic embeddings such as the
|
||||
* JS shell that don't have an event loop of their own, it's easier to have
|
||||
* SpiderMonkey handle job queues internally.
|
||||
*
|
||||
* Note that the embedding still has to trigger processing of job queues at
|
||||
* right time(s), such as after evaluation of a script has run to completion.
|
||||
*/
|
||||
extern JS_FRIEND_API(bool)
|
||||
UseInternalJobQueues(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Instruct the runtime to stop draining the internal job queue.
|
||||
*
|
||||
* Useful if the embedding is in the process of quitting in reaction to a
|
||||
* builtin being called, or if it wants to resume executing jobs later on.
|
||||
*/
|
||||
extern JS_FRIEND_API(void)
|
||||
StopDrainingJobQueue(JSContext* cx);
|
||||
|
||||
extern JS_FRIEND_API(void)
|
||||
RunJobs(JSContext* cx);
|
||||
|
||||
extern JS_FRIEND_API(JS::Zone*)
|
||||
GetCompartmentZone(JSCompartment* comp);
|
||||
|
||||
|
@ -157,19 +157,6 @@ static const TimeDuration MAX_TIMEOUT_INTERVAL = TimeDuration::FromSeconds(1800.
|
||||
# define SINGLESTEP_PROFILING
|
||||
#endif
|
||||
|
||||
using JobQueue = GCVector<JSObject*, 0, SystemAllocPolicy>;
|
||||
|
||||
struct ShellAsyncTasks
|
||||
{
|
||||
explicit ShellAsyncTasks(JSContext* cx)
|
||||
: outstanding(0),
|
||||
finished(cx)
|
||||
{}
|
||||
|
||||
size_t outstanding;
|
||||
Vector<JS::AsyncTask*> finished;
|
||||
};
|
||||
|
||||
enum class ScriptKind
|
||||
{
|
||||
Script,
|
||||
@ -318,9 +305,6 @@ struct ShellContext
|
||||
bool lastWarningEnabled;
|
||||
JS::PersistentRootedValue lastWarning;
|
||||
JS::PersistentRootedValue promiseRejectionTrackerCallback;
|
||||
JS::PersistentRooted<JobQueue> jobQueue;
|
||||
ExclusiveData<ShellAsyncTasks> asyncTasks;
|
||||
bool drainingJobQueue;
|
||||
#ifdef SINGLESTEP_PROFILING
|
||||
Vector<StackChars, 0, SystemAllocPolicy> stacks;
|
||||
#endif
|
||||
@ -488,8 +472,6 @@ ShellContext::ShellContext(JSContext* cx)
|
||||
lastWarningEnabled(false),
|
||||
lastWarning(cx, NullValue()),
|
||||
promiseRejectionTrackerCallback(cx, NullValue()),
|
||||
asyncTasks(mutexid::ShellAsyncTasks, cx),
|
||||
drainingJobQueue(false),
|
||||
watchdogLock(mutexid::ShellContextWatchdog),
|
||||
exitCode(0),
|
||||
quitting(false),
|
||||
@ -816,115 +798,13 @@ RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
|
||||
return JS_CallFunction(cx, loaderObj, importFun, args, &value);
|
||||
}
|
||||
|
||||
static JSObject*
|
||||
ShellGetIncumbentGlobalCallback(JSContext* cx)
|
||||
{
|
||||
return JS::CurrentGlobalOrNull(cx);
|
||||
}
|
||||
|
||||
static bool
|
||||
ShellEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, JS::HandleObject allocationSite,
|
||||
JS::HandleObject incumbentGlobal, void* data)
|
||||
{
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
MOZ_ASSERT(job);
|
||||
return sc->jobQueue.append(job);
|
||||
}
|
||||
|
||||
static bool
|
||||
ShellStartAsyncTaskCallback(JSContext* cx, JS::AsyncTask* task)
|
||||
{
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
task->user = sc;
|
||||
|
||||
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
||||
asyncTasks->outstanding++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ShellFinishAsyncTaskCallback(JS::AsyncTask* task)
|
||||
{
|
||||
ShellContext* sc = (ShellContext*)task->user;
|
||||
|
||||
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
||||
MOZ_ASSERT(asyncTasks->outstanding > 0);
|
||||
asyncTasks->outstanding--;
|
||||
return asyncTasks->finished.append(task);
|
||||
}
|
||||
|
||||
static void
|
||||
DrainJobQueue(JSContext* cx)
|
||||
{
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
if (sc->quitting || sc->drainingJobQueue)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
// Wait for any outstanding async tasks to finish so that the
|
||||
// finishedAsyncTasks list is fixed.
|
||||
while (true) {
|
||||
AutoLockHelperThreadState lock;
|
||||
if (!sc->asyncTasks.lock()->outstanding)
|
||||
break;
|
||||
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
|
||||
}
|
||||
|
||||
// Lock the whole time while copying back the asyncTasks finished queue
|
||||
// so that any new tasks created during finish() cannot racily join the
|
||||
// job queue. Call finish() only thereafter, to avoid a circular mutex
|
||||
// dependency (see also bug 1297901).
|
||||
Vector<JS::AsyncTask*> finished(cx);
|
||||
{
|
||||
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
||||
finished = Move(asyncTasks->finished);
|
||||
asyncTasks->finished.clear();
|
||||
}
|
||||
|
||||
for (JS::AsyncTask* task : finished)
|
||||
task->finish(cx);
|
||||
|
||||
// It doesn't make sense for job queue draining to be reentrant. At the
|
||||
// same time we don't want to assert against it, because that'd make
|
||||
// drainJobQueue unsafe for fuzzers. We do want fuzzers to test this,
|
||||
// so we simply ignore nested calls of drainJobQueue.
|
||||
sc->drainingJobQueue = true;
|
||||
|
||||
RootedObject job(cx);
|
||||
JS::HandleValueArray args(JS::HandleValueArray::empty());
|
||||
RootedValue rval(cx);
|
||||
|
||||
// Execute jobs in a loop until we've reached the end of the queue.
|
||||
// Since executing a job can trigger enqueuing of additional jobs,
|
||||
// it's crucial to re-check the queue length during each iteration.
|
||||
for (size_t i = 0; i < sc->jobQueue.length(); i++) {
|
||||
job = sc->jobQueue[i];
|
||||
AutoCompartment ac(cx, job);
|
||||
{
|
||||
AutoReportException are(cx);
|
||||
JS::Call(cx, UndefinedHandleValue, job, args, &rval);
|
||||
}
|
||||
sc->jobQueue[i].set(nullptr);
|
||||
}
|
||||
sc->jobQueue.clear();
|
||||
sc->drainingJobQueue = false;
|
||||
|
||||
// It's possible a job added an async task, and it's also possible
|
||||
// that task has already finished.
|
||||
{
|
||||
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
||||
if (asyncTasks->outstanding == 0 && asyncTasks->finished.length() == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
DrainJobQueue(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
DrainJobQueue(cx);
|
||||
MOZ_ASSERT(!GetShellContext(cx)->quitting);
|
||||
js::RunJobs(cx);
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
@ -1107,7 +987,8 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
|
||||
stderr);
|
||||
}
|
||||
|
||||
DrainJobQueue(cx);
|
||||
if (!GetShellContext(cx)->quitting)
|
||||
js::RunJobs(cx);
|
||||
|
||||
} while (!hitEOF && !sc->quitting);
|
||||
|
||||
@ -2287,6 +2168,7 @@ Quit(JSContext* cx, unsigned argc, Value* vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
js::StopDrainingJobQueue(cx);
|
||||
sc->exitCode = code;
|
||||
sc->quitting = true;
|
||||
return false;
|
||||
@ -3586,9 +3468,7 @@ WorkerMain(void* arg)
|
||||
if (input->parentRuntime)
|
||||
sc->isWorker = true;
|
||||
JS_SetContextPrivate(cx, sc.get());
|
||||
JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, nullptr);
|
||||
SetWorkerContextOptions(cx);
|
||||
sc->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
|
||||
|
||||
Maybe<EnvironmentPreparer> environmentPreparer;
|
||||
if (input->parentRuntime) {
|
||||
@ -3597,13 +3477,11 @@ WorkerMain(void* arg)
|
||||
js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
|
||||
JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
|
||||
|
||||
js::UseInternalJobQueues(cx);
|
||||
|
||||
if (!JS::InitSelfHostedCode(cx))
|
||||
return;
|
||||
|
||||
JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
|
||||
JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
|
||||
JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
|
||||
|
||||
environmentPreparer.emplace(cx);
|
||||
} else {
|
||||
JS_AddInterruptCallback(cx, ShellInterruptCallback);
|
||||
@ -3635,13 +3513,6 @@ WorkerMain(void* arg)
|
||||
JS_ExecuteScript(cx, script, &result);
|
||||
} while (0);
|
||||
|
||||
if (input->parentRuntime) {
|
||||
JS::SetGetIncumbentGlobalCallback(cx, nullptr);
|
||||
JS::SetEnqueuePromiseJobCallback(cx, nullptr);
|
||||
}
|
||||
|
||||
sc->jobQueue.reset();
|
||||
|
||||
KillWatchdog(cx);
|
||||
JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
|
||||
}
|
||||
@ -8290,7 +8161,8 @@ Shell(JSContext* cx, OptionParser* op, char** envp)
|
||||
* tasks before the main thread JSRuntime is torn down. Drain after
|
||||
* uncaught exceptions have been reported since draining runs callbacks.
|
||||
*/
|
||||
DrainJobQueue(cx);
|
||||
if (!GetShellContext(cx)->quitting)
|
||||
js::RunJobs(cx);
|
||||
|
||||
if (sc->exitCode)
|
||||
result = sc->exitCode;
|
||||
@ -8647,14 +8519,11 @@ main(int argc, char** argv, char** envp)
|
||||
|
||||
JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
|
||||
|
||||
js::UseInternalJobQueues(cx);
|
||||
|
||||
if (!JS::InitSelfHostedCode(cx))
|
||||
return 1;
|
||||
|
||||
sc->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
|
||||
JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
|
||||
JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
|
||||
JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
|
||||
|
||||
EnvironmentPreparer environmentPreparer(cx);
|
||||
|
||||
JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
|
||||
@ -8686,13 +8555,10 @@ main(int argc, char** argv, char** envp)
|
||||
printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
|
||||
#endif
|
||||
|
||||
JS::SetGetIncumbentGlobalCallback(cx, nullptr);
|
||||
JS::SetEnqueuePromiseJobCallback(cx, nullptr);
|
||||
JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
|
||||
|
||||
// Must clear out some of sc's pointer containers before JS_DestroyContext.
|
||||
sc->markObservers.reset();
|
||||
sc->jobQueue.reset();
|
||||
|
||||
KillWatchdog(cx);
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
\
|
||||
_(GlobalHelperThreadState, 300) \
|
||||
\
|
||||
_(ShellAsyncTasks, 350) \
|
||||
_(InternalAsyncTasks, 350) \
|
||||
\
|
||||
_(GCLock, 400) \
|
||||
\
|
||||
|
@ -694,8 +694,13 @@ FreeOp::isDefaultFreeOp() const
|
||||
JSObject*
|
||||
JSRuntime::getIncumbentGlobal(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->getIncumbentGlobalCallback,
|
||||
"Must set a callback using SetGetIncumbentGlobalCallback before using Promises");
|
||||
// If the embedding didn't set a callback for getting the incumbent
|
||||
// global, the currently active global is used.
|
||||
if (!cx->getIncumbentGlobalCallback) {
|
||||
if (!cx->compartment())
|
||||
return nullptr;
|
||||
return cx->global();
|
||||
}
|
||||
|
||||
return cx->getIncumbentGlobalCallback(cx);
|
||||
}
|
||||
|
@ -286,6 +286,7 @@ void DisableExtraThreads();
|
||||
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
|
||||
|
||||
class AutoLockForExclusiveAccess;
|
||||
|
||||
} // namespace js
|
||||
|
||||
struct JSRuntime : public js::MallocProvider<JSRuntime>
|
||||
|
Loading…
Reference in New Issue
Block a user