Bug 1367850 - Move Android UI thread runnable queue from AndroidBridge to AndroidUiThread r=jchen

MozReview-Commit-ID: 4okw7R2P2LC
This commit is contained in:
Randall Barker 2017-06-02 11:03:14 -07:00
parent 7949bdab33
commit 49e524747b
6 changed files with 153 additions and 134 deletions

View File

@ -153,7 +153,6 @@ AndroidBridge::~AndroidBridge()
}
AndroidBridge::AndroidBridge()
: mUiTaskQueueLock("UiTaskQueue")
{
ALOG_BRIDGE("AndroidBridge::Init");
@ -947,109 +946,6 @@ AndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow)
return layerClient->IsContentDocumentDisplayed();
}
class AndroidBridge::DelayedTask
{
using TimeStamp = mozilla::TimeStamp;
using TimeDuration = mozilla::TimeDuration;
public:
DelayedTask(already_AddRefed<nsIRunnable> aTask)
: mTask(aTask)
, mRunTime() // Null timestamp representing no delay.
{}
DelayedTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
: mTask(aTask)
, mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs))
{}
bool IsEarlierThan(const DelayedTask& aOther) const
{
if (mRunTime) {
return aOther.mRunTime ? mRunTime < aOther.mRunTime : false;
}
// In the case of no delay, we're earlier if aOther has a delay.
// Otherwise, we're not earlier, to maintain task order.
return !!aOther.mRunTime;
}
int64_t MillisecondsToRunTime() const
{
if (mRunTime) {
return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds());
}
return 0;
}
already_AddRefed<nsIRunnable> TakeTask()
{
return mTask.forget();
}
private:
nsCOMPtr<nsIRunnable> mTask;
const TimeStamp mRunTime;
};
void
AndroidBridge::PostTaskToUiThread(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
{
// add the new task into the mUiTaskQueue, sorted with
// the earliest task first in the queue
size_t i;
DelayedTask newTask(aDelayMs ? DelayedTask(mozilla::Move(aTask), aDelayMs)
: DelayedTask(mozilla::Move(aTask)));
{
MutexAutoLock lock(mUiTaskQueueLock);
for (i = 0; i < mUiTaskQueue.Length(); i++) {
if (newTask.IsEarlierThan(mUiTaskQueue[i])) {
mUiTaskQueue.InsertElementAt(i, mozilla::Move(newTask));
break;
}
}
if (i == mUiTaskQueue.Length()) {
// We didn't insert the task, which means we should append it.
mUiTaskQueue.AppendElement(mozilla::Move(newTask));
}
}
if (i == 0) {
// if we're inserting it at the head of the queue, notify Java because
// we need to get a callback at an earlier time than the last scheduled
// callback
GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
}
}
int64_t
AndroidBridge::RunDelayedUiThreadTasks()
{
MutexAutoLock lock(mUiTaskQueueLock);
while (!mUiTaskQueue.IsEmpty()) {
const int64_t timeLeft = mUiTaskQueue[0].MillisecondsToRunTime();
if (timeLeft > 0) {
// this task (and therefore all remaining tasks)
// have not yet reached their runtime. return the
// time left until we should be called again
return timeLeft;
}
// Retrieve task before unlocking/running.
nsCOMPtr<nsIRunnable> nextTask(mUiTaskQueue[0].TakeTask());
mUiTaskQueue.RemoveElementAt(0);
// Unlock to allow posting new tasks reentrantly.
MutexAutoUnlock unlock(mUiTaskQueueLock);
nextTask->Run();
}
return -1;
}
Object::LocalRef AndroidBridge::ChannelCreate(Object::Param stream) {
JNIEnv* const env = GetEnvForThread();
auto rv = Object::LocalRef::Adopt(env, env->CallStaticObjectMethod(

View File

@ -224,15 +224,6 @@ protected:
jni::Object::GlobalRef mMessageQueue;
jfieldID mMessageQueueMessages;
jmethodID mMessageQueueNext;
private:
class DelayedTask;
nsTArray<DelayedTask> mUiTaskQueue;
mozilla::Mutex mUiTaskQueueLock;
public:
void PostTaskToUiThread(already_AddRefed<nsIRunnable> aTask, int aDelayMs);
int64_t RunDelayedUiThreadTasks();
};
class AutoJNIClass {

View File

@ -3,12 +3,15 @@
* 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 "AndroidBridge.h"
#include "base/message_loop.h"
#include "GeneratedJNIWrappers.h"
#include "mozilla/Atomics.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"
#include "nsThread.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
@ -18,17 +21,22 @@ using namespace mozilla;
namespace {
class AndroidUiThread;
class AndroidUiTask;
StaticAutoPtr<LinkedList<AndroidUiTask> > sTaskQueue;
StaticAutoPtr<mozilla::Mutex> sTaskQueueLock;
StaticRefPtr<AndroidUiThread> sThread;
static bool sThreadDestroyed;
static MessageLoop* sMessageLoop;
static Atomic<Monitor*> sMessageLoopAccessMonitor;
void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs);
/*
* The AndroidUiThread is derived from nsThread so that nsIRunnable objects that get
* dispatched may be intercepted. Only nsIRunnable objects that need to be synchronously
* executed are passed into the nsThread to be queued. All other nsIRunnable object
* are immediately dispatched to the Android UI thread via the AndroidBridge.
* are immediately dispatched to the Android UI thread.
* AndroidUiThread is derived from nsThread instead of being an nsIEventTarget
* wrapper that contains an nsThread object because if nsIRunnable objects with a
* delay were dispatch directly to an nsThread object, such as obtained from
@ -48,14 +56,6 @@ public:
nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override;
nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs) override;
static int64_t RunDelayedTasksIfValid() {
if (!AndroidBridge::Bridge() ||
sThreadDestroyed) {
return -1;
}
return AndroidBridge::Bridge()->RunDelayedUiThreadTasks();
}
private:
~AndroidUiThread()
{}
@ -69,7 +69,7 @@ AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
if (aFlags & NS_DISPATCH_SYNC) {
return nsThread::Dispatch(Move(aEvent), aFlags);
} else {
AndroidBridge::Bridge()->PostTaskToUiThread(Move(aEvent), 0);
EnqueueTask(Move(aEvent), 0);
return NS_OK;
}
}
@ -77,7 +77,7 @@ AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
NS_IMETHODIMP
AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
{
AndroidBridge::Bridge()->PostTaskToUiThread(Move(aEvent), aDelayMs);
EnqueueTask(Move(aEvent), aDelayMs);
return NS_OK;
}
@ -105,7 +105,7 @@ NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver)
NS_IMETHODIMP
ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
{
AndroidBridge::Bridge()->PostTaskToUiThread(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
return NS_OK;
}
@ -121,6 +121,49 @@ ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread, bool eventWasPr
return NS_OK;
}
class AndroidUiTask : public LinkedListElement<AndroidUiTask> {
using TimeStamp = mozilla::TimeStamp;
using TimeDuration = mozilla::TimeDuration;
public:
AndroidUiTask(already_AddRefed<nsIRunnable> aTask)
: mTask(aTask)
, mRunTime() // Null timestamp representing no delay.
{}
AndroidUiTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
: mTask(aTask)
, mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs))
{}
bool IsEarlierThan(const AndroidUiTask& aOther) const
{
if (mRunTime) {
return aOther.mRunTime ? mRunTime < aOther.mRunTime : false;
}
// In the case of no delay, we're earlier if aOther has a delay.
// Otherwise, we're not earlier, to maintain task order.
return !!aOther.mRunTime;
}
int64_t MillisecondsToRunTime() const
{
if (mRunTime) {
return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds());
}
return 0;
}
already_AddRefed<nsIRunnable> TakeTask()
{
return mTask.forget();
}
private:
nsCOMPtr<nsIRunnable> mTask;
const TimeStamp mRunTime;
};
class CreateOnUiThread : public Runnable {
public:
CreateOnUiThread() : Runnable("CreateOnUiThread")
@ -147,7 +190,17 @@ public:
NS_IMETHOD Run() override {
MOZ_ASSERT(!sThreadDestroyed);
MOZ_ASSERT(sMessageLoopAccessMonitor);
MOZ_ASSERT(sTaskQueue);
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
sThreadDestroyed = true;
{
// Flush the queue
MutexAutoLock lock (*sTaskQueueLock);
while (AndroidUiTask* task = sTaskQueue->getFirst()) {
delete task;
}
}
delete sMessageLoop;
sMessageLoop = nullptr;
@ -155,7 +208,6 @@ public:
nsThreadManager::get().UnregisterCurrentThread(*sThread);
sThread = nullptr;
mDestroyed = true;
sThreadDestroyed = true;
lock.NotifyAll();
return NS_OK;
}
@ -173,6 +225,47 @@ private:
bool mDestroyed;
};
void
EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
{
if (sThreadDestroyed) {
return;
}
// add the new task into the sTaskQueue, sorted with
// the earliest task first in the queue
AndroidUiTask* newTask = (aDelayMs ? new AndroidUiTask(mozilla::Move(aTask), aDelayMs)
: new AndroidUiTask(mozilla::Move(aTask)));
{
MOZ_ASSERT(sTaskQueue);
MOZ_ASSERT(sTaskQueueLock);
MutexAutoLock lock(*sTaskQueueLock);
AndroidUiTask* task = sTaskQueue->getFirst();
while (task) {
if (newTask->IsEarlierThan(*task)) {
task->setPrevious(newTask);
break;
}
task = task->getNext();
}
if (!newTask->isInList()) {
sTaskQueue->insertBack(newTask);
}
}
if (!newTask->getPrevious()) {
// if we're inserting it at the head of the queue, notify Java because
// we need to get a callback at an earlier time than the last scheduled
// callback
GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
}
}
} // namespace
namespace mozilla {
@ -182,20 +275,20 @@ CreateAndroidUiThread()
{
MOZ_ASSERT(!sThread);
MOZ_ASSERT(!sMessageLoopAccessMonitor);
sTaskQueue = new LinkedList<AndroidUiTask>();
sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock");
sMessageLoopAccessMonitor = new Monitor("AndroidUiThreadMessageLoopAccessMonitor");
sThreadDestroyed = false;
RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread;
AndroidBridge::Bridge()->PostTaskToUiThread(do_AddRef(runnable), 0);
EnqueueTask(do_AddRef(runnable), 0);
}
void
DestroyAndroidUiThread()
{
MOZ_ASSERT(sThread);
// Insure the Android bridge has not already been deconstructed.
MOZ_ASSERT(AndroidBridge::Bridge() != nullptr);
RefPtr<DestroyOnUiThread> runnable = new DestroyOnUiThread;
AndroidBridge::Bridge()->PostTaskToUiThread(do_AddRef(runnable), 0);
EnqueueTask(do_AddRef(runnable), 0);
runnable->WaitForDestruction();
delete sMessageLoopAccessMonitor;
sMessageLoopAccessMonitor = nullptr;
@ -231,4 +324,38 @@ GetAndroidUiThread()
return sThread;
}
int64_t
RunAndroidUiTasks()
{
MutexAutoLock lock(*sTaskQueueLock);
if (sThreadDestroyed) {
return -1;
}
while (!sTaskQueue->isEmpty()) {
AndroidUiTask* task = sTaskQueue->getFirst();
const int64_t timeLeft = task->MillisecondsToRunTime();
if (timeLeft > 0) {
// this task (and therefore all remaining tasks)
// have not yet reached their runtime. return the
// time left until we should be called again
return timeLeft;
}
// Retrieve task before unlocking/running.
nsCOMPtr<nsIRunnable> runnable(task->TakeTask());
// LinkedListElements auto remove from list upon destruction
delete task;
// Unlock to allow posting new tasks reentrantly.
MutexAutoUnlock unlock(*sTaskQueueLock);
runnable->Run();
if (sThreadDestroyed) {
return -1;
}
}
return -1;
}
} // namespace mozilla

View File

@ -15,6 +15,7 @@ namespace mozilla {
void CreateAndroidUiThread();
void DestroyAndroidUiThread();
int64_t RunAndroidUiTasks();
MessageLoop* GetAndroidUiThreadMessageLoop();
RefPtr<nsThread> GetAndroidUiThread();

View File

@ -242,7 +242,7 @@ public:
static int64_t RunUiThreadCallback()
{
return AndroidUiThread::RunDelayedTasksIfValid();
return RunAndroidUiTasks();
}
};

View File

@ -422,9 +422,13 @@ public:
};
NativePanZoomController::GlobalRef npzc = mNPZC;
AndroidBridge::Bridge()->PostTaskToUiThread(NewRunnableFunction(
RefPtr<nsThread> uiThread = GetAndroidUiThread();
if (!uiThread) {
return;
}
uiThread->Dispatch(NewRunnableFunction(
static_cast<void(*)(const NPZCRef&)>(callDestroy),
mozilla::Move(npzc)), 0);
mozilla::Move(npzc)), nsIThread::DISPATCH_NORMAL);
}
public: