Bug 1341321 - Require runtimes to be single threaded when using a Debugger, r=jandem.

--HG--
extra : rebase_source : 42f88769ddd36082339664cad86acd1f6dff5d67
This commit is contained in:
Brian Hackett 2017-03-01 07:15:50 -07:00
Родитель b43c07a10c
Коммит deb1eaae99
8 изменённых файлов: 194 добавлений и 31 удалений

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

@ -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.