blob: feb8f7beef43ffcdc4db80f273423a032714ccff [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#pragma once
#include "android/base/Log.h"
#include "android/base/async/Looper.h"
#include "android/base/async/ThreadLooper.h"
#include "android/base/synchronization/Lock.h"
#include "android/emulation/VmLock.h"
#include <functional>
#include <memory>
#include <vector>
namespace android {
// All operations that change the global VM state (e.g.
// virtual device operations) should happen in a thread
// that holds the global VM lock.
//
// DeviceContextRunner is a helper template class used
// to ensure that a given operation is always performed
// in such a thread. more specifically:
// - If the current thread already owns the lock,
// the operation is performed as-is.
// - Otherwise, it is queued and will be run in the
// main-loop thread as soon as possible.
//
// Usage is the following:
//
// - Define a custom type |OP| corresponding to
// the state of each operation. It must be copy-able.
//
// - Define a derived class of
// |DeviceContextRunner<OP>| that must implement the
// abstract performDeviceOperation(const OP& op) method.
//
// - Create a DeviceContextRunner<OP> instance, and call
// its init() method, passing a valid android::VmLock
// instance to it. NOTE: This should be called from
// the main loop thread, during emulation setup time!!
//
// - Whenever you want to perform an operation, call
// queueDeviceOperation(<op>) on it. If the current
// thread holds the lock, it will be
// performed immediately. Otherwise, it will be queued
// and run later. Hence the void return type of
// queueDeviceOperation; it is run asynchronously, so
// you cannot expect a return value.
template <typename T>
class DeviceContextRunner {
public:
using AutoLock = android::base::AutoLock;
using Lock = android::base::Lock;
using Looper = android::base::Looper;
using ThreadLooper = android::base::ThreadLooper;
using VmLock = android::VmLock;
using PendingList = std::vector<T>;
void init(VmLock* vmLock) { init(vmLock, ThreadLooper::get()); }
// Looper parameter is for unit testing purposes.
void init(VmLock* vmLock, Looper* looper) {
mVmLock = vmLock;
// TODO(digit): Find a better event abstraction.
//
// Operating on Looper::Timer objects is not supposed to be
// thread-safe, but it appears that their QEMU1 and QEMU2 specific
// implementation *is* (see qemu-timer.c:timer_mod()).
//
// This means that in practice the code below is safe when used
// in the context of an Android emulation engine. However, we probably
// need a better abstraction that also works with the generic
// Looper implementation (for unit-testing) or any other kind of
// runtime environment, should we one day link AndroidEmu to a
// different emulation engine.
mTimer.reset(looper->createTimer(
[](void* that, Looper::Timer*) {
static_cast<DeviceContextRunner*>(that)->onTimerEvent();
},
this));
if (!mTimer.get()) {
LOG(WARNING) << "Failed to create a loop timer, falling back "
"to regular locking!";
}
}
protected:
// To be implemented by the class that derives DeviceContextRunner:
// the method that actually touches the virtual device.
virtual void performDeviceOperation(const T& op) = 0;
// queueDeviceOperation: If the VM lock is currently held,
// we are OK to actually perform device operations.
// Otherwise, we need to add the request to a pending
// set of requests, to be finished later when we do have the VM lock.
void queueDeviceOperation(const T& op) {
if (mVmLock->isLockedBySelf()) {
// Perform the operation correctly since the current thread
// already holds the lock that protects the global VM state.
performDeviceOperation(op);
} else {
// Queue the operation in the mPendingMap structure, then
// restart the timer.
AutoLock lock(mLock);
mPending.push_back(op);
lock.unlock();
// NOTE: See TODO above why this is thread-safe when used with
// QEMU1 and QEMU2.
mTimer->startAbsolute(0);
}
}
private:
void onTimerEvent() {
// First, clear the current pending set of device commands
// and operate on each one in turn.
PendingList pending;
AutoLock lock(mLock);
pending.swap(mPending);
lock.unlock();
for (const auto& elt : pending) {
performDeviceOperation(elt);
}
// We need to do swap() above so that we can check if
// the timer needs to be re-armed.
// (if someone added an event during processing)
lock.lock();
if (mPending.size()) {
mTimer->startAbsolute(0);
}
}
VmLock* mVmLock = nullptr;
Lock mLock;
PendingList mPending;
std::unique_ptr<Looper::Timer> mTimer;
};
} // namespace android