Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "base/message_loop.h"
#include "mozilla/Atomics.h"
#include "mozilla/EventQueue.h"
#include "mozilla/java/GeckoThreadWrappers.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/ThreadEventQueue.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "GeckoProfiler.h"
#include "nsThread.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include <android/api-level.h>
#include <pthread.h>
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.
* 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
* nsThreadManager::GetCurrentThread(), the nsIRunnable could get stuck in the
* nsThread nsIRunnable queue. This is due to the fact that Android controls the
* event loop in the Android UI thread and has no knowledge of when the nsThread
* needs to be drained.
*/
class AndroidUiThread : public nsThread {
public:
NS_INLINE_DECL_REFCOUNTING_INHERITED(AndroidUiThread, nsThread)
AndroidUiThread()
: nsThread(
MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
nsThread::NOT_MAIN_THREAD, {.stackSize = 0}) {}
nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aFlags) override;
nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aDelayMs) override;
private:
~AndroidUiThread() {}
};
NS_IMETHODIMP
AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aFlags) {
EnqueueTask(std::move(aEvent), 0);
return NS_OK;
}
NS_IMETHODIMP
AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
uint32_t aDelayMs) {
EnqueueTask(std::move(aEvent), aDelayMs);
return NS_OK;
}
static void PumpEvents() { NS_ProcessPendingEvents(sThread.get()); }
class ThreadObserver : public nsIThreadObserver {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADOBSERVER
ThreadObserver() {}
private:
virtual ~ThreadObserver() {}
};
NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver)
NS_IMETHODIMP
ThreadObserver::OnDispatchedEvent() {
EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
return NS_OK;
}
NS_IMETHODIMP
ThreadObserver::OnProcessNextEvent(nsIThreadInternal* thread, bool mayWait) {
return NS_OK;
}
NS_IMETHODIMP
ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* thread,
bool eventWasProcessed) {
return NS_OK;
}
class AndroidUiTask : public LinkedListElement<AndroidUiTask> {
using TimeStamp = mozilla::TimeStamp;
using TimeDuration = mozilla::TimeDuration;
public:
explicit 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") {}
NS_IMETHOD Run() override {
MOZ_ASSERT(!sThreadDestroyed);
MOZ_ASSERT(sMessageLoopAccessMonitor);
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
sThread = new AndroidUiThread();
sThread->InitCurrentThread();
sThread->SetObserver(new ThreadObserver());
RegisterThreadWithProfiler();
sMessageLoop =
new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
lock.NotifyAll();
return NS_OK;
}
private:
static void RegisterThreadWithProfiler() {
#if defined(MOZ_GECKO_PROFILER)
// We don't use the PROFILER_REGISTER_THREAD macro here because by this
// point the Android UI thread is already quite a ways into its stack;
// the profiler's sampler thread will ignore a lot of frames if we do not
// provide a better value for the stack top. We'll manually obtain that
// info via pthreads.
// Fallback address if any pthread calls fail
char fallback;
char* stackTop = &fallback;
auto regOnExit = MakeScopeExit(
[&stackTop]() { profiler_register_thread("AndroidUI", stackTop); });
pthread_attr_t attrs;
if (pthread_getattr_np(pthread_self(), &attrs)) {
return;
}
void* stackBase;
size_t stackSize;
if (pthread_attr_getstack(&attrs, &stackBase, &stackSize)) {
return;
}
stackTop = static_cast<char*>(stackBase) + stackSize - 1;
#endif // defined(MOZ_GECKO_PROFILER)
}
};
class DestroyOnUiThread : public Runnable {
public:
DestroyOnUiThread() : Runnable("DestroyOnUiThread"), mDestroyed(false) {}
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;
MOZ_ASSERT(sThread);
PROFILER_UNREGISTER_THREAD();
nsThreadManager::get().UnregisterCurrentThread(*sThread);
sThread = nullptr;
mDestroyed = true;
lock.NotifyAll();
return NS_OK;
}
void WaitForDestruction() {
MOZ_ASSERT(sMessageLoopAccessMonitor);
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
while (!mDestroyed) {
lock.Wait();
}
}
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(std::move(aTask), aDelayMs)
: new AndroidUiTask(std::move(aTask)));
bool headOfList = false;
{
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);
}
headOfList = !newTask->getPrevious();
}
if (headOfList) {
// 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
java::GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
}
}
} // namespace
namespace mozilla {
void 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;
EnqueueTask(do_AddRef(runnable), 0);
}
void DestroyAndroidUiThread() {
MOZ_ASSERT(sThread);
RefPtr<DestroyOnUiThread> runnable = new DestroyOnUiThread;
EnqueueTask(do_AddRef(runnable), 0);
runnable->WaitForDestruction();
delete sMessageLoopAccessMonitor;
sMessageLoopAccessMonitor = nullptr;
}
MessageLoop* GetAndroidUiThreadMessageLoop() {
if (!sMessageLoopAccessMonitor) {
return nullptr;
}
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
while (!sMessageLoop) {
lock.Wait();
}
return sMessageLoop;
}
RefPtr<nsThread> GetAndroidUiThread() {
if (!sMessageLoopAccessMonitor) {
return nullptr;
}
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
while (!sThread) {
lock.Wait();
}
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