Bug 1198381 - Extend nsIThread with idleDispatch, r=froydnj,smaug

The intent of idleDispatch is the possibility to have a runnable
executed when the thread is idle. This is accomplished by adding an
event queue for idle tasks that will only be considered when the main
event queue is empty and the caller of ProcessNextEvent doesn't
require that we wait until there is an event on the main event queue.

MozReview-Commit-ID: IDWQfzZqWpZ

--HG--
extra : rebase_source : 0d5bfeebd08e01597c2cd8b76e8e848d9f9c58f0
This commit is contained in:
Andreas Farre 2016-08-24 16:18:06 +02:00
Родитель 9a297f4b3e
Коммит d87b4d239b
12 изменённых файлов: 410 добавлений и 4 удалений

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

@ -32,6 +32,15 @@ using namespace mozilla;
#ifndef XPCOM_GLUE_AVOID_NSPR
NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod)
NS_IMETHODIMP
IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline)
{
*aIdleDeadline = TimeStamp();
return NS_OK;
}
NS_IMPL_ISUPPORTS(Runnable, nsIRunnable)
NS_IMETHODIMP
@ -51,6 +60,15 @@ CancelableRunnable::Cancel()
return NS_OK;
}
NS_IMPL_ISUPPORTS_INHERITED(IncrementalRunnable, CancelableRunnable,
nsIIncrementalRunnable)
void
IncrementalRunnable::SetDeadline(TimeStamp aDeadline)
{
// Do nothing
}
#endif // XPCOM_GLUE_AVOID_NSPR
//-----------------------------------------------------------------------------
@ -202,6 +220,38 @@ NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags)
return NS_DispatchToMainThread(event.forget(), aDispatchFlags);
}
extern NS_METHOD
nsresult
NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent)
{
nsresult rv;
nsCOMPtr<nsIRunnable> event(aEvent);
#ifdef MOZILLA_INTERNAL_API
nsIThread* thread = NS_GetCurrentThread();
if (!thread) {
return NS_ERROR_UNEXPECTED;
}
#else
nsCOMPtr<nsIThread> thread;
rv = NS_GetCurrentThread(getter_AddRefs(thread));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#endif
// To keep us from leaking the runnable if dispatch method fails,
// we grab the reference on failures and release it.
nsIRunnable* temp = event.get();
rv = thread->IdleDispatch(event.forget());
if (NS_WARN_IF(NS_FAILED(rv))) {
// Dispatch() leaked the reference to the event, but due to caller's
// assumptions, we shouldn't leak here. And given we are on the same
// thread as the dispatch target, it's mostly safe to do it here.
NS_RELEASE(temp);
}
return rv;
}
#ifndef XPCOM_GLUE_AVOID_NSPR
nsresult
NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout)

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

@ -14,6 +14,8 @@
#include "nsIThread.h"
#include "nsIRunnable.h"
#include "nsICancelableRunnable.h"
#include "nsIIdlePeriod.h"
#include "nsIIncrementalRunnable.h"
#include "nsStringGlue.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
@ -130,6 +132,9 @@ extern nsresult
NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent,
uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
extern nsresult
NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent);
#ifndef XPCOM_GLUE_AVOID_NSPR
/**
* Process all pending events for the given thread before returning. This
@ -222,6 +227,23 @@ extern nsIThread* NS_GetCurrentThread();
namespace mozilla {
// This class is designed to be subclassed.
class IdlePeriod : public nsIIdlePeriod
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIIDLEPERIOD
IdlePeriod() {}
protected:
virtual ~IdlePeriod() {}
private:
IdlePeriod(const IdlePeriod&) = delete;
IdlePeriod& operator=(const IdlePeriod&) = delete;
IdlePeriod& operator=(const IdlePeriod&&) = delete;
};
// This class is designed to be subclassed.
class Runnable : public nsIRunnable
{
@ -258,6 +280,25 @@ private:
CancelableRunnable& operator=(const CancelableRunnable&&) = delete;
};
// This class is designed to be subclassed.
class IncrementalRunnable : public CancelableRunnable,
public nsIIncrementalRunnable
{
public:
NS_DECL_ISUPPORTS_INHERITED
// nsIIncrementalRunnable
virtual void SetDeadline(TimeStamp aDeadline) override;
IncrementalRunnable() {}
protected:
virtual ~IncrementalRunnable() {}
private:
IncrementalRunnable(const IncrementalRunnable&) = delete;
IncrementalRunnable& operator=(const IncrementalRunnable&) = delete;
IncrementalRunnable& operator=(const IncrementalRunnable&&) = delete;
};
namespace detail {
// An event that can be used to call a C++11 functions or function objects,

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

@ -10,6 +10,7 @@
#include "GeckoProfiler.h"
#include "nsComponentManagerUtils.h"
#include "nsIIdlePeriod.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "mozilla/Services.h"
@ -506,6 +507,18 @@ LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents)
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
LazyIdleThread::ProcessNextEvent(bool aMayWait,
bool* aEventWasProcessed)

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

@ -0,0 +1,49 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MainThreadIdlePeriod.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "nsRefreshDriver.h"
#define DEFAULT_LONG_IDLE_PERIOD 50.0f
namespace mozilla {
NS_IMETHODIMP
MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline)
{
MOZ_ASSERT(aIdleDeadline);
Maybe<TimeStamp> deadline = nsRefreshDriver::GetIdleDeadlineHint();
if (deadline.isSome()) {
*aIdleDeadline = deadline.value();
} else {
*aIdleDeadline =
TimeStamp::Now() + TimeDuration::FromMilliseconds(GetLongIdlePeriod());
}
return NS_OK;
}
/* static */ float
MainThreadIdlePeriod::GetLongIdlePeriod()
{
static float sLongIdlePeriod = DEFAULT_LONG_IDLE_PERIOD;
static bool sInitialized = false;
if (!sInitialized) {
sInitialized = true;
Preferences::AddFloatVarCache(&sLongIdlePeriod, "idle_queue.long_period",
DEFAULT_LONG_IDLE_PERIOD);
}
return sLongIdlePeriod;
}
} // namespace mozilla

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

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_mainthreadidleperiod_h
#define mozilla_dom_mainthreadidleperiod_h
#include "mozilla/TimeStamp.h"
#include "nsThreadUtils.h"
namespace mozilla {
class MainThreadIdlePeriod final : public IdlePeriod
{
public:
NS_DECL_NSIIDLEPERIOD
static float GetLongIdlePeriod();
private:
virtual ~MainThreadIdlePeriod() {}
};
} // namespace mozilla
#endif // mozilla_dom_mainthreadidleperiod_h

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

@ -7,6 +7,7 @@
XPIDL_SOURCES += [
'nsIEnvironment.idl',
'nsIEventTarget.idl',
'nsIIdlePeriod.idl',
'nsIProcess.idl',
'nsIRunnable.idl',
'nsISupportsPriority.idl',
@ -22,6 +23,7 @@ XPIDL_MODULE = 'xpcom_threads'
EXPORTS += [
'nsEventQueue.h',
'nsICancelableRunnable.h',
'nsIIncrementalRunnable.h',
'nsMemoryPressure.h',
'nsProcess.h',
'nsThread.h',
@ -33,6 +35,7 @@ EXPORTS.mozilla += [
'HangAnnotations.h',
'HangMonitor.h',
'LazyIdleThread.h',
'MainThreadIdlePeriod.h',
'MozPromise.h',
'SharedThreadPool.h',
'StateMirroring.h',
@ -48,6 +51,7 @@ UNIFIED_SOURCES += [
'HangAnnotations.cpp',
'HangMonitor.cpp',
'LazyIdleThread.cpp',
'MainThreadIdlePeriod.cpp',
'nsEnvironment.cpp',
'nsEventQueue.cpp',
'nsMemoryPressure.cpp',

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

@ -0,0 +1,32 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
%{C++
namespace mozilla {
class TimeStamp;
}
%}
native TimeStamp(mozilla::TimeStamp);
/**
* An instance implementing nsIIdlePeriod is used by an associated
* nsIThread to estimate when it is likely that it will receive an
* event.
*/
[builtinclass, uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)]
interface nsIIdlePeriod : nsISupports
{
/**
* Return an estimate of a point in time in the future when we
* think that the associated thread will become busy. Should
* return TimeStamp() (i.e. the null time) or a time less than
* TimeStamp::Now() if the thread is currently busy or will become
* busy very soon.
*/
TimeStamp getIdlePeriodHint();
};

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

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsIIncrementalRunnable_h__
#define nsIIncrementalRunnable_h__
#include "nsISupports.h"
#include "mozilla/TimeStamp.h"
#define NS_IINCREMENTALRUNNABLE_IID \
{ 0x688be92e, 0x7ade, 0x4fdc, \
{ 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c } }
/**
* A task interface for tasks that can schedule their work to happen
* in increments bounded by a deadline.
*/
class nsIIncrementalRunnable : public nsISupports
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINCREMENTALRUNNABLE_IID)
/**
* Notify the task of a point in time in the future when the task
* should stop executing.
*/
virtual void SetDeadline(mozilla::TimeStamp aDeadline) = 0;
protected:
nsIIncrementalRunnable() { }
virtual ~nsIIncrementalRunnable() {}
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIIncrementalRunnable,
NS_IINCREMENTALRUNNABLE_IID)
#endif // nsIIncrementalRunnable_h__

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

@ -5,9 +5,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIEventTarget.idl"
#include "nsIIdlePeriod.idl"
%{C++
#include "mozilla/AlreadyAddRefed.h"
%}
[ptr] native PRThread(PRThread);
native alreadyAddRefed_nsIIdlePeriod(already_AddRefed<nsIIdlePeriod>);
/**
* This interface provides a high-level abstraction for an operating system
* thread.
@ -112,4 +119,31 @@ interface nsIThread : nsIEventTarget
* on the thread object.
*/
void asyncShutdown();
/**
* Register an instance of nsIIdlePeriod which works as a facade of
* the abstract notion of a "current idle period". The
* nsIIdlePeriod should always represent the "current" idle period
* with regard to expected work for the thread. The thread is free
* to use this when there are no higher prioritized tasks to process
* to determine if it is reasonable to schedule tasks that could run
* when the thread is idle. The responsibility of the registered
* nsIIdlePeriod is to answer with an estimated deadline at which
* the thread should expect that it could receive new higher
* priority tasks.
*/
[noscript] void registerIdlePeriod(in alreadyAddRefed_nsIIdlePeriod aIdlePeriod);
/**
* Dispatch an event to the thread's idle queue. This function may be called
* from any thread, and it may be called re-entrantly.
*
* @param event
* The alreadyAddRefed<> event to dispatch.
* NOTE that the event will be leaked if it fails to dispatch.
*
* @throws NS_ERROR_INVALID_ARG
* Indicates that event is null.
*/
[noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event);
};

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

@ -34,6 +34,8 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsIIdlePeriod.h"
#include "nsIIncrementalRunnable.h"
#include "nsThreadSyncDispatch.h"
#include "LeakRefPtr.h"
@ -594,6 +596,7 @@ nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
, mScriptObserver(nullptr)
, mEvents(WrapNotNull(&mEventsRoot))
, mEventsRoot(mLock)
, mIdleEvents(mLock)
, mPriority(PRIORITY_NORMAL)
, mThread(nullptr)
, mNestedEventLoopDepth(0)
@ -631,6 +634,8 @@ nsThread::Init()
NS_ADDREF_THIS();
mIdlePeriod = new IdlePeriod();
mShutdownRequired = true;
// ThreadFunc is responsible for setting mThread
@ -661,6 +666,8 @@ nsThread::InitCurrentThread()
mThread = PR_GetCurrentThread();
SetupCurrentThreadForChaosMode();
mIdlePeriod = new IdlePeriod();
nsThreadManager::get().RegisterCurrentThread(*this);
return NS_OK;
}
@ -960,6 +967,37 @@ nsThread::HasPendingEvents(bool* aResult)
return NS_OK;
}
NS_IMETHODIMP
nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
MutexAutoLock lock(mLock);
mIdlePeriod = aIdlePeriod;
return NS_OK;
}
NS_IMETHODIMP
nsThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
{
MutexAutoLock lock(mLock);
LeakRefPtr<nsIRunnable> event(Move(aEvent));
if (NS_WARN_IF(!event)) {
return NS_ERROR_INVALID_ARG;
}
if (mEventsAreDoomed) {
NS_WARNING("An idle event was posted to a thread that will never run it (rejected)");
return NS_ERROR_UNEXPECTED;
}
mIdleEvents.PutEvent(event.take(), lock);
return NS_OK;
}
#ifdef MOZ_CANARY
void canary_alarm_handler(int signum);
@ -1012,6 +1050,63 @@ void canary_alarm_handler(int signum)
} \
PR_END_MACRO
void
nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
{
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
MOZ_ASSERT(aEvent);
TimeStamp idleDeadline;
{
MutexAutoUnlock unlock(mLock);
mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
}
if (!idleDeadline || idleDeadline < TimeStamp::Now()) {
aEvent = nullptr;
return;
}
mIdleEvents.GetEvent(false, aEvent, aProofOfLock);
if (*aEvent) {
nsCOMPtr<nsIIncrementalRunnable> incrementalEvent(do_QueryInterface(*aEvent));
if (incrementalEvent) {
incrementalEvent->SetDeadline(idleDeadline);
}
}
}
void
nsThread::GetEvent(bool aWait, nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
{
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
MOZ_ASSERT(aEvent);
// We'll try to get an event to execute in three stages.
// [1] First we just try to get it from the regular queue without waiting.
mEvents->GetEvent(false, aEvent, aProofOfLock);
// [2] If we didn't get an event from the regular queue, try to
// get one from the idle queue
if (!*aEvent) {
// Since events in mEvents have higher priority than idle
// events, we will only consider idle events when there are no
// pending events in mEvents. We will for the same reason never
// wait for an idle event, since a higher priority event might
// appear at any time.
GetIdleEvent(aEvent, aProofOfLock);
}
// [3] If we neither got an event from the regular queue nor the
// idle queue, then if we should wait for events we block on the
// main queue until an event is available.
// If we are shutting down, then do not wait for new events.
if (!*aEvent && aWait) {
mEvents->GetEvent(aWait, aEvent, aProofOfLock);
}
}
NS_IMETHODIMP
nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
{
@ -1064,12 +1159,10 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
// Scope for |event| to make sure that its destructor fires while
// mNestedEventLoopDepth has been incremented, since that destructor can
// also do work.
// If we are shutting down, then do not wait for new events.
nsCOMPtr<nsIRunnable> event;
{
MutexAutoLock lock(mLock);
mEvents->GetEvent(reallyWait, getter_AddRefs(event), lock);
GetEvent(reallyWait, getter_AddRefs(event), lock);
}
*aResult = (event.get() != nullptr);

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

@ -8,6 +8,7 @@
#define nsThread_h__
#include "mozilla/Mutex.h"
#include "nsIIdlePeriod.h"
#include "nsIThreadInternal.h"
#include "nsISupportsPriority.h"
#include "nsEventQueue.h"
@ -94,6 +95,10 @@ public:
private:
void DoMainThreadSpecificProcessing(bool aReallyWait);
void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
void GetEvent(bool aWait, nsIRunnable** aEvent,
mozilla::MutexAutoLock& aProofOfLock);
protected:
class nsChainedEventQueue;
@ -178,6 +183,9 @@ protected:
NotNull<nsChainedEventQueue*> aQueue)
: mThread(aThread)
, mQueue(aQueue)
{
}
@ -192,7 +200,7 @@ protected:
}
};
// This lock protects access to mObserver, mEvents and mEventsAreDoomed.
// This lock protects access to mObserver, mEvents, mIdleEvents,
// All of those fields are only modified on the thread itself (never from
// another thread). This means that we can avoid holding the lock while
// using mObserver and mEvents on the thread itself. When calling PutEvent
@ -208,6 +216,13 @@ protected:
NotNull<nsChainedEventQueue*> mEvents; // never null
nsChainedEventQueue mEventsRoot;
// mIdlePeriod keeps track of the current idle period. If at any
// time the main event queue is empty, calling
// mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
// the current idle period will end.
nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
nsEventQueue mIdleEvents;
int32_t mPriority;
PRThread* mThread;
uint32_t mNestedEventLoopDepth;

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

@ -16,6 +16,8 @@
#include <unistd.h>
#endif
#include "MainThreadIdlePeriod.h"
using namespace mozilla;
static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
@ -99,6 +101,11 @@ nsThreadManager::Init()
return rv;
}
{
nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
mMainThread->RegisterIdlePeriod(idlePeriod.forget());
}
// We need to keep a pointer to the current thread, so we can satisfy
// GetIsMainThread calls that occur post-Shutdown.
mMainThread->GetPRThread(&mMainPRThread);