Bug 970307: Part 1: Report status of each thread to find when all the threads are idle. r=nfroyd

This commit is contained in:
Patrick Wang (Chih-Kai Wang) 2014-11-24 15:21:52 +08:00
parent 3612a5d903
commit dc3aa76c24
14 changed files with 394 additions and 3 deletions

View File

@ -19,6 +19,7 @@
#include "nsServiceManagerUtils.h"
#include "nsXPCOMCIDInternal.h"
#include "ProfilerHelpers.h"
#include "nsThread.h"
namespace mozilla {
namespace dom {
@ -172,6 +173,10 @@ private:
~TransactionQueue()
{ }
#ifdef MOZ_NUWA_PROCESS
nsThread* mThread;
#endif
NS_DECL_NSIRUNNABLE
};
@ -791,6 +796,9 @@ TransactionQueue::TransactionQueue(TransactionThreadPool* aThreadPool,
mObjectStoreNames(aObjectStoreNames),
mMode(aMode),
mShouldFinish(false)
#ifdef MOZ_NUWA_PROCESS
, mThread(nullptr)
#endif
{
MOZ_ASSERT(aThreadPool);
aThreadPool->AssertIsOnOwningThread();
@ -816,6 +824,12 @@ TransactionThreadPool::TransactionQueue::Dispatch(nsIRunnable* aRunnable)
mQueue.AppendElement(aRunnable);
#ifdef MOZ_NUWA_PROCESS
if (mThread) {
mThread->SetWorking();
}
#endif
mMonitor.Notify();
}
@ -849,6 +863,12 @@ TransactionThreadPool::TransactionQueue::Run()
nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queue;
nsRefPtr<FinishCallback> finishCallback;
bool shouldFinish = false;
#ifdef MOZ_NUWA_PROCESS
mThread = static_cast<nsThread*>(NS_GetCurrentThread());
// Set ourself as working thread. We can reset later if we found
// our queue is empty.
mThread->SetWorking();
#endif
do {
NS_ASSERTION(queue.IsEmpty(), "Should have cleared this!");
@ -856,6 +876,9 @@ TransactionThreadPool::TransactionQueue::Run()
{
MonitorAutoLock lock(mMonitor);
while (!mShouldFinish && mQueue.IsEmpty()) {
#ifdef MOZ_NUWA_PROCESS
mThread->SetIdle();
#endif
if (NS_FAILED(mMonitor.Wait())) {
NS_ERROR("Failed to wait!");
}
@ -888,6 +911,10 @@ TransactionThreadPool::TransactionQueue::Run()
}
} while (!shouldFinish);
#ifdef MOZ_NUWA_PROCESS
mThread = nullptr;
#endif
#ifdef DEBUG
if (kDEBUGThreadSleepMS) {
MOZ_ALWAYS_TRUE(

View File

@ -65,6 +65,9 @@ public:
NS_IMETHOD Run()
{
MOZ_ASSERT(!NS_IsMainThread());
#ifdef MOZ_NUWA_PROCESS
NS_SetIgnoreStatusOfCurrentThread();
#endif
nsAutoString event;
gWpaSupplicant->WaitForEvent(event, mInterface);
if (!event.IsEmpty()) {

View File

@ -65,6 +65,7 @@
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsSandboxFlags.h"
#include "nsThread.h"
#include "xpcpublic.h"
#ifdef ANDROID
@ -4166,6 +4167,17 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
{
MutexAutoLock lock(mMutex);
#ifdef MOZ_NUWA_PROCESS
{
nsThread *thr = static_cast<nsThread*>(NS_GetCurrentThread());
ReentrantMonitorAutoEnter mon(thr->ThreadStatusMonitor());
if (mControlQueue.IsEmpty() &&
!(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
thr->SetIdle();
}
}
#endif // MOZ_NUWA_PROCESS
while (mControlQueue.IsEmpty() &&
!(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
WaitForWorkerEvents();

View File

@ -701,6 +701,7 @@ nsSocketTransportService::Run()
"NuwaMarkCurrentThread is undefined!");
NuwaMarkCurrentThread(nullptr, nullptr);
}
NS_SetIgnoreStatusOfCurrentThread();
#endif
SOCKET_LOG(("STS thread init\n"));

View File

@ -127,6 +127,8 @@ public:
}
#endif
NS_SetIgnoreStatusOfCurrentThread();
int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
O_RDONLY | O_CLOEXEC);
NS_ENSURE_STATE(lowMemFd != -1);

View File

@ -375,3 +375,11 @@ nsAutoLowPriorityIO::~nsAutoLowPriorityIO()
#endif
}
#ifdef MOZ_NUWA_PROCESS
#ifdef MOZILLA_INTERNAL_API
void
NS_SetIgnoreStatusOfCurrentThread() {
nsThreadManager::get()->SetIgnoreThreadStatus();
}
#endif // MOZILLA_INTERNAL_API
#endif // MOZ_NUWA_PROCESS

View File

@ -565,4 +565,19 @@ private:
void
NS_SetMainThread();
/**
* Helpers for thread to report their status when compiled with Nuwa.
*/
#ifdef MOZILLA_INTERNAL_API
#ifdef MOZ_NUWA_PROCESS
extern void
NS_SetIgnoreStatusOfCurrentThread();
#else // MOZ_NUWA_PROCESS
inline void
NS_SetIgnoreStatusOfCurrentThread()
{
}
#endif // MOZ_NUWA_PROCESS
#endif // MOZILLA_INTERNAL_API
#endif // nsThreadUtils_h__

View File

@ -207,6 +207,7 @@ TimerThread::Run()
}
#endif
NS_SetIgnoreStatusOfCurrentThread();
MonitorAutoLock lock(mMonitor);
// We need to know how many microseconds give a positive PRIntervalTime. This

View File

@ -13,7 +13,6 @@
#undef LOG
#endif
#include "mozilla/ReentrantMonitor.h"
#include "nsMemoryPressure.h"
#include "nsThreadManager.h"
#include "nsIClassInfoImpl.h"
@ -329,6 +328,10 @@ nsThread::ThreadFunc(void* aArg)
// Inform the ThreadManager
nsThreadManager::get()->RegisterCurrentThread(self);
#ifdef MOZ_NUWA_PROCESS
self->mThreadStatusInfo =
static_cast<void*>(nsThreadManager::get()->GetCurrentThreadStatusInfo());
#endif
mozilla::IOInterposer::RegisterCurrentThread();
@ -434,6 +437,10 @@ nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
, mShutdownRequired(false)
, mEventsAreDoomed(false)
, mIsMainThread(aMainThread)
#ifdef MOZ_NUWA_PROCESS
, mThreadStatusMonitor("nsThread.mThreadStatusLock")
, mThreadStatusInfo(nullptr)
#endif
{
}
@ -481,6 +488,11 @@ nsThread::InitCurrentThread()
SetupCurrentThreadForChaosMode();
nsThreadManager::get()->RegisterCurrentThread(this);
#ifdef MOZ_NUWA_PROCESS
mThreadStatusInfo =
static_cast<void*>(nsThreadManager::get()->GetCurrentThreadStatusInfo());
#endif
return NS_OK;
}
@ -496,7 +508,15 @@ nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget)
NS_WARNING("An event was posted to a thread that will never run it (rejected)");
return NS_ERROR_UNEXPECTED;
}
queue->PutEvent(aEvent);
#ifdef MOZ_NUWA_PROCESS
{
ReentrantMonitorAutoEnter mon(mThreadStatusMonitor);
SetWorking();
#endif // MOZ_NUWA_PROCESS
queue->PutEvent(aEvent);
#ifdef MOZ_NUWA_PROCESS
}
#endif // MOZ_NUWA_PROCESS
// Make sure to grab the observer before dropping the lock, otherwise the
// event that we just placed into the queue could run and eventually delete
@ -837,6 +857,15 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
--mRunningEvent;
#ifdef MOZ_NUWA_PROCESS
{
ReentrantMonitorAutoEnter mon(mThreadStatusMonitor);
if ((!mEvents->GetEvent(false, nullptr)) && (mRunningEvent == 0)) {
SetIdle();
}
}
#endif // MOZ_NUWA_PROCESS
NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent,
(this, mRunningEvent, *aResult));
@ -1046,6 +1075,22 @@ nsThread::SetMainThreadObserver(nsIThreadObserver* aObserver)
return NS_OK;
}
#ifdef MOZ_NUWA_PROCESS
void
nsThread::SetWorking()
{
nsThreadManager::get()->SetThreadIsWorking(
static_cast<nsThreadManager::ThreadStatusInfo*>(mThreadStatusInfo), true);
}
void
nsThread::SetIdle()
{
nsThreadManager::get()->SetThreadIsWorking(
static_cast<nsThreadManager::ThreadStatusInfo*>(mThreadStatusInfo), false);
}
#endif
//-----------------------------------------------------------------------------
NS_IMETHODIMP

View File

@ -16,6 +16,7 @@
#include "nsTObserverArray.h"
#include "mozilla/Attributes.h"
#include "nsAutoPtr.h"
#include "mozilla/ReentrantMonitor.h"
// A native thread
class nsThread
@ -65,6 +66,14 @@ public:
static nsresult
SetMainThreadObserver(nsIThreadObserver* aObserver);
#ifdef MOZ_NUWA_PROCESS
void SetWorking();
void SetIdle();
mozilla::ReentrantMonitor& ThreadStatusMonitor() {
return mThreadStatusMonitor;
}
#endif
protected:
static nsIThreadObserver* sMainThreadObserver;
@ -182,6 +191,12 @@ protected:
// Set to true when events posted to this thread will never run.
bool mEventsAreDoomed;
MainThreadFlag mIsMainThread;
#ifdef MOZ_NUWA_PROCESS
mozilla::ReentrantMonitor mThreadStatusMonitor;
// The actual type is defined in nsThreadManager.h which is not exposed to
// file out of thread module.
void* mThreadStatusInfo;
#endif
};
//-----------------------------------------------------------------------------

View File

@ -11,6 +11,7 @@
#include "nsTArray.h"
#include "nsAutoPtr.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/ReentrantMonitor.h"
#ifdef MOZ_CANARY
#include <fcntl.h>
#include <unistd.h>
@ -47,6 +48,54 @@ NS_SetMainThread()
typedef nsTArray<nsRefPtr<nsThread>> nsThreadArray;
#ifdef MOZ_NUWA_PROCESS
class NotifyAllThreadsWereIdle: public nsRunnable
{
public:
NotifyAllThreadsWereIdle(
nsTArray<nsRefPtr<nsThreadManager::AllThreadsWereIdleListener>>* aListeners)
: mListeners(aListeners)
{
}
virtual NS_IMETHODIMP
Run() {
// Copy listener array, which may be modified during call back.
nsTArray<nsRefPtr<nsThreadManager::AllThreadsWereIdleListener>> arr(*mListeners);
for (size_t i = 0; i < arr.Length(); i++) {
arr[i]->OnAllThreadsWereIdle();
}
return NS_OK;
}
private:
// Raw pointer, since it's pointing to a member of thread manager.
nsTArray<nsRefPtr<nsThreadManager::AllThreadsWereIdleListener>>* mListeners;
};
struct nsThreadManager::ThreadStatusInfo {
Atomic<bool> mWorking;
Atomic<bool> mWillBeWorking;
bool mIgnored;
nsThreadManager *mMgr;
ThreadStatusInfo(nsThreadManager *aManager)
: mWorking(false)
, mWillBeWorking(false)
, mIgnored(false)
, mMgr(aManager)
{
ReentrantMonitorAutoEnter mon(*(mMgr->mMonitor));
mMgr->mThreadStatusInfos.AppendElement(this);
}
~ThreadStatusInfo()
{
ReentrantMonitorAutoEnter mon(*(mMgr->mMonitor));
mMgr->mThreadStatusInfos.RemoveElement(this);
}
};
#endif // MOZ_NUWA_PROCESS
//-----------------------------------------------------------------------------
static void
@ -55,6 +104,14 @@ ReleaseObject(void* aData)
static_cast<nsISupports*>(aData)->Release();
}
#ifdef MOZ_NUWA_PROCESS
static void
DeleteThreadStatusInfo(void* aData)
{
delete static_cast<nsThreadManager::ThreadStatusInfo*>(aData);
}
#endif
static PLDHashOperator
AppendAndRemoveThread(PRThread* aKey, nsRefPtr<nsThread>& aThread, void* aArg)
{
@ -96,7 +153,17 @@ nsThreadManager::Init()
return NS_ERROR_FAILURE;
}
#ifdef MOZ_NUWA_PROCESS
if (PR_NewThreadPrivateIndex(&mThreadStatusInfoIndex,
DeleteThreadStatusInfo) == PR_FAILURE) {
return NS_ERROR_FAILURE;
}
#endif // MOZ_NUWA_PROCESS
mLock = new Mutex("nsThreadManager.mLock");
#ifdef MOZ_NUWA_PROCESS
mMonitor = MakeUnique<ReentrantMonitor>("nsThreadManager.mMonitor");
#endif // MOZ_NUWA_PROCESS
#ifdef MOZ_CANARY
const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
@ -194,6 +261,9 @@ nsThreadManager::Shutdown()
// Remove the TLS entry for the main thread.
PR_SetThreadPrivate(mCurThreadIndex, nullptr);
#ifdef MOZ_NUWA_PROCESS
PR_SetThreadPrivate(mThreadStatusInfoIndex, nullptr);
#endif
}
void
@ -226,6 +296,9 @@ nsThreadManager::UnregisterCurrentThread(nsThread* aThread)
PR_SetThreadPrivate(mCurThreadIndex, nullptr);
// Ref-count balanced via ReleaseObject
#ifdef MOZ_NUWA_PROCESS
PR_SetThreadPrivate(mThreadStatusInfoIndex, nullptr);
#endif
}
nsThread*
@ -250,6 +323,21 @@ nsThreadManager::GetCurrentThread()
return thread.get(); // reference held in TLS
}
#ifdef MOZ_NUWA_PROCESS
nsThreadManager::ThreadStatusInfo*
nsThreadManager::GetCurrentThreadStatusInfo()
{
void* data = PR_GetThreadPrivate(mThreadStatusInfoIndex);
if (!data) {
ThreadStatusInfo *thrInfo = new ThreadStatusInfo(this);
PR_SetThreadPrivate(mThreadStatusInfoIndex, thrInfo);
data = thrInfo;
}
return static_cast<ThreadStatusInfo*>(data);
}
#endif
NS_IMETHODIMP
nsThreadManager::NewThread(uint32_t aCreationFlags,
uint32_t aStackSize,
@ -342,3 +430,127 @@ nsThreadManager::GetHighestNumberOfThreads()
MutexAutoLock lock(*mLock);
return mHighestNumberOfThreads;
}
#ifdef MOZ_NUWA_PROCESS
void
nsThreadManager::SetIgnoreThreadStatus()
{
GetCurrentThreadStatusInfo()->mIgnored = true;
}
void
nsThreadManager::SetThreadIdle()
{
SetThreadIsWorking(GetCurrentThreadStatusInfo(), false);
}
void
nsThreadManager::SetThreadWorking()
{
SetThreadIsWorking(GetCurrentThreadStatusInfo(), true);
}
void
nsThreadManager::SetThreadIsWorking(ThreadStatusInfo* aInfo, bool aIsWorking)
{
aInfo->mWillBeWorking = aIsWorking;
if (mThreadsIdledListeners.Length() > 0) {
// A race condition occurs since we don't want threads to try to enter the
// monitor (nsThreadManager::mMonitor) when no one cares about their status.
// And thus the race can happen when we put the first listener into
// |mThreadsIdledListeners|:
//
// (1) Thread A wants to dispatch a task to Thread B.
// (2) Thread A checks |mThreadsIdledListeners|, and nothing is in the
// list. So Thread A decides not to enter |mMonitor| when updating B's
// status.
// (3) Thread A is suspended just before it changed status of B.
// (4) A listener is added to |mThreadsIdledListeners|
// (5) Now is Thread C's turn to run. Thread C finds there's something in
// |mThreadsIdledListeners|, so it enters |mMonitor| and check all
// thread info structs in |mThreadStatusInfos| while A is in the middle
// of changing B's status.
//
// Then C may find Thread B is an idle thread (which is not correct, because
// A attempted to change B's status prior to C starting to walk throught
// |mThreadStatusInfo|), but the fact that thread A is working (thread A
// hasn't finished dispatching a task to thread B) can prevent thread C from
// firing a bogus notification.
//
// If the state transition that happens outside the monitor is in the other
// direction, the race condition could be:
//
// (1) Thread D has just finished its jobs and wants to set its status to idle.
// (2) Thread D checks |mThreadsIdledListeners|, and nothing is in the list.
// So Thread D decides not to enter |mMonitor|.
// (3) Thread D is is suspended before it updates its own status.
// (4) A listener is put into |mThreadsIdledListeners|.
// (5) Thread C wants to changes status of itself. It checks
// |mThreadsIdledListeners| and finds something inside the list. Thread C
// then enters |mMonitor|, updates its status and checks thread info in
// |mThreadStatusInfos| while D is changing status of itself out of monitor.
//
// Thread C will find that thread D is working (D actually wants to change its
// status to idle before C starting to check), then C returns without firing
// any notification. Finding that thread D is working can make our checking
// mechanism miss a chance to fire a notification: because thread D thought
// there's nothing in |mThreadsIdledListeners| and thus won't check the
// |mThreadStatusInfos| after changing the status of itself.
//
// |mWillBeWorking| can be used to address this problem. We require each
// thread to put the value that is going to be set to |mWorking| to
// |mWillBeWorking| before the thread decide whether it should enter
// |mMonitor| to change status or not. Thus C finds that D is working while
// D's |mWillBeWorking| is false, and C realizes that D is just updating and
// can treat D as an idle thread.
//
// It doesn't matter whether D will check thread status after changing its
// own status or not. If D checks, which means D will enter the monitor
// before updating status, thus D must be blocked until C has finished
// dispatching the notification task to main thread, and D will find that main
// thread is working and will not fire an additional event. On the other hand,
// if D doesn't check |mThreadStatusInfos|, it's still ok, because C has
// treated D as an idle thread already.
bool hasWorkingThread = false;
ReentrantMonitorAutoEnter mon(*mMonitor);
// Get data structure of thread info.
aInfo->mWorking = aIsWorking;
for (size_t i = 0; i < mThreadStatusInfos.Length(); i++) {
ThreadStatusInfo *info = mThreadStatusInfos[i];
if (!info->mIgnored) {
if (info->mWorking) {
if (info->mWillBeWorking) {
hasWorkingThread = true;
break;
}
}
}
}
if (!hasWorkingThread) {
nsRefPtr<NotifyAllThreadsWereIdle> runnable =
new NotifyAllThreadsWereIdle(&mThreadsIdledListeners);
NS_DispatchToMainThread(runnable);
}
} else {
// Update thread info without holding any lock.
aInfo->mWorking = aIsWorking;
}
}
void
nsThreadManager::AddAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener)
{
MOZ_ASSERT(GetCurrentThreadStatusInfo()->mWorking);
mThreadsIdledListeners.AppendElement(listener);
}
void
nsThreadManager::RemoveAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener)
{
mThreadsIdledListeners.RemoveElement(listener);
}
#endif // MOZ_NUWA_PROCESS

View File

@ -14,9 +14,26 @@
class nsIRunnable;
namespace mozilla {
class ReentrantMonitor;
}
class nsThreadManager : public nsIThreadManager
{
public:
#ifdef MOZ_NUWA_PROCESS
struct ThreadStatusInfo;
class AllThreadsWereIdleListener {
public:
NS_INLINE_DECL_REFCOUNTING(AllThreadsWereIdleListener);
virtual void OnAllThreadsWereIdle() = 0;
protected:
virtual ~AllThreadsWereIdleListener()
{
}
};
#endif // MOZ_NUWA_PROCESS
NS_DECL_ISUPPORTS
NS_DECL_NSITHREADMANAGER
@ -54,6 +71,17 @@ public:
{
}
#ifdef MOZ_NUWA_PROCESS
void SetIgnoreThreadStatus();
void SetThreadIdle();
void SetThreadWorking();
void SetThreadIsWorking(ThreadStatusInfo* aInfo, bool aIsWorking);
void AddAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener);
void RemoveAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener);
ThreadStatusInfo* GetCurrentThreadStatusInfo();
#endif // MOZ_NUWA_PROCESS
private:
nsThreadManager()
: mCurThreadIndex(0)
@ -62,6 +90,9 @@ private:
, mInitialized(false)
, mCurrentNumberOfThreads(1)
, mHighestNumberOfThreads(1)
#ifdef MOZ_NUWA_PROCESS
, mMonitor(nullptr)
#endif
{
}
@ -78,6 +109,13 @@ private:
uint32_t mCurrentNumberOfThreads;
// The highest number of threads encountered so far during the session
uint32_t mHighestNumberOfThreads;
#ifdef MOZ_NUWA_PROCESS
unsigned mThreadStatusInfoIndex;
nsTArray<nsRefPtr<AllThreadsWereIdleListener>> mThreadsIdledListeners;
nsTArray<ThreadStatusInfo*> mThreadStatusInfos;
mozilla::UniquePtr<mozilla::ReentrantMonitor> mMonitor;
#endif // MOZ_NUWA_PROCESS
};
#define NS_THREADMANAGER_CID \

View File

@ -150,7 +150,6 @@ NS_IMETHODIMP
nsThreadPool::Run()
{
LOG(("THRD-P(%p) enter\n", this));
mThreadNaming.SetThreadPoolName(mName);
nsCOMPtr<nsIThread> current;
@ -208,6 +207,9 @@ nsThreadPool::Run()
} else {
PRIntervalTime delta = timeout - (now - idleSince);
LOG(("THRD-P(%p) waiting [%d]\n", this, delta));
#ifdef MOZ_NUWA_PROCESS
nsThreadManager::get()->SetThreadIdle();
#endif // MOZ_NUWA_PROCESS
mon.Wait(delta);
}
} else if (wasIdle) {
@ -217,6 +219,9 @@ nsThreadPool::Run()
}
if (event) {
LOG(("THRD-P(%p) running [%p]\n", this, event.get()));
#ifdef MOZ_NUWA_PROCESS
nsThreadManager::get()->SetThreadWorking();
#endif // MOZ_NUWA_PROCESS
event->Run();
}
} while (!exitThread);

View File

@ -556,6 +556,13 @@ nsTimerImpl::Fire()
return;
}
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess() && IsNuwaReady()) {
// A timer event fired after Nuwa frozen can freeze main thread.
return;
}
#endif
PROFILER_LABEL("Timer", "Fire",
js::ProfileEntry::Category::OTHER);