diff options
Diffstat (limited to 'gps/utils/LocTimer.cpp')
-rw-r--r-- | gps/utils/LocTimer.cpp | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/gps/utils/LocTimer.cpp b/gps/utils/LocTimer.cpp new file mode 100644 index 0000000..915cf54 --- /dev/null +++ b/gps/utils/LocTimer.cpp @@ -0,0 +1,648 @@ +/* Copyright (c) 2015, 2020 The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <errno.h> +#include <sys/timerfd.h> +#include <sys/epoll.h> +#include <log_util.h> +#include <loc_timer.h> +#include <LocTimer.h> +#include <LocHeap.h> +#include <LocThread.h> +#include <LocSharedLock.h> +#include <MsgTask.h> + +#ifdef __HOST_UNIT_TEST__ +#define EPOLLWAKEUP 0 +#define CLOCK_BOOTTIME CLOCK_MONOTONIC +#define CLOCK_BOOTTIME_ALARM CLOCK_MONOTONIC +#endif + +namespace loc_util { + +/* +There are implementations of 5 classes in this file: +LocTimer, LocTimerDelegate, LocTimerContainer, LocTimerPollTask, LocTimerWrapper + +LocTimer - client front end, interface for client to start / stop timers, also + to provide a callback. +LocTimerDelegate - an internal timer entity, which also is a LocRankable obj. + Its life cycle is different than that of LocTimer. It gets + created when LocTimer::start() is called, and gets deleted + when it expires or clients calls the hosting LocTimer obj's + stop() method. When a LocTimerDelegate obj is ticking, it + stays in the corresponding LocTimerContainer. When expired + or stopped, the obj is removed from the container. Since it + is also a LocRankable obj, and LocTimerContainer also is a + heap, its ranks() implementation decides where it is placed + in the heap. +LocTimerContainer - core of the timer service. It is a container (derived from + LocHeap) for LocTimerDelegate (implements LocRankable) objs. + There are 2 of such containers, one for sw timers (or Linux + timers) one for hw timers (or Linux alarms). It adds one of + each (those that expire the soonest) to kernel via services + provided by LocTimerPollTask. All the heap management on the + LocTimerDelegate objs are done in the MsgTask context, such + that synchronization is ensured. +LocTimerPollTask - is a class that wraps timerfd and epoll POXIS APIs. It also + both implements LocRunnalbe with epoll_wait() in the run() + method. It is also a LocThread client, so as to loop the run + method. +LocTimerWrapper - a LocTimer client itself, to implement the existing C API with + APIs, loc_timer_start() and loc_timer_stop(). + +*/ + +class LocTimerPollTask; + +// This is a multi-functaional class that: +// * extends the LocHeap class for the detection of head update upon add / remove +// events. When that happens, soonest time out changes, so timerfd needs update. +// * contains the timers, and add / remove them into the heap +// * provides and maps 2 of such containers, one for timers (or mSwTimers), one +// for alarms (or mHwTimers); +// * provides a polling thread; +// * provides a MsgTask thread for synchronized add / remove / timer client callback. +class LocTimerContainer : public LocHeap { + // mutex to synchronize getters of static members + static pthread_mutex_t mMutex; + // Container of timers + static LocTimerContainer* mSwTimers; + // Container of alarms + static LocTimerContainer* mHwTimers; + // Msg task to provider msg Q, sender and reader. + static MsgTask* mMsgTask; + // Poll task to provide epoll call and threading to poll. + static LocTimerPollTask* mPollTask; + // timer / alarm fd + int mDevFd; + // ctor + LocTimerContainer(bool wakeOnExpire); + // dtor + ~LocTimerContainer(); + static MsgTask* getMsgTaskLocked(); + static LocTimerPollTask* getPollTaskLocked(); + // extend LocHeap and pop if the top outRanks input + LocTimerDelegate* popIfOutRanks(LocTimerDelegate& timer); + // update the timer POSIX calls with updated soonest timer spec + void updateSoonestTime(LocTimerDelegate* priorTop); + +public: + // factory method to control the creation of mSwTimers / mHwTimers + static LocTimerContainer* get(bool wakeOnExpire); + + LocTimerDelegate* getSoonestTimer(); + int getTimerFd(); + // add a timer / alarm obj into the container + void add(LocTimerDelegate& timer); + // remove a timer / alarm obj from the container + void remove(LocTimerDelegate& timer); + // handling of timer / alarm expiration + void expire(); +}; + +class TimerRunnable : public LocRunnable { + const int mFd; +public: + inline TimerRunnable(const int fd) : mFd(fd) {} + // The method to be implemented by thread clients + // and be scheduled by LocThread + // This method will be repeated called until it returns false; or + // until thread is stopped. + virtual bool run() override; + + // The method to wake up the potential blocking thread + // no op if not applicable + inline virtual void interrupt() { close(mFd); } +}; + +// This class implements the polling thread that epolls imer / alarm fds. +// The LocRunnable::run() contains the actual polling. The other methods +// will be run in the caller's thread context to add / remove timer / alarm +// fds the kernel, while the polling is blocked on epoll_wait() call. +// Since the design is that we have maximally 2 polls, one for all the +// timers; one for all the alarms, we will poll at most on 2 fds. But it +// is possile that all we have are only timers or alarms at one time, so we +// allow dynamically add / remove fds we poll on. The design decision of +// having 1 fd per container of timer / alarm is such that, we may not need +// to make a system call each time a timer / alarm is added / removed, unless +// that changes the "soonest" time out of that of all the timers / alarms. +class LocTimerPollTask { + // the epoll fd + const int mFd; + // the thread that calls TimerRunnable::run() method, where + // epoll_wait() is blocking and waiting for events.. + LocThread mThread; +public: + // ctor + LocTimerPollTask(); + // dtor + ~LocTimerPollTask() = default; + // add a container of timers. Each contain has a unique device fd, i.e. + // either timer or alarm fd, and a heap of timers / alarms. It is expected + // that container would have written to the device fd with the soonest + // time out value in the heap at the time of calling this method. So all + // this method does is to add the fd of the input container to the poll + // and also add the pointer of the container to the event data ptr, such + // when poll_wait wakes up on events, we know who is the owner of the fd. + void addPoll(LocTimerContainer& timerContainer); + // remove a fd that is assciated with a container. The expectation is that + // the atual timer would have been removed from the container. + void removePoll(LocTimerContainer& timerContainer); +}; + +// Internal class of timer obj. It gets born when client calls LocTimer::start(); +// and gets deleted when client calls LocTimer::stop() or when the it expire()'s. +// This class implements LocRankable::ranks() so that when an obj is added into +// the container (of LocHeap), it gets placed in sorted order. +class LocTimerDelegate : public LocRankable { + friend class LocTimerContainer; + friend class LocTimer; + LocTimer* mClient; + LocSharedLock* mLock; + struct timespec mFutureTime; + LocTimerContainer* mContainer; + // not a complete obj, just ctor for LocRankable comparisons + inline LocTimerDelegate(struct timespec& delay) + : mClient(NULL), mLock(NULL), mFutureTime(delay), mContainer(NULL) {} + inline ~LocTimerDelegate() { if (mLock) { mLock->drop(); mLock = NULL; } } +public: + LocTimerDelegate(LocTimer& client, struct timespec& futureTime, LocTimerContainer* container); + void destroyLocked(); + // LocRankable virtual method + virtual int ranks(LocRankable& rankable); + void expire(); + inline struct timespec getFutureTime() { return mFutureTime; } +}; + +/***************************LocTimerContainer methods***************************/ + +// Most of these static recources are created on demand. They however are never +// destoyed. The theory is that there are processes that link to this util lib +// but never use timer, then these resources would never need to be created. +// For those processes that do use timer, it will likely also need to every +// once in a while. It might be cheaper keeping them around. +pthread_mutex_t LocTimerContainer::mMutex = PTHREAD_MUTEX_INITIALIZER; +LocTimerContainer* LocTimerContainer::mSwTimers = NULL; +LocTimerContainer* LocTimerContainer::mHwTimers = NULL; +MsgTask* LocTimerContainer::mMsgTask = NULL; +LocTimerPollTask* LocTimerContainer::mPollTask = NULL; + +// ctor - initialize timer heaps +// A container for swTimer (timer) is created, when wakeOnExpire is true; or +// HwTimer (alarm), when wakeOnExpire is false. +LocTimerContainer::LocTimerContainer(bool wakeOnExpire) : + mDevFd(timerfd_create(wakeOnExpire ? CLOCK_BOOTTIME_ALARM : CLOCK_BOOTTIME, 0)) { + + if ((-1 == mDevFd) && (errno == EINVAL)) { + LOC_LOGW("%s: timerfd_create failure, fallback to CLOCK_MONOTONIC - %s", + __FUNCTION__, strerror(errno)); + mDevFd = timerfd_create(CLOCK_MONOTONIC, 0); + } + + if (-1 != mDevFd) { + // ensure we have the necessary resources created + LocTimerContainer::getPollTaskLocked(); + LocTimerContainer::getMsgTaskLocked(); + } else { + LOC_LOGE("%s: timerfd_create failure - %s", __FUNCTION__, strerror(errno)); + } +} + +// dtor +// we do not ever destroy the static resources. +inline +LocTimerContainer::~LocTimerContainer() { + close(mDevFd); +} + +LocTimerContainer* LocTimerContainer::get(bool wakeOnExpire) { + // get the reference of either mHwTimer or mSwTimers per wakeOnExpire + LocTimerContainer*& container = wakeOnExpire ? mHwTimers : mSwTimers; + // it is cheap to check pointer first than locking mutext unconditionally + if (!container) { + pthread_mutex_lock(&mMutex); + // let's check one more time to be safe + if (!container) { + container = new LocTimerContainer(wakeOnExpire); + // timerfd_create failure + if (-1 == container->getTimerFd()) { + delete container; + container = NULL; + } + } + pthread_mutex_unlock(&mMutex); + } + return container; +} + +MsgTask* LocTimerContainer::getMsgTaskLocked() { + // it is cheap to check pointer first than locking mutext unconditionally + if (!mMsgTask) { + mMsgTask = new MsgTask("LocTimerMsgTask"); + } + return mMsgTask; +} + +LocTimerPollTask* LocTimerContainer::getPollTaskLocked() { + // it is cheap to check pointer first than locking mutext unconditionally + if (!mPollTask) { + mPollTask = new LocTimerPollTask(); + } + return mPollTask; +} + +inline +LocTimerDelegate* LocTimerContainer::getSoonestTimer() { + return (LocTimerDelegate*)(peek()); +} + +inline +int LocTimerContainer::getTimerFd() { + return mDevFd; +} + +void LocTimerContainer::updateSoonestTime(LocTimerDelegate* priorTop) { + LocTimerDelegate* curTop = getSoonestTimer(); + + // check if top has changed + if (curTop != priorTop) { + struct itimerspec delay; + memset(&delay, 0, sizeof(struct itimerspec)); + bool toSetTime = false; + // if tree is empty now, we remove poll and disarm timer + if (!curTop) { + mPollTask->removePoll(*this); + // setting the values to disarm timer + delay.it_value.tv_sec = 0; + delay.it_value.tv_nsec = 0; + toSetTime = true; + } else if (!priorTop || curTop->outRanks(*priorTop)) { + // do this first to avoid race condition, in case settime is called + // with too small an interval + mPollTask->addPoll(*this); + delay.it_value = curTop->getFutureTime(); + toSetTime = true; + } + if (toSetTime) { + timerfd_settime(getTimerFd(), TFD_TIMER_ABSTIME, &delay, NULL); + } + } +} + +// all the heap management is done in the MsgTask context. +inline +void LocTimerContainer::add(LocTimerDelegate& timer) { + struct MsgTimerPush : public LocMsg { + LocTimerContainer* mTimerContainer; + LocTimerDelegate* mTimer; + inline MsgTimerPush(LocTimerContainer& container, LocTimerDelegate& timer) : + LocMsg(), mTimerContainer(&container), mTimer(&timer) {} + inline virtual void proc() const { + LocTimerDelegate* priorTop = mTimerContainer->getSoonestTimer(); + mTimerContainer->push((LocRankable&)(*mTimer)); + mTimerContainer->updateSoonestTime(priorTop); + } + }; + + mMsgTask->sendMsg(new MsgTimerPush(*this, timer)); +} + +// all the heap management is done in the MsgTask context. +void LocTimerContainer::remove(LocTimerDelegate& timer) { + struct MsgTimerRemove : public LocMsg { + LocTimerContainer* mTimerContainer; + LocTimerDelegate* mTimer; + inline MsgTimerRemove(LocTimerContainer& container, LocTimerDelegate& timer) : + LocMsg(), mTimerContainer(&container), mTimer(&timer) {} + inline virtual void proc() const { + LocTimerDelegate* priorTop = mTimerContainer->getSoonestTimer(); + + // update soonest timer only if mTimer is actually removed from + // mTimerContainer AND mTimer is not priorTop. + if (priorTop == ((LocHeap*)mTimerContainer)->remove((LocRankable&)*mTimer)) { + // if passing in NULL, we tell updateSoonestTime to update + // kernel with the current top timer interval. + mTimerContainer->updateSoonestTime(NULL); + } + // all timers are deleted here, and only here. + delete mTimer; + } + }; + + mMsgTask->sendMsg(new MsgTimerRemove(*this, timer)); +} + +// all the heap management is done in the MsgTask context. +// Upon expire, we check and continuously pop the heap until +// the top node's timeout is in the future. +void LocTimerContainer::expire() { + struct MsgTimerExpire : public LocMsg { + LocTimerContainer* mTimerContainer; + inline MsgTimerExpire(LocTimerContainer& container) : + LocMsg(), mTimerContainer(&container) {} + inline virtual void proc() const { + struct timespec now; + // get time spec of now + clock_gettime(CLOCK_BOOTTIME, &now); + LocTimerDelegate timerOfNow(now); + // pop everything in the heap that outRanks now, i.e. has time older than now + // and then call expire() on that timer. + for (LocTimerDelegate* timer = (LocTimerDelegate*)mTimerContainer->pop(); + NULL != timer; + timer = mTimerContainer->popIfOutRanks(timerOfNow)) { + // the timer delegate obj will be deleted before the return of this call + timer->expire(); + } + mTimerContainer->updateSoonestTime(NULL); + } + }; + + struct itimerspec delay; + memset(&delay, 0, sizeof(struct itimerspec)); + timerfd_settime(getTimerFd(), TFD_TIMER_ABSTIME, &delay, NULL); + mPollTask->removePoll(*this); + mMsgTask->sendMsg(new MsgTimerExpire(*this)); +} + +LocTimerDelegate* LocTimerContainer::popIfOutRanks(LocTimerDelegate& timer) { + LocTimerDelegate* poppedNode = NULL; + if (mTree && !timer.outRanks(*peek())) { + poppedNode = (LocTimerDelegate*)(pop()); + } + + return poppedNode; +} + + +/***************************LocTimerPollTask methods***************************/ + +inline +LocTimerPollTask::LocTimerPollTask() + : mFd(epoll_create(2)), mThread() { + // before a next call returens, a thread will be created. The run() method + // could already be running in parallel. Also, since each of the objs + // creates a thread, the container will make sure that there will be only + // one of such obj for our timer implementation. + mThread.start("LocTimerPollTask", std::make_shared<TimerRunnable>(mFd)); +} + +void LocTimerPollTask::addPoll(LocTimerContainer& timerContainer) { + struct epoll_event ev; + memset(&ev, 0, sizeof(ev)); + + ev.events = EPOLLIN; + ev.data.fd = timerContainer.getTimerFd(); + // it is important that we set this context pointer with the input + // timer container this is how we know which container should handle + // which expiration. + ev.data.ptr = &timerContainer; + + epoll_ctl(mFd, EPOLL_CTL_ADD, timerContainer.getTimerFd(), &ev); +} + +inline +void LocTimerPollTask::removePoll(LocTimerContainer& timerContainer) { + epoll_ctl(mFd, EPOLL_CTL_DEL, timerContainer.getTimerFd(), NULL); +} + +// The polling thread context will call this method. If run() method needs to +// be repetitvely called, it must return true from the previous call. +bool TimerRunnable::run() { + struct epoll_event ev[2]; + + // we have max 2 descriptors to poll from + int fds = epoll_wait(mFd, ev, 2, -1); + + // we pretty much want to continually poll until the fd is closed + bool rerun = (fds > 0) || (errno == EINTR); + + if (fds > 0) { + // we may have 2 events + for (int i = 0; i < fds; i++) { + // each fd has a context pointer associated with the right timer container + LocTimerContainer* container = (LocTimerContainer*)(ev[i].data.ptr); + if (container) { + container->expire(); + } else { + epoll_ctl(mFd, EPOLL_CTL_DEL, ev[i].data.fd, NULL); + } + } + } + + // if rerun is true, we are requesting to be scheduled again + return rerun; +} + +/***************************LocTimerDelegate methods***************************/ + +inline +LocTimerDelegate::LocTimerDelegate(LocTimer& client, + struct timespec& futureTime, + LocTimerContainer* container) + : mClient(&client), + mLock(mClient->mLock->share()), + mFutureTime(futureTime), + mContainer(container) { + // adding the timer into the container + mContainer->add(*this); +} + +inline +void LocTimerDelegate::destroyLocked() { + // client handle will likely be deleted soon after this + // method returns. Nulling this handle so that expire() + // won't call the callback on the dead handle any more. + mClient = NULL; + + if (mContainer) { + LocTimerContainer* container = mContainer; + mContainer = NULL; + if (container) { + container->remove(*this); + } + } // else we do not do anything. No such *this* can be + // created and reached here with mContainer ever been + // a non NULL. So *this* must have reached the if clause + // once, and we want it reach there only once. +} + +int LocTimerDelegate::ranks(LocRankable& rankable) { + int rank = -1; + LocTimerDelegate* timer = (LocTimerDelegate*)(&rankable); + if (timer) { + // larger time ranks lower!!! + // IOW, if input obj has bigger tv_sec/tv_nsec, this obj outRanks higher + rank = timer->mFutureTime.tv_sec - mFutureTime.tv_sec; + if(0 == rank) + { + //rank against tv_nsec for msec accuracy + rank = (int)(timer->mFutureTime.tv_nsec - mFutureTime.tv_nsec); + } + } + return rank; +} + +inline +void LocTimerDelegate::expire() { + // keeping a copy of client pointer to be safe + // when timeOutCallback() is called at the end of this + // method, *this* obj may be already deleted. + LocTimer* client = mClient; + // force a stop, which will lead to delete of this obj + if (client && client->stop()) { + // calling client callback with a pointer save on the stack + // only if stop() returns true, i.e. it hasn't been stopped + // already. + client->timeOutCallback(); + } +} + + +/***************************LocTimer methods***************************/ +LocTimer::LocTimer() : mTimer(NULL), mLock(new LocSharedLock()) { +} + +LocTimer::~LocTimer() { + stop(); + if (mLock) { + mLock->drop(); + mLock = NULL; + } +} + +bool LocTimer::start(unsigned int timeOutInMs, bool wakeOnExpire) { + bool success = false; + mLock->lock(); + if (!mTimer) { + struct timespec futureTime; + clock_gettime(CLOCK_BOOTTIME, &futureTime); + futureTime.tv_sec += timeOutInMs / 1000; + futureTime.tv_nsec += (timeOutInMs % 1000) * 1000000; + if (futureTime.tv_nsec >= 1000000000) { + futureTime.tv_sec += futureTime.tv_nsec / 1000000000; + futureTime.tv_nsec %= 1000000000; + } + + LocTimerContainer* container; + container = LocTimerContainer::get(wakeOnExpire); + if (NULL != container) { + mTimer = new LocTimerDelegate(*this, futureTime, container); + // if mTimer is non 0, success should be 0; or vice versa + } + success = (NULL != mTimer); + } + mLock->unlock(); + return success; +} + +bool LocTimer::stop() { + bool success = false; + mLock->lock(); + if (mTimer) { + LocTimerDelegate* timer = mTimer; + mTimer = NULL; + if (timer) { + timer->destroyLocked(); + success = true; + } + } + mLock->unlock(); + return success; +} + +/***************************LocTimerWrapper methods***************************/ +////////////////////////////////////////////////////////////////////////// +// This section below wraps for the C style APIs +////////////////////////////////////////////////////////////////////////// +class LocTimerWrapper : public LocTimer { + loc_timer_callback mCb; + void* mCallerData; + LocTimerWrapper* mMe; + static pthread_mutex_t mMutex; + inline ~LocTimerWrapper() { mCb = NULL; mMe = NULL; } +public: + inline LocTimerWrapper(loc_timer_callback cb, void* callerData) : + mCb(cb), mCallerData(callerData), mMe(this) { + } + void destroy() { + pthread_mutex_lock(&mMutex); + if (NULL != mCb && this == mMe) { + delete this; + } + pthread_mutex_unlock(&mMutex); + } + virtual void timeOutCallback() { + loc_timer_callback cb = mCb; + void* callerData = mCallerData; + if (cb) { + cb(callerData, 0); + } + destroy(); + } +}; + +} // namespace loc_util + +////////////////////////////////////////////////////////////////////////// +// This section below wraps for the C style APIs +////////////////////////////////////////////////////////////////////////// + +using loc_util::LocTimerWrapper; + +pthread_mutex_t LocTimerWrapper::mMutex = PTHREAD_MUTEX_INITIALIZER; + +void* loc_timer_start(uint64_t msec, loc_timer_callback cb_func, + void *caller_data, bool wake_on_expire) +{ + LocTimerWrapper* locTimerWrapper = NULL; + + if (cb_func) { + locTimerWrapper = new LocTimerWrapper(cb_func, caller_data); + + if (locTimerWrapper) { + locTimerWrapper->start(msec, wake_on_expire); + } + } + + return locTimerWrapper; +} + +void loc_timer_stop(void*& handle) +{ + if (handle) { + LocTimerWrapper* locTimerWrapper = (LocTimerWrapper*)(handle); + locTimerWrapper->destroy(); + handle = NULL; + } +} |