зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1341321
- Require runtimes to be single threaded when using a Debugger, r=jandem.
--HG-- extra : rebase_source : 42f88769ddd36082339664cad86acd1f6dff5d67
This commit is contained in:
Родитель
b43c07a10c
Коммит
deb1eaae99
|
@ -0,0 +1,20 @@
|
|||
if (helperThreadCount() == 0)
|
||||
quit();
|
||||
|
||||
// The new Debugger here should throw but we don't have a way to verify this
|
||||
// (exceptions that worker threads throw do not cause the test to fail).
|
||||
evalInCooperativeThread('cooperativeYield(); var dbg = new Debugger();');
|
||||
|
||||
var dbg = new Debugger;
|
||||
assertEq(dbg.addAllGlobalsAsDebuggees(), undefined);
|
||||
|
||||
function assertThrows(f) {
|
||||
var exception = false;
|
||||
try { f(); } catch (e) { exception = true; }
|
||||
assertEq(exception, true);
|
||||
}
|
||||
|
||||
var dbg = new Debugger;
|
||||
dbg.onNewGlobalObject = function(global) {};
|
||||
assertThrows(() => evalInCooperativeThread("var x = 3"));
|
||||
assertThrows(cooperativeYield);
|
|
@ -565,6 +565,15 @@ JS_GetParentRuntime(JSContext* cx)
|
|||
return cx->runtime()->parentRuntime ? cx->runtime()->parentRuntime : cx->runtime();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::SetSingleThreadedExecutionCallbacks(JSContext* cx,
|
||||
BeginSingleThreadedExecutionCallback begin,
|
||||
EndSingleThreadedExecutionCallback end)
|
||||
{
|
||||
cx->runtime()->beginSingleThreadedExecutionCallback = begin;
|
||||
cx->runtime()->endSingleThreadedExecutionCallback = end;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSVersion)
|
||||
JS_GetVersion(JSContext* cx)
|
||||
{
|
||||
|
|
|
@ -1033,6 +1033,31 @@ JS_EndRequest(JSContext* cx);
|
|||
extern JS_PUBLIC_API(void)
|
||||
JS_SetFutexCanWait(JSContext* cx);
|
||||
|
||||
namespace JS {
|
||||
|
||||
// Single threaded execution callbacks are used to notify API clients that a
|
||||
// feature is in use on a context's runtime that is not yet compatible with
|
||||
// cooperatively multithreaded execution.
|
||||
//
|
||||
// Between a call to BeginSingleThreadedExecutionCallback and a corresponding
|
||||
// call to EndSingleThreadedExecutionCallback, only one thread at a time may
|
||||
// enter compartments in the runtime. The begin callback may yield as necessary
|
||||
// to permit other threads to finish up what they're doing, while the end
|
||||
// callback may not yield or otherwise operate on the runtime (it may be called
|
||||
// during GC).
|
||||
//
|
||||
// These callbacks may be left unspecified for runtimes which only ever have a
|
||||
// single context.
|
||||
typedef void (*BeginSingleThreadedExecutionCallback)(JSContext* cx);
|
||||
typedef void (*EndSingleThreadedExecutionCallback)(JSContext* cx);
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
SetSingleThreadedExecutionCallbacks(JSContext* cx,
|
||||
BeginSingleThreadedExecutionCallback begin,
|
||||
EndSingleThreadedExecutionCallback end);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
namespace js {
|
||||
|
||||
void
|
||||
|
|
|
@ -1164,9 +1164,8 @@ JSCompartment::updateDebuggerObservesCoverage()
|
|||
|
||||
if (debuggerObservesCoverage()) {
|
||||
// Interrupt any running interpreter frame. The scriptCounts are
|
||||
// allocated on demand when a script resume its execution.
|
||||
// allocated on demand when a script resumes its execution.
|
||||
JSContext* cx = TlsContext.get();
|
||||
MOZ_ASSERT(zone()->group()->ownedByCurrentThread());
|
||||
for (ActivationIterator iter(cx); !iter.done(); ++iter) {
|
||||
if (iter->isInterpreter())
|
||||
iter->asInterpreter()->enableInterruptsUnconditionally();
|
||||
|
|
|
@ -3384,6 +3384,7 @@ struct CooperationState
|
|||
, idle(false)
|
||||
, numThreads(0)
|
||||
, yieldCount(0)
|
||||
, singleThreaded(false)
|
||||
{}
|
||||
|
||||
Mutex lock;
|
||||
|
@ -3391,6 +3392,7 @@ struct CooperationState
|
|||
bool idle;
|
||||
size_t numThreads;
|
||||
uint64_t yieldCount;
|
||||
bool singleThreaded;
|
||||
};
|
||||
static CooperationState* cooperationState = nullptr;
|
||||
|
||||
|
@ -3436,8 +3438,8 @@ CooperativeYieldThread(JSContext* cx, unsigned argc, Value* vp)
|
|||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!cooperationState) {
|
||||
JS_ReportErrorASCII(cx, "No cooperative threads have been created");
|
||||
if (!cx->runtime()->gc.canChangeActiveContext(cx)) {
|
||||
JS_ReportErrorASCII(cx, "Cooperating multithreading context switches are not currently allowed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3446,6 +3448,11 @@ CooperativeYieldThread(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (cooperationState->singleThreaded) {
|
||||
JS_ReportErrorASCII(cx, "Yielding is not allowed while single threaded");
|
||||
return false;
|
||||
}
|
||||
|
||||
CooperativeBeginWait(cx);
|
||||
CooperativeYield();
|
||||
CooperativeEndWait(cx);
|
||||
|
@ -3454,6 +3461,35 @@ CooperativeYieldThread(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
CooperativeBeginSingleThreadedExecution(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(!cooperationState->singleThreaded);
|
||||
|
||||
// Yield until all other threads have exited any zone groups they are in.
|
||||
while (true) {
|
||||
bool done = true;
|
||||
for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
|
||||
if (!group->ownedByCurrentThread() && group->ownerContext().context())
|
||||
done = false;
|
||||
}
|
||||
if (done)
|
||||
break;
|
||||
CooperativeBeginWait(cx);
|
||||
CooperativeYield();
|
||||
CooperativeEndWait(cx);
|
||||
}
|
||||
|
||||
cooperationState->singleThreaded = true;
|
||||
}
|
||||
|
||||
static void
|
||||
CooperativeEndSingleThreadedExecution(JSContext* cx)
|
||||
{
|
||||
if (cooperationState)
|
||||
cooperationState->singleThreaded = false;
|
||||
}
|
||||
|
||||
struct WorkerInput
|
||||
{
|
||||
JSRuntime* parentRuntime;
|
||||
|
@ -3621,6 +3657,11 @@ EvalInThread(JSContext* cx, unsigned argc, Value* vp, bool cooperative)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (cooperative && cooperationState->singleThreaded) {
|
||||
JS_ReportErrorASCII(cx, "Creating cooperative threads is not allowed while single threaded");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!args[0].toString()->ensureLinear(cx))
|
||||
return false;
|
||||
|
||||
|
@ -3652,8 +3693,6 @@ EvalInThread(JSContext* cx, unsigned argc, Value* vp, bool cooperative)
|
|||
}
|
||||
|
||||
if (cooperative) {
|
||||
if (!cooperationState)
|
||||
cooperationState = js_new<CooperationState>();
|
||||
cooperationState->numThreads++;
|
||||
CooperativeBeginWait(cx);
|
||||
}
|
||||
|
@ -3900,7 +3939,7 @@ KillWorkerThreads(JSContext* cx)
|
|||
workerThreadsLock = nullptr;
|
||||
|
||||
// Yield until all other cooperative threads in the main runtime finish.
|
||||
while (cooperationState && cooperationState->numThreads) {
|
||||
while (cooperationState->numThreads) {
|
||||
CooperativeBeginWait(cx);
|
||||
CooperativeYield();
|
||||
CooperativeEndWait(cx);
|
||||
|
@ -8369,6 +8408,11 @@ main(int argc, char** argv, char** envp)
|
|||
|
||||
js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
|
||||
|
||||
cooperationState = js_new<CooperationState>();
|
||||
JS::SetSingleThreadedExecutionCallbacks(cx,
|
||||
CooperativeBeginSingleThreadedExecution,
|
||||
CooperativeEndSingleThreadedExecution);
|
||||
|
||||
result = Shell(cx, &op, envp);
|
||||
|
||||
#ifdef DEBUG
|
||||
|
|
|
@ -713,6 +713,9 @@ Debugger::~Debugger()
|
|||
* background finalized.
|
||||
*/
|
||||
JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink);
|
||||
|
||||
JSContext* cx = TlsContext.get();
|
||||
cx->runtime()->endSingleThreadedExecution(cx);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -2404,12 +2407,7 @@ class MOZ_RAII ExecutionObservableCompartments : public Debugger::ExecutionObser
|
|||
}
|
||||
|
||||
bool init() { return compartments_.init() && zones_.init(); }
|
||||
bool add(JSCompartment* comp) {
|
||||
// The current cx should have exclusive access to observed content,
|
||||
// since debuggees must be in the same zone group as ther debugger.
|
||||
MOZ_ASSERT(comp->zone()->group() == TlsContext.get()->zone()->group());
|
||||
return compartments_.put(comp) && zones_.put(comp->zone());
|
||||
}
|
||||
bool add(JSCompartment* comp) { return compartments_.put(comp) && zones_.put(comp->zone()); }
|
||||
|
||||
typedef HashSet<JSCompartment*>::Range CompartmentRange;
|
||||
const HashSet<JSCompartment*>* compartments() const { return &compartments_; }
|
||||
|
@ -2503,9 +2501,6 @@ class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableS
|
|||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: script_(cx, script)
|
||||
{
|
||||
// The current cx should have exclusive access to observed content,
|
||||
// since debuggees must be in the same zone group as ther debugger.
|
||||
MOZ_ASSERT(singleZone()->group() == cx->zone()->group());
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
|
@ -3936,11 +3931,24 @@ Debugger::construct(JSContext* cx, unsigned argc, Value* vp)
|
|||
obj->setReservedSlot(slot, proto->getReservedSlot(slot));
|
||||
obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
|
||||
|
||||
// Debuggers currently require single threaded execution. A debugger may be
|
||||
// used to debug content in other zone groups, and may be used to observe
|
||||
// all activity in the runtime via hooks like OnNewGlobalObject.
|
||||
if (!cx->runtime()->beginSingleThreadedExecution(cx)) {
|
||||
JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in Debugger");
|
||||
return false;
|
||||
}
|
||||
|
||||
Debugger* debugger;
|
||||
{
|
||||
/* Construct the underlying C++ object. */
|
||||
auto dbg = cx->make_unique<Debugger>(cx, obj.get());
|
||||
if (!dbg || !dbg->init(cx))
|
||||
if (!dbg) {
|
||||
JS::AutoSuppressGCAnalysis nogc; // Suppress warning about |dbg|.
|
||||
cx->runtime()->endSingleThreadedExecution(cx);
|
||||
return false;
|
||||
}
|
||||
if (!dbg->init(cx))
|
||||
return false;
|
||||
|
||||
debugger = dbg.release();
|
||||
|
@ -3962,13 +3970,6 @@ Debugger::construct(JSContext* cx, unsigned argc, Value* vp)
|
|||
bool
|
||||
Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global)
|
||||
{
|
||||
// Debuggers are required to be in the same zone group as their debuggees.
|
||||
// The debugger must be able to observe all activity in the debuggee
|
||||
// compartment, which requires that its thread have exclusive access to
|
||||
// that compartment's contents.
|
||||
MOZ_ASSERT(cx->zone() == object->zone());
|
||||
MOZ_RELEASE_ASSERT(global->zone()->group() == cx->zone()->group());
|
||||
|
||||
if (debuggees.has(global))
|
||||
return true;
|
||||
|
||||
|
|
|
@ -97,6 +97,10 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
|||
#endif
|
||||
activeContext_(nullptr),
|
||||
activeContextChangeProhibited_(0),
|
||||
singleThreadedExecutionRequired_(0),
|
||||
startingSingleThreadedExecution_(false),
|
||||
beginSingleThreadedExecutionCallback(nullptr),
|
||||
endSingleThreadedExecutionCallback(nullptr),
|
||||
profilerSampleBufferGen_(0),
|
||||
profilerSampleBufferLapCount_(1),
|
||||
telemetryCallback(nullptr),
|
||||
|
@ -308,6 +312,7 @@ JSRuntime::destroyRuntime()
|
|||
|
||||
MOZ_ASSERT(ionLazyLinkListSize_ == 0);
|
||||
MOZ_ASSERT(ionLazyLinkList().isEmpty());
|
||||
MOZ_ASSERT(!singleThreadedExecutionRequired_);
|
||||
|
||||
MOZ_ASSERT(!hasHelperThreadZones());
|
||||
AutoLockForExclusiveAccess lock(this);
|
||||
|
@ -334,12 +339,23 @@ JSRuntime::destroyRuntime()
|
|||
MOZ_ASSERT(oldCount > 0);
|
||||
}
|
||||
|
||||
static void
|
||||
CheckCanChangeActiveContext(JSRuntime* rt)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!rt->activeContextChangeProhibited());
|
||||
MOZ_RELEASE_ASSERT(!rt->activeContext() || rt->gc.canChangeActiveContext(rt->activeContext()));
|
||||
|
||||
if (rt->singleThreadedExecutionRequired()) {
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next())
|
||||
MOZ_RELEASE_ASSERT(group->ownerContext().context() == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::setActiveContext(JSContext* cx)
|
||||
{
|
||||
CheckCanChangeActiveContext(this);
|
||||
MOZ_ASSERT_IF(cx, cx->isCooperativelyScheduled());
|
||||
MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
|
||||
MOZ_RELEASE_ASSERT(!activeContext() || gc.canChangeActiveContext(activeContext()));
|
||||
|
||||
activeContext_ = cx;
|
||||
}
|
||||
|
@ -347,9 +363,7 @@ JSRuntime::setActiveContext(JSContext* cx)
|
|||
void
|
||||
JSRuntime::setNewbornActiveContext(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT_IF(cx, cx->isCooperativelyScheduled());
|
||||
MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
|
||||
MOZ_RELEASE_ASSERT(!activeContext());
|
||||
CheckCanChangeActiveContext(this);
|
||||
|
||||
activeContext_ = cx;
|
||||
|
||||
|
@ -361,14 +375,46 @@ JSRuntime::setNewbornActiveContext(JSContext* cx)
|
|||
void
|
||||
JSRuntime::deleteActiveContext(JSContext* cx)
|
||||
{
|
||||
CheckCanChangeActiveContext(this);
|
||||
MOZ_ASSERT(cx == activeContext());
|
||||
MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
|
||||
MOZ_RELEASE_ASSERT(gc.canChangeActiveContext(cx));
|
||||
|
||||
js_delete_poison(cx);
|
||||
activeContext_ = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::beginSingleThreadedExecution(JSContext* cx)
|
||||
{
|
||||
if (singleThreadedExecutionRequired_ == 0) {
|
||||
if (startingSingleThreadedExecution_)
|
||||
return false;
|
||||
startingSingleThreadedExecution_ = true;
|
||||
if (beginSingleThreadedExecutionCallback)
|
||||
beginSingleThreadedExecutionCallback(cx);
|
||||
MOZ_ASSERT(startingSingleThreadedExecution_);
|
||||
startingSingleThreadedExecution_ = false;
|
||||
}
|
||||
|
||||
singleThreadedExecutionRequired_++;
|
||||
|
||||
for (ZoneGroupsIter group(this); !group.done(); group.next()) {
|
||||
MOZ_RELEASE_ASSERT(group->ownedByCurrentThread() ||
|
||||
group->ownerContext().context() == nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::endSingleThreadedExecution(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(singleThreadedExecutionRequired_);
|
||||
if (--singleThreadedExecutionRequired_ == 0) {
|
||||
if (endSingleThreadedExecutionCallback)
|
||||
endSingleThreadedExecutionCallback(cx);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::addTelemetry(int id, uint32_t sample, const char* key)
|
||||
{
|
||||
|
|
|
@ -342,6 +342,14 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
|
|||
// Count of AutoProhibitActiveContextChange instances on the active context.
|
||||
mozilla::Atomic<size_t> activeContextChangeProhibited_;
|
||||
|
||||
// Count of beginSingleThreadedExecution() calls that have occurred with no
|
||||
// matching endSingleThreadedExecution().
|
||||
mozilla::Atomic<size_t> singleThreadedExecutionRequired_;
|
||||
|
||||
// Whether some thread has called beginSingleThreadedExecution() and we are
|
||||
// in the associated callback (which may execute JS on other threads).
|
||||
js::ActiveThreadData<bool> startingSingleThreadedExecution_;
|
||||
|
||||
public:
|
||||
JSContext* activeContext() const { return activeContext_; }
|
||||
const void* addressOfActiveContext() { return &activeContext_; }
|
||||
|
@ -374,6 +382,17 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
|
|||
};
|
||||
|
||||
bool activeContextChangeProhibited() { return activeContextChangeProhibited_; }
|
||||
bool singleThreadedExecutionRequired() { return singleThreadedExecutionRequired_; }
|
||||
|
||||
js::ActiveThreadData<JS::BeginSingleThreadedExecutionCallback> beginSingleThreadedExecutionCallback;
|
||||
js::ActiveThreadData<JS::EndSingleThreadedExecutionCallback> endSingleThreadedExecutionCallback;
|
||||
|
||||
// Ensure there is only a single thread interacting with this runtime.
|
||||
// beginSingleThreadedExecution() returns false if some context has already
|
||||
// started forcing this runtime to be single threaded. Calls to these
|
||||
// functions must be balanced.
|
||||
bool beginSingleThreadedExecution(JSContext* cx);
|
||||
void endSingleThreadedExecution(JSContext* cx);
|
||||
|
||||
/*
|
||||
* The profiler sampler generation after the latest sample.
|
||||
|
|
Загрузка…
Ссылка в новой задаче