/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "chre/core/event_loop.h"
#include "chre/core/event.h"
#include "chre/core/event_loop_manager.h"
#include "chre/core/nanoapp.h"
#include "chre/platform/context.h"
#include "chre/platform/fatal_error.h"
#include "chre/platform/log.h"
#include "chre/util/conditional_lock_guard.h"
#include "chre/util/lock_guard.h"
#include "chre/util/system/debug_dump.h"
#include "chre_api/chre/version.h"
namespace chre {
namespace {
/**
* Populates a chreNanoappInfo structure using info from the given Nanoapp
* instance.
*
* @param app A potentially null pointer to the Nanoapp to read from
* @param info The structure to populate - should not be null, but this function
* will handle that input
*
* @return true if neither app nor info were null, and info was populated
*/
bool populateNanoappInfo(const Nanoapp *app, struct chreNanoappInfo *info) {
bool success = false;
if (app != nullptr && info != nullptr) {
info->appId = app->getAppId();
info->version = app->getAppVersion();
info->instanceId = app->getInstanceId();
success = true;
}
return success;
}
} // anonymous namespace
bool EventLoop::findNanoappInstanceIdByAppId(uint64_t appId,
uint32_t *instanceId) const {
CHRE_ASSERT(instanceId != nullptr);
ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
bool found = false;
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if (app->getAppId() == appId) {
*instanceId = app->getInstanceId();
found = true;
break;
}
}
return found;
}
void EventLoop::forEachNanoapp(NanoappCallbackFunction *callback, void *data) {
ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
for (const UniquePtr<Nanoapp>& nanoapp : mNanoapps) {
callback(nanoapp.get(), data);
}
}
void EventLoop::invokeMessageFreeFunction(
uint64_t appId, chreMessageFreeFunction *freeFunction, void *message,
size_t messageSize) {
Nanoapp *nanoapp = lookupAppByAppId(appId);
if (nanoapp == nullptr) {
LOGE("Couldn't find app 0x%016" PRIx64 " for message free callback", appId);
} else {
auto prevCurrentApp = mCurrentApp;
mCurrentApp = nanoapp;
freeFunction(message, messageSize);
mCurrentApp = prevCurrentApp;
}
}
void EventLoop::run() {
LOGI("EventLoop start");
bool havePendingEvents = false;
while (mRunning) {
// Events are delivered in two stages: first they arrive in the inbound
// event queue mEvents (potentially posted from another thread), then within
// this context these events are distributed to smaller event queues
// associated with each Nanoapp that should receive the event. Once the
// event is delivered to all interested Nanoapps, its free callback is
// invoked.
if (!havePendingEvents || !mEvents.empty()) {
if (mEvents.size() > mMaxEventPoolUsage) {
mMaxEventPoolUsage = mEvents.size();
}
// mEvents.pop() will be a blocking call if mEvents.empty()
distributeEvent(mEvents.pop());
}
havePendingEvents = deliverEvents();
mPowerControlManager.postEventLoopProcess(mEvents.size());
}
// Deliver any events sitting in Nanoapps' own queues (we could drop them to
// exit faster, but this is less code and should complete quickly under normal
// conditions), then purge the main queue of events pending distribution. All
// nanoapps should be prevented from sending events or messages at this point
// via currentNanoappIsStopping() returning true.
flushNanoappEventQueues();
while (!mEvents.empty()) {
freeEvent(mEvents.pop());
}
// Unload all running nanoapps
while (!mNanoapps.empty()) {
unloadNanoappAtIndex(mNanoapps.size() - 1);
}
LOGI("Exiting EventLoop");
}
bool EventLoop::startNanoapp(UniquePtr<Nanoapp>& nanoapp) {
CHRE_ASSERT(!nanoapp.isNull());
bool success = false;
auto *eventLoopManager = EventLoopManagerSingleton::get();
EventLoop& eventLoop = eventLoopManager->getEventLoop();
uint32_t existingInstanceId;
if (nanoapp.isNull()) {
// no-op, invalid argument
} else if (eventLoop.findNanoappInstanceIdByAppId(nanoapp->getAppId(),
&existingInstanceId)) {
LOGE("App with ID 0x%016" PRIx64 " already exists as instance ID 0x%"
PRIx32, nanoapp->getAppId(), existingInstanceId);
} else if (!mNanoapps.prepareForPush()) {
LOG_OOM();
} else {
nanoapp->setInstanceId(eventLoopManager->getNextInstanceId());
LOGD("Instance ID %" PRIu32 " assigned to app ID 0x%016" PRIx64,
nanoapp->getInstanceId(), nanoapp->getAppId());
Nanoapp *newNanoapp = nanoapp.get();
{
LockGuard<Mutex> lock(mNanoappsLock);
mNanoapps.push_back(std::move(nanoapp));
// After this point, nanoapp is null as we've transferred ownership into
// mNanoapps.back() - use newNanoapp to reference it
}
mCurrentApp = newNanoapp;
success = newNanoapp->start();
mCurrentApp = nullptr;
if (!success) {
// TODO: to be fully safe, need to purge/flush any events and messages
// sent by the nanoapp here (but don't call nanoappEnd). For now, we just
// destroy the Nanoapp instance.
LOGE("Nanoapp %" PRIu32 " failed to start", newNanoapp->getInstanceId());
// Note that this lock protects against concurrent read and modification
// of mNanoapps, but we are assured that no new nanoapps were added since
// we pushed the new nanoapp
LockGuard<Mutex> lock(mNanoappsLock);
mNanoapps.pop_back();
} else {
notifyAppStatusChange(CHRE_EVENT_NANOAPP_STARTED, *newNanoapp);
}
}
return success;
}
bool EventLoop::unloadNanoapp(uint32_t instanceId,
bool allowSystemNanoappUnload) {
bool unloaded = false;
for (size_t i = 0; i < mNanoapps.size(); i++) {
if (instanceId == mNanoapps[i]->getInstanceId()) {
if (!allowSystemNanoappUnload && mNanoapps[i]->isSystemNanoapp()) {
LOGE("Refusing to unload system nanoapp");
} else {
// Make sure all messages sent by this nanoapp at least have their
// associated free callback processing pending in the event queue (i.e.
// there are no messages pending delivery to the host)
EventLoopManagerSingleton::get()->getHostCommsManager()
.flushMessagesSentByNanoapp(mNanoapps[i]->getAppId());
// Distribute all inbound events we have at this time - here we're
// interested in handling any message free callbacks generated by
// flushMessagesSentByNanoapp()
flushInboundEventQueue();
// Mark that this nanoapp is stopping early, so it can't send events or
// messages during the nanoapp event queue flush
mStoppingNanoapp = mNanoapps[i].get();
// Process any pending events, with the intent of ensuring that we free
// all events generated by this nanoapp
flushNanoappEventQueues();
// Post the unload event now (so we can reference the Nanoapp instance
// directly), but nanoapps won't get it until after the unload completes
notifyAppStatusChange(CHRE_EVENT_NANOAPP_STOPPED, *mStoppingNanoapp);
// Finally, we are at a point where there should not be any pending
// events or messages sent by the app that could potentially reference
// the nanoapp's memory, so we are safe to unload it
unloadNanoappAtIndex(i);
mStoppingNanoapp = nullptr;
// TODO: right now we assume that the nanoapp will clean up all of its
// resource allocations in its nanoappEnd callback (memory, sensor
// subscriptions, etc.), otherwise we're leaking resources. We should
// perform resource cleanup automatically here to avoid these types of
// potential leaks.
LOGD("Unloaded nanoapp with instanceId %" PRIu32, instanceId);
unloaded = true;
}
break;
}
}
return unloaded;
}
bool EventLoop::postEvent(uint16_t eventType, void *eventData,
chreEventCompleteFunction *freeCallback, uint32_t senderInstanceId,
uint32_t targetInstanceId) {
bool success = false;
if (mRunning && (senderInstanceId == kSystemInstanceId ||
mEventPool.getFreeBlockCount() > kMinReservedSystemEventCount)) {
success = allocateAndPostEvent(eventType, eventData, freeCallback,
senderInstanceId,targetInstanceId);
if (!success) {
// This can only happen if the event is a system event type. This
// postEvent method will fail if a non-system event is posted when the
// memory pool is close to full.
FATAL_ERROR("Failed to allocate system event type %" PRIu16, eventType);
}
}
return success;
}
bool EventLoop::postEventOrFree(uint16_t eventType, void *eventData,
chreEventCompleteFunction *freeCallback, uint32_t senderInstanceId,
uint32_t targetInstanceId) {
bool success = false;
if (mRunning) {
success = allocateAndPostEvent(eventType, eventData, freeCallback,
senderInstanceId,targetInstanceId);
if (!success) {
freeCallback(eventType, eventData);
LOGE("Failed to allocate event 0x%" PRIx16 " to instanceId %" PRIu32,
eventType, targetInstanceId);
}
}
return success;
}
void EventLoop::stop() {
auto callback = [](uint16_t /* type */, void * /* data */) {
EventLoopManagerSingleton::get()->getEventLoop().onStopComplete();
};
// Stop accepting new events and tell the main loop to finish.
postEvent(0, nullptr, callback, kSystemInstanceId, kSystemInstanceId);
}
void EventLoop::onStopComplete() {
mRunning = false;
}
Nanoapp *EventLoop::findNanoappByInstanceId(uint32_t instanceId) const {
ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
return lookupAppByInstanceId(instanceId);
}
bool EventLoop::populateNanoappInfoForAppId(
uint64_t appId, struct chreNanoappInfo *info) const {
ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
Nanoapp *app = lookupAppByAppId(appId);
return populateNanoappInfo(app, info);
}
bool EventLoop::populateNanoappInfoForInstanceId(
uint32_t instanceId, struct chreNanoappInfo *info) const {
ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
Nanoapp *app = lookupAppByInstanceId(instanceId);
return populateNanoappInfo(app, info);
}
bool EventLoop::currentNanoappIsStopping() const {
return (mCurrentApp == mStoppingNanoapp || !mRunning);
}
void EventLoop::logStateToBuffer(char *buffer, size_t *bufferPos,
size_t bufferSize) const {
debugDumpPrint(buffer, bufferPos, bufferSize, "\nNanoapps:\n");
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
app->logStateToBuffer(buffer, bufferPos, bufferSize);
}
debugDumpPrint(buffer, bufferPos, bufferSize, "\nEvent Loop:\n");
debugDumpPrint(buffer, bufferPos, bufferSize,
" Max event pool usage: %zu/%zu\n",
mMaxEventPoolUsage, kMaxEventCount);
}
bool EventLoop::allocateAndPostEvent(uint16_t eventType, void *eventData,
chreEventCompleteFunction *freeCallback, uint32_t senderInstanceId,
uint32_t targetInstanceId) {
bool success = false;
Event *event = mEventPool.allocate(eventType, eventData, freeCallback,
senderInstanceId, targetInstanceId);
if (event != nullptr) {
success = mEvents.push(event);
}
return success;
}
bool EventLoop::deliverEvents() {
bool havePendingEvents = false;
// Do one loop of round-robin. We might want to have some kind of priority or
// time sharing in the future, but this should be good enough for now.
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if (app->hasPendingEvent()) {
havePendingEvents |= deliverNextEvent(app);
}
}
return havePendingEvents;
}
bool EventLoop::deliverNextEvent(const UniquePtr<Nanoapp>& app) {
// TODO: cleaner way to set/clear this? RAII-style?
mCurrentApp = app.get();
Event *event = app->processNextEvent();
mCurrentApp = nullptr;
if (event->isUnreferenced()) {
freeEvent(event);
}
return app->hasPendingEvent();
}
void EventLoop::distributeEvent(Event *event) {
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if ((event->targetInstanceId == chre::kBroadcastInstanceId
&& app->isRegisteredForBroadcastEvent(event->eventType))
|| event->targetInstanceId == app->getInstanceId()) {
app->postEvent(event);
}
}
if (event->isUnreferenced()) {
// Events sent to the system instance ID are processed via the free callback
// and are not expected to be delivered to any nanoapp, so no need to log a
// warning in that case
if (event->senderInstanceId != kSystemInstanceId) {
LOGW("Dropping event 0x%" PRIx16, event->eventType);
}
freeEvent(event);
}
}
void EventLoop::flushInboundEventQueue() {
while (!mEvents.empty()) {
distributeEvent(mEvents.pop());
}
}
void EventLoop::flushNanoappEventQueues() {
while (deliverEvents());
}
void EventLoop::freeEvent(Event *event) {
if (event->freeCallback != nullptr) {
// TODO: find a better way to set the context to the creator of the event
mCurrentApp = lookupAppByInstanceId(event->senderInstanceId);
event->freeCallback(event->eventType, event->eventData);
mCurrentApp = nullptr;
}
mEventPool.deallocate(event);
}
Nanoapp *EventLoop::lookupAppByAppId(uint64_t appId) const {
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if (app->getAppId() == appId) {
return app.get();
}
}
return nullptr;
}
Nanoapp *EventLoop::lookupAppByInstanceId(uint32_t instanceId) const {
// The system instance ID always has nullptr as its Nanoapp pointer, so can
// skip iterating through the nanoapp list for that case
if (instanceId != kSystemInstanceId) {
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if (app->getInstanceId() == instanceId) {
return app.get();
}
}
}
return nullptr;
}
void EventLoop::notifyAppStatusChange(uint16_t eventType,
const Nanoapp& nanoapp) {
auto *info = memoryAlloc<chreNanoappInfo>();
if (info == nullptr) {
LOG_OOM();
} else {
info->appId = nanoapp.getAppId();
info->version = nanoapp.getAppVersion();
info->instanceId = nanoapp.getInstanceId();
postEvent(eventType, info, freeEventDataCallback);
}
}
void EventLoop::unloadNanoappAtIndex(size_t index) {
const UniquePtr<Nanoapp>& nanoapp = mNanoapps[index];
// Lock here to prevent the nanoapp instance from being accessed between the
// time it is ended and fully erased
LockGuard<Mutex> lock(mNanoappsLock);
// Let the app know it's going away
mCurrentApp = nanoapp.get();
nanoapp->end();
mCurrentApp = nullptr;
// Destroy the Nanoapp instance
mNanoapps.erase(index);
}
} // namespace chre