Bug 1440257: Ensure that mscom::MainThreadInvoker gets cleaned up very soon after it finishes executing. r?aklotz
MainThreadInvoker queues both a Gecko runnable and an APC to the main thread to deal with different ways in which the main thread can block.
However, the main thread doesn't check for APCs very often any more.
This means that the APC's reference to the SyncRunnable doesn't get cleaned up for a long time, thus leaking memory.
To work around this, we:
1. Queue an APC wich does the actual work.
2. Post a Gecko runnable (which always runs).
If the APC hasn't run, the Gecko runnable runs it.
Otherwise, it does nothing.
MozReview-Commit-ID: L0P4rMBnlaZ
--- a/ipc/mscom/MainThreadInvoker.cpp
+++ b/ipc/mscom/MainThreadInvoker.cpp
@@ -11,52 +11,67 @@
#include "mozilla/Assertions.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/HangMonitor.h"
#include "mozilla/mscom/SpinEvent.h"
#include "mozilla/RefPtr.h"
#include "mozilla/SystemGroup.h"
#include "private/prpriv.h" // For PR_GetThreadID
-#include "WinUtils.h"
+#include <winternl.h> // For NTSTATUS and NTAPI
namespace {
+typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID);
+
/**
* SyncRunnable implements different code paths depending on whether or not
* we are running on a multiprocessor system. In the multiprocessor case, we
* leave the thread in a spin loop while waiting for the main thread to execute
* our runnable. Since spinning is pointless in the uniprocessor case, we block
* on an event that is set by the main thread once it has finished the runnable.
*/
class SyncRunnable : public mozilla::Runnable
{
public:
explicit SyncRunnable(already_AddRefed<nsIRunnable> aRunnable)
: mozilla::Runnable("MainThreadInvoker")
, mRunnable(aRunnable)
- {}
+ {
+ static const bool gotStatics = InitStatics();
+ MOZ_ASSERT(gotStatics);
+ }
~SyncRunnable() = default;
NS_IMETHOD Run() override
{
if (mHasRun) {
+ // The APC already ran, so we have nothing to do.
return NS_OK;
}
+
+ // Run the pending APC in the queue.
+ MOZ_ASSERT(sNtTestAlert);
+ sNtTestAlert();
+ return NS_OK;
+ }
+
+ // This is called by MainThreadInvoker::MainThreadAPC.
+ void APCRun()
+ {
mHasRun = true;
TimeStamp runStart(TimeStamp::Now());
mRunnable->Run();
TimeStamp runEnd(TimeStamp::Now());
mDuration = runEnd - runStart;
mEvent.Signal();
- return NS_OK;
}
bool WaitUntilComplete()
{
return mEvent.Wait(mozilla::mscom::MainThreadInvoker::GetTargetThread());
}
const mozilla::TimeDuration& GetDuration() const
@@ -64,18 +79,31 @@ public:
return mDuration;
}
private:
bool mHasRun = false;
nsCOMPtr<nsIRunnable> mRunnable;
mozilla::mscom::SpinEvent mEvent;
mozilla::TimeDuration mDuration;
+
+ static NtTestAlertPtr sNtTestAlert;
+
+ static bool InitStatics()
+ {
+ sNtTestAlert = reinterpret_cast<NtTestAlertPtr>(
+ ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert"));
+ MOZ_ASSERT(sNtTestAlert);
+ return sNtTestAlert;
+ }
+
};
+NtTestAlertPtr SyncRunnable::sNtTestAlert = nullptr;
+
} // anonymous namespace
namespace mozilla {
namespace mscom {
HANDLE MainThreadInvoker::sMainThread = nullptr;
/* static */ bool
@@ -115,39 +143,50 @@ MainThreadInvoker::Invoke(already_AddRef
if (NS_IsMainThread()) {
runnable->Run();
return true;
}
RefPtr<SyncRunnable> syncRunnable = new SyncRunnable(runnable.forget());
- if (NS_FAILED(SystemGroup::Dispatch(
- TaskCategory::Other, do_AddRef(syncRunnable)))) {
- return false;
- }
-
+ // The main thread could be either blocked on a condition variable waiting
+ // for a Gecko event, or it could be blocked waiting on a Windows HANDLE in
+ // IPC code (doing a sync message send). In the former case, we wake it by
+ // posting a Gecko runnable to the main thread. In the latter case, we wake
+ // it using an APC. However, the latter case doesn't happen very often now
+ // and APCs aren't otherwise run by the main thread. To ensure the
+ // SyncRunnable is cleaned up, we need both to run consistently.
+ // To do this, we:
+ // 1. Queue an APC which does the actual work.
// This ref gets released in MainThreadAPC when it runs.
SyncRunnable* syncRunnableRef = syncRunnable.get();
NS_ADDREF(syncRunnableRef);
if (!::QueueUserAPC(&MainThreadAPC, sMainThread,
reinterpret_cast<UINT_PTR>(syncRunnableRef))) {
return false;
}
+ // 2. Post a Gecko runnable (which always runs). If the APC hasn't run, the
+ // Gecko runnable runs it. Otherwise, it does nothing.
+ if (NS_FAILED(SystemGroup::Dispatch(
+ TaskCategory::Other, do_AddRef(syncRunnable)))) {
+ return false;
+ }
+
bool result = syncRunnable->WaitUntilComplete();
mDuration = syncRunnable->GetDuration();
return result;
}
/* static */ VOID CALLBACK
MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam)
{
AUTO_PROFILER_THREAD_WAKE;
mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity);
MOZ_ASSERT(NS_IsMainThread());
auto runnable = reinterpret_cast<SyncRunnable*>(aParam);
- runnable->Run();
+ runnable->APCRun();
NS_RELEASE(runnable);
}
} // namespace mscom
} // namespace mozilla