/* * 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