/*
* 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/log.h"
#include "chre_api/chre/version.h"
namespace chre {
EventLoop::EventLoop()
: mTimerPool(*this) {}
bool EventLoop::findNanoappInstanceIdByAppId(uint64_t appId,
uint32_t *instanceId) {
CHRE_ASSERT(instanceId != nullptr);
// TODO: would be nice to have a ConditionalLockGuard where we just pass this
// bool to the constructor and it automatically handles the unlock for us
bool needLock = (getCurrentEventLoop() != this);
if (needLock) {
mNanoappsLock.lock();
}
bool found = false;
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if (app->getAppId() == appId) {
*instanceId = app->getInstanceId();
found = true;
break;
}
}
if (needLock) {
mNanoappsLock.unlock();
}
return found;
}
void EventLoop::forEachNanoapp(NanoappCallbackFunction *callback, void *data) {
bool needLock = (getCurrentEventLoop() != this);
if (needLock) {
mNanoappsLock.lock();
}
for (const UniquePtr<Nanoapp>& nanoapp : mNanoapps) {
callback(nanoapp.get(), data);
}
if (needLock) {
mNanoappsLock.unlock();
}
}
void EventLoop::run() {
LOGI("EventLoop start");
mRunning = true;
bool havePendingEvents = false;
while (mRunning) {
// TODO: document the two-stage event delivery process further... general
// idea is we block in mEvents.pop() if we know that no apps have pending
// events
if (!havePendingEvents || !mEvents.empty()) {
// TODO: this is *not* thread-safe; if we have multiple EventLoops, then
// there is no safety mechanism that ensures an event is not freed twice,
// or that its free callback is invoked in the proper EventLoop, etc.
Event *event = mEvents.pop();
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);
}
}
// TODO: most basic round-robin implementation - we might want to have some
// kind of priority in the future, but this should be good enough for now
havePendingEvents = false;
for (const UniquePtr<Nanoapp>& app : mNanoapps) {
if (app->hasPendingEvent()) {
havePendingEvents |= deliverNextEvent(app);
}
}
}
// Drop any events pending distribution
while (!mEvents.empty()) {
freeEvent(mEvents.pop());
}
// Stop all running nanoapps
while (!mNanoapps.empty()) {
stopNanoapp(mNanoapps.size() - 1);
}
LOGI("Exiting EventLoop");
}
bool EventLoop::startNanoapp(UniquePtr<Nanoapp>& nanoapp) {
CHRE_ASSERT(!nanoapp.isNull());
bool success = false;
auto *eventLoopManager = EventLoopManagerSingleton::get();
uint32_t existingInstanceId;
if (nanoapp.isNull()) {
// no-op, invalid argument
} else if (eventLoopManager->findNanoappInstanceIdByAppId(nanoapp->getAppId(),
&existingInstanceId,
nullptr)) {
LOGE("App with ID 0x%016" PRIx64 " already exists as instance ID 0x%"
PRIx32, nanoapp->getAppId(), existingInstanceId);
} else if (!mNanoapps.prepareForPush()) {
LOGE("Failed to allocate space for new nanoapp");
} else {
nanoapp->setInstanceId(eventLoopManager->getNextInstanceId());
mCurrentApp = nanoapp.get();
success = nanoapp->start();
mCurrentApp = nullptr;
if (!success) {
LOGE("Nanoapp %" PRIu32 " failed to start", nanoapp->getInstanceId());
} else {
LockGuard<Mutex> lock(mNanoappsLock);
mNanoapps.push_back(std::move(nanoapp));
}
}
return success;
}
void EventLoop::stopNanoapp(Nanoapp *nanoapp) {
for (size_t i = 0; i < mNanoapps.size(); i++) {
if (nanoapp == mNanoapps[i].get()) {
stopNanoapp(i);
return;
}
}
CHRE_ASSERT_LOG(false,
"Attempted to stop a nanoapp that is not already running");
}
bool EventLoop::postEvent(uint16_t eventType, void *eventData,
chreEventCompleteFunction *freeCallback, uint32_t senderInstanceId,
uint32_t targetInstanceId) {
bool success = false;
if (mRunning) {
Event *event = mEventPool.allocate(eventType, eventData, freeCallback,
senderInstanceId, targetInstanceId);
if (event != nullptr) {
success = mEvents.push(event);
} else {
LOGE("Failed to allocate event");
}
}
return success;
}
void EventLoop::stop() {
postEvent(0, nullptr, nullptr, kSystemInstanceId, kSystemInstanceId);
// Stop accepting new events and tell the main loop to finish
mRunning = false;
}
Nanoapp *EventLoop::getCurrentNanoapp() const {
CHRE_ASSERT(getCurrentEventLoop() == this);
return mCurrentApp;
}
size_t EventLoop::getNanoappCount() const {
CHRE_ASSERT(getCurrentEventLoop() == this);
return mNanoapps.size();
}
TimerPool& EventLoop::getTimerPool() {
return mTimerPool;
}
Nanoapp *EventLoop::findNanoappByInstanceId(uint32_t instanceId) {
bool needLock = (getCurrentEventLoop() != this);
if (needLock) {
mNanoappsLock.lock();
}
Nanoapp *nanoapp = lookupAppByInstanceId(instanceId);
if (needLock) {
mNanoappsLock.unlock();
}
return nanoapp;
}
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);
}
}
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();
}
Nanoapp *EventLoop::lookupAppByInstanceId(uint32_t instanceId) {
// 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::stopNanoapp(size_t index) {
const UniquePtr<Nanoapp>& nanoapp = mNanoapps[index];
// Process any events pending in this app's queue. Note that since we're
// running in the context of this EventLoop, no new events will be added to
// this nanoapp's event queue while we're doing this, so once it's empty, we
// can be assured it will stay that way.
while (nanoapp->hasPendingEvent()) {
deliverNextEvent(nanoapp);
}
// TODO: to safely stop a nanoapp while the EventLoop is still running, we
// need to deliver/purge any events that the nanoapp sent itself prior to
// calling end(), so that we won't try to invoke a free callback after
// unloading the nanoapp where that callback resides. Likewise, we need to
// make sure any messages to the host from this nanoapp are flushed as well.
// Let the app know it's going away
mCurrentApp = nanoapp.get();
nanoapp->end();
mCurrentApp = nullptr;
// Destroy the Nanoapp instance
{
LockGuard<Mutex> lock(mNanoappsLock);
mNanoapps.erase(index);
}
}
} // namespace chre