Bug 1910021 - Using Mutex to protect ConsoleCallData. r=dom-worker-reviewers,smaug

Disabled dom/console/tests/xpcshell/test_failing_console_listener for tsan build.
Since the failing console mechanism causes too many additional deadlock checking then time out the test.

Differential Revision: https://phabricator.services.mozilla.com/D224922
This commit is contained in:
Eden Chuang 2024-10-30 12:42:08 +00:00
parent 2e7917e4d3
commit bfa9317a53
2 changed files with 96 additions and 75 deletions

View File

@ -33,6 +33,7 @@
#include "mozilla/HoldDropJSObjects.h" #include "mozilla/HoldDropJSObjects.h"
#include "mozilla/JSObjectHolder.h" #include "mozilla/JSObjectHolder.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_devtools.h" #include "mozilla/StaticPrefs_devtools.h"
#include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_dom.h"
@ -109,7 +110,8 @@ class ConsoleCallData final {
ConsoleCallData(Console::MethodName aName, const nsAString& aString, ConsoleCallData(Console::MethodName aName, const nsAString& aString,
Console* aConsole) Console* aConsole)
: mConsoleID(aConsole->mConsoleID), : mMutex("ConsoleCallData"),
mConsoleID(aConsole->mConsoleID),
mPrefix(aConsole->mPrefix), mPrefix(aConsole->mPrefix),
mMethodName(aName), mMethodName(aName),
mMicroSecondTimeStamp(JS_Now()), mMicroSecondTimeStamp(JS_Now()),
@ -123,7 +125,7 @@ class ConsoleCallData final {
mInnerIDNumber(0), mInnerIDNumber(0),
mMethodString(aString) {} mMethodString(aString) {}
void SetIDs(uint64_t aOuterID, uint64_t aInnerID) { void SetIDs(uint64_t aOuterID, uint64_t aInnerID) MOZ_REQUIRES(mMutex) {
MOZ_ASSERT(mIDType == eUnknown); MOZ_ASSERT(mIDType == eUnknown);
mOuterIDNumber = aOuterID; mOuterIDNumber = aOuterID;
@ -131,7 +133,8 @@ class ConsoleCallData final {
mIDType = eNumber; mIDType = eNumber;
} }
void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID) { void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
MOZ_REQUIRES(mMutex) {
MOZ_ASSERT(mIDType == eUnknown); MOZ_ASSERT(mIDType == eUnknown);
mOuterIDString = aOuterID; mOuterIDString = aOuterID;
@ -139,11 +142,12 @@ class ConsoleCallData final {
mIDType = eString; mIDType = eString;
} }
void SetOriginAttributes(const OriginAttributes& aOriginAttributes) { void SetOriginAttributes(const OriginAttributes& aOriginAttributes)
MOZ_REQUIRES(mMutex) {
mOriginAttributes = aOriginAttributes; mOriginAttributes = aOriginAttributes;
} }
void SetAddonId(nsIPrincipal* aPrincipal) { void SetAddonId(nsIPrincipal* aPrincipal) MOZ_REQUIRES(mMutex) {
nsAutoString addonId; nsAutoString addonId;
aPrincipal->GetAddonId(addonId); aPrincipal->GetAddonId(addonId);
@ -154,11 +158,13 @@ class ConsoleCallData final {
NS_ASSERT_OWNINGTHREAD(ConsoleCallData); NS_ASSERT_OWNINGTHREAD(ConsoleCallData);
} }
const nsString mConsoleID; Mutex mMutex;
const nsString mPrefix;
const Console::MethodName mMethodName; const nsString mConsoleID MOZ_GUARDED_BY(mMutex);
int64_t mMicroSecondTimeStamp; const nsString mPrefix MOZ_GUARDED_BY(mMutex);
const Console::MethodName mMethodName MOZ_GUARDED_BY(mMutex);
int64_t mMicroSecondTimeStamp MOZ_GUARDED_BY(mMutex);
// These values are set in the owning thread and they contain the timestamp of // These values are set in the owning thread and they contain the timestamp of
// when the new timer has started, the name of it and the status of the // when the new timer has started, the name of it and the status of the
@ -168,9 +174,9 @@ class ConsoleCallData final {
// They will be set on the owning thread and never touched again on that // They will be set on the owning thread and never touched again on that
// thread. They will be used in order to create a ConsoleTimerStart dictionary // thread. They will be used in order to create a ConsoleTimerStart dictionary
// when console.time() is used. // when console.time() is used.
DOMHighResTimeStamp mStartTimerValue; DOMHighResTimeStamp mStartTimerValue MOZ_GUARDED_BY(mMutex);
nsString mStartTimerLabel; nsString mStartTimerLabel MOZ_GUARDED_BY(mMutex);
Console::TimerStatus mStartTimerStatus; Console::TimerStatus mStartTimerStatus MOZ_GUARDED_BY(mMutex);
// These values are set in the owning thread and they contain the duration, // These values are set in the owning thread and they contain the duration,
// the name and the status of the LogTimer method. If status is false, // the name and the status of the LogTimer method. If status is false,
@ -178,16 +184,16 @@ class ConsoleCallData final {
// touched again on that thread. They will be used in order to create a // touched again on that thread. They will be used in order to create a
// ConsoleTimerLogOrEnd dictionary. This members are set when // ConsoleTimerLogOrEnd dictionary. This members are set when
// console.timeEnd() or console.timeLog() are called. // console.timeEnd() or console.timeLog() are called.
double mLogTimerDuration; double mLogTimerDuration MOZ_GUARDED_BY(mMutex);
nsString mLogTimerLabel; nsString mLogTimerLabel MOZ_GUARDED_BY(mMutex);
Console::TimerStatus mLogTimerStatus; Console::TimerStatus mLogTimerStatus MOZ_GUARDED_BY(mMutex);
// These 2 values are set by IncreaseCounter or ResetCounter on the owning // These 2 values are set by IncreaseCounter or ResetCounter on the owning
// thread and they are used by CreateCounterOrResetCounterValue. // thread and they are used by CreateCounterOrResetCounterValue.
// These members are set when console.count() or console.countReset() are // These members are set when console.count() or console.countReset() are
// called. // called.
nsString mCountLabel; nsString mCountLabel MOZ_GUARDED_BY(mMutex);
uint32_t mCountValue; uint32_t mCountValue MOZ_GUARDED_BY(mMutex);
// The concept of outerID and innerID is misleading because when a // The concept of outerID and innerID is misleading because when a
// ConsoleCallData is created from a window, these are the window IDs, but // ConsoleCallData is created from a window, these are the window IDs, but
@ -195,19 +201,19 @@ class ConsoleCallData final {
// subworker of a ChromeWorker these IDs are the type of worker and the // subworker of a ChromeWorker these IDs are the type of worker and the
// filename of the callee. // filename of the callee.
// In Console.sys.mjs the ID is 'jsm'. // In Console.sys.mjs the ID is 'jsm'.
enum { eString, eNumber, eUnknown } mIDType; enum { eString, eNumber, eUnknown } mIDType MOZ_GUARDED_BY(mMutex);
uint64_t mOuterIDNumber; uint64_t mOuterIDNumber MOZ_GUARDED_BY(mMutex);
nsString mOuterIDString; nsString mOuterIDString MOZ_GUARDED_BY(mMutex);
uint64_t mInnerIDNumber; uint64_t mInnerIDNumber MOZ_GUARDED_BY(mMutex);
nsString mInnerIDString; nsString mInnerIDString MOZ_GUARDED_BY(mMutex);
OriginAttributes mOriginAttributes; OriginAttributes mOriginAttributes MOZ_GUARDED_BY(mMutex);
nsString mAddonId; nsString mAddonId MOZ_GUARDED_BY(mMutex);
const nsString mMethodString; const nsString mMethodString MOZ_GUARDED_BY(mMutex);
// Stack management is complicated, because we want to do it as // Stack management is complicated, because we want to do it as
// lazily as possible. Therefore, we have the following behavior: // lazily as possible. Therefore, we have the following behavior:
@ -215,9 +221,9 @@ class ConsoleCallData final {
// 2) mReifiedStack is initialized if we're created in a worker. // 2) mReifiedStack is initialized if we're created in a worker.
// 3) mStack is set (possibly to null if there is no JS on the stack) if // 3) mStack is set (possibly to null if there is no JS on the stack) if
// we're created on main thread. // we're created on main thread.
Maybe<ConsoleStackEntry> mTopStackFrame; Maybe<ConsoleStackEntry> mTopStackFrame MOZ_GUARDED_BY(mMutex);
Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack; Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack MOZ_GUARDED_BY(mMutex);
nsCOMPtr<nsIStackFrame> mStack; nsCOMPtr<nsIStackFrame> mStack MOZ_GUARDED_BY(mMutex);
private: private:
~ConsoleCallData() = default; ~ConsoleCallData() = default;
@ -513,24 +519,27 @@ class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
jsapi.Init(); jsapi.Init();
JSContext* cx = jsapi.cx(); JSContext* cx = jsapi.cx();
JSObject* sandbox = {
mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal()); MutexAutoLock lock(mCallData->mMutex);
JS::Rooted<JSObject*> global(cx, sandbox);
if (NS_WARN_IF(!global)) { JSObject* sandbox =
return NS_ERROR_FAILURE; mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
JS::Rooted<JSObject*> global(cx, sandbox);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
// The CreateSandbox call returns a proxy to the actual sandbox object. We
// don't need a proxy here.
global = js::UncheckedUnwrap(global);
JSAutoRealm ar(cx, global);
// We don't need to set a parent object in mCallData bacause there are not
// DOM objects exposed to worklet.
ProcessCallData(cx, mConsoleData, mCallData);
} }
// The CreateSandbox call returns a proxy to the actual sandbox object. We
// don't need a proxy here.
global = js::UncheckedUnwrap(global);
JSAutoRealm ar(cx, global);
// We don't need to set a parent object in mCallData bacause there are not
// DOM objects exposed to worklet.
ProcessCallData(cx, mConsoleData, mCallData);
return NS_OK; return NS_OK;
} }
@ -674,35 +683,38 @@ class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
// The windows have to run in parallel. // The windows have to run in parallel.
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow); MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
if (aOuterWindow) { {
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID()); MutexAutoLock lock(mCallData->mMutex);
} else { if (aOuterWindow) {
ConsoleStackEntry frame; mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
if (mCallData->mTopStackFrame) {
frame = *mCallData->mTopStackFrame;
}
nsCString id = frame.mFilename;
nsString innerID;
if (aWorkerPrivate->IsSharedWorker()) {
innerID = u"SharedWorker"_ns;
} else if (aWorkerPrivate->IsServiceWorker()) {
innerID = u"ServiceWorker"_ns;
// Use scope as ID so the webconsole can decide if the message should
// show up per tab
id = aWorkerPrivate->ServiceWorkerScope();
} else { } else {
innerID = u"Worker"_ns; ConsoleStackEntry frame;
if (mCallData->mTopStackFrame) {
frame = *mCallData->mTopStackFrame;
}
nsCString id = frame.mFilename;
nsString innerID;
if (aWorkerPrivate->IsSharedWorker()) {
innerID = u"SharedWorker"_ns;
} else if (aWorkerPrivate->IsServiceWorker()) {
innerID = u"ServiceWorker"_ns;
// Use scope as ID so the webconsole can decide if the message should
// show up per tab
id = aWorkerPrivate->ServiceWorkerScope();
} else {
innerID = u"Worker"_ns;
}
mCallData->SetIDs(NS_ConvertUTF8toUTF16(id), innerID);
} }
mCallData->SetIDs(NS_ConvertUTF8toUTF16(id), innerID); mClonedData.mGlobal = aGlobal;
ProcessCallData(aCx, mConsoleData, mCallData);
mClonedData.mGlobal = nullptr;
} }
mClonedData.mGlobal = aGlobal;
ProcessCallData(aCx, mConsoleData, mCallData);
mClonedData.mGlobal = nullptr;
} }
RefPtr<ConsoleCallData> mCallData; RefPtr<ConsoleCallData> mCallData;
@ -1294,6 +1306,9 @@ void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
RefPtr<ConsoleCallData> callData = RefPtr<ConsoleCallData> callData =
new ConsoleCallData(aMethodName, aMethodString, this); new ConsoleCallData(aMethodName, aMethodString, this);
MutexAutoLock lock(callData->mMutex);
if (!StoreCallData(aCx, callData, aData)) { if (!StoreCallData(aCx, callData, aData)) {
return; return;
} }
@ -1427,7 +1442,6 @@ void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
if (callData->mTopStackFrame.isSome()) { if (callData->mTopStackFrame.isSome()) {
filename = callData->mTopStackFrame->mFilename; filename = callData->mTopStackFrame->mFilename;
} }
callData->SetIDs(u"jsm"_ns, NS_ConvertUTF8toUTF16(filename)); callData->SetIDs(u"jsm"_ns, NS_ConvertUTF8toUTF16(filename));
} }
@ -1512,6 +1526,7 @@ void MainThreadConsoleData::ProcessCallData(
const Sequence<JS::Value>& aArguments) { const Sequence<JS::Value>& aArguments) {
AssertIsOnMainThread(); AssertIsOnMainThread();
MOZ_ASSERT(aData); MOZ_ASSERT(aData);
aData->mMutex.AssertCurrentThreadOwns();
JS::Rooted<JS::Value> eventValue(aCx); JS::Rooted<JS::Value> eventValue(aCx);
@ -1573,6 +1588,8 @@ bool Console::PopulateConsoleNotificationInTheTargetScope(
MOZ_ASSERT(aTargetScope); MOZ_ASSERT(aTargetScope);
MOZ_ASSERT(JS_IsGlobalObject(aTargetScope)); MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));
aData->mMutex.AssertCurrentThreadOwns();
ConsoleStackEntry frame; ConsoleStackEntry frame;
if (aData->mTopStackFrame) { if (aData->mTopStackFrame) {
frame = *aData->mTopStackFrame; frame = *aData->mTopStackFrame;
@ -2501,11 +2518,14 @@ void Console::RetrieveConsoleEvents(JSContext* aCx,
// Here we have aCx and sequence in the same compartment. // Here we have aCx and sequence in the same compartment.
// targetScope is the destination scope and value will be populated in its // targetScope is the destination scope and value will be populated in its
// compartment. // compartment.
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope( {
aCx, sequence, targetScope, &value, mCallDataStorage[i], MutexAutoLock lock(mCallDataStorage[i]->mMutex);
&mGroupStack))) { if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
aRv.Throw(NS_ERROR_FAILURE); aCx, sequence, targetScope, &value, mCallDataStorage[i],
return; &mGroupStack))) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
} }
aEvents.AppendElement(value); aEvents.AppendElement(value);

View File

@ -7,6 +7,7 @@ support-files = ""
["test_console_shouldLog.js"] ["test_console_shouldLog.js"]
["test_failing_console_listener.js"] ["test_failing_console_listener.js"]
skip-if = ["tsan"]
["test_formatting.js"] ["test_formatting.js"]