/*
- Copyright 2004-2005 Chris Tallon
+ Copyright 2004-2007 Chris Tallon
This file is part of VOMP.
logger = Log::getInstance();
threadLock(); // lock here, the thread loop will unlock and wait
- //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 1");
if (!threadStart())
{
shutdown();
threadStop();
- TimerList::iterator i;
- UINT numTimers = timerList.size();
- while(numTimers)
+ TimerEvent* timerEvent = NULL;
+ TimerReceiver* client = NULL;
+ int clientReference = 0;
+
+ while(timerList.size())
{
- i = timerList.begin();
- delete *i;
- timerList.pop_front();
- --numTimers;
+ threadLock();
+ timerEvent = timerList.front();
+ client = timerEvent->client;
+ clientReference = timerEvent->clientReference;
+ threadUnlock();
+
+ cancelTimer(client, clientReference);
}
logger->log("Timers", Log::DEBUG, "Timers shutdown end");
return 1;
}
-int Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC)
+bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC)
{
if (!initted) return 0;
// Check that this timer is not already in the list
TimerList::iterator i;
- Timer* currentTimer = NULL;
+ TimerEvent* currentTimerEvent = NULL;
for(i = timerList.begin(); i != timerList.end(); i++)
{
- currentTimer = *i;
- if ((currentTimer->client == client) && (currentTimer->clientReference == clientReference))
+ currentTimerEvent = *i;
+
+ if ((currentTimerEvent->client == client) && (currentTimerEvent->clientReference == clientReference))
{
- // Overwrite an existing timer
- currentTimer->requestedTime.tv_sec = requestedTime;
- currentTimer->requestedTime.tv_nsec = requestedTimeNSEC;
- resetThreadFlag = true;
- threadSignalNoLock();
+ // Timer exists already, either waiting or running
+ // Update the clocks
+ currentTimerEvent->requestedTime.tv_sec = requestedTime;
+ currentTimerEvent->requestedTime.tv_nsec = requestedTimeNSEC;
- //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 2 (b)");
- threadUnlock();
- return 0;
+ if (currentTimerEvent->running)
+ {
+ // If this timerEvent is currently running, update the clocks and set the restart flag.
+ // Instead of being deleted in timerEventFinished it will be restarted
+ currentTimerEvent->restartAfterFinish = true;
+ // Don't need to resetThreadFlag because this timer isn't re-live yet
+ threadUnlock();
+ return true;
+ }
+ else
+ {
+ // A waiting timer has been edited
+ resetThreadFlag = true;
+ threadSignalNoLock();
+ threadUnlock();
+ return true;
+ }
}
}
- Timer* t = new Timer();
+ // Timer did not exist already
+
+ TimerEvent* t = new TimerEvent();
t->client = client;
t->clientReference = clientReference;
t->requestedTime.tv_sec = requestedTime;
logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
- return 1;
+ return true;
}
-int Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs)
+bool Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs)
{
struct timespec currentTime;
return setTimerT(client, clientReference, requestedTime, requestedTimeNSEC);
}
-int Timers::cancelTimer(TimerReceiver* client, int clientReference)
+bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
{
- if (!initted) return 0;
+ /* This method locks the timers mutex
+ Then one of three things can happen:
+ 1. The TimerEvent is found, running = false. This means it hasn't started yet.
+ Delete the timer normally, set resetFlag
+ 2. The TimerEvent is found, running = true. This means the timer is currently firing,
+ timercall on the client is being called.
+ In this case, this thread calling cancelTimer needs to unlock and wait for the
+ timercall thread to get back. (sleeps or signalling)
+ 3. The TimerEvent is not found. Client error or the thread returned to
+ the Timers module in between client calling cancelTimer and cancelTimer actually
+ running. Do nothing, return normally.
+
+ By making sure there is no waiting timerevent, and no running timerevent, this ensures
+ that the program cannot segfault because a timer fired on a just deleted object.
+
+ */
+
+ if (!initted) return false;
logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
- //logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 4");
+ while(1)
+ {
+ threadLock();
+
+ TimerList::iterator i;
+ TimerEvent* currentTimerEvent = NULL;
+ for(i = timerList.begin(); i != timerList.end(); i++)
+ {
+ currentTimerEvent = *i;
+ if ((currentTimerEvent->client == client) && (currentTimerEvent->clientReference == clientReference))
+ {
+ break;
+ }
+ }
+
+ if (i == timerList.end())
+ {
+ // Case 3, no timer found
+ threadUnlock();
+ return true;
+ }
+ else
+ {
+ // Timer found, Case 1 or 2
+
+ if (currentTimerEvent->running == false)
+ {
+ // Case 1. Just delete the timer and reset the thread.
+ timerList.erase(i);
+ delete currentTimerEvent;
+ logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
+ resetThreadFlag = true;
+ threadSignalNoLock();
+ threadUnlock();
+ return true;
+ }
+ else
+ {
+ // Case 2. For now, use polling with a 50ms delay.
+ // Don't delete a running timer.
+ // FIXME upgrade me to signalling
+
+ logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
+
+ threadUnlock();
+ MILLISLEEP(50);
+ }
+ }
+ } // end of the big while loop
+}
+
+void Timers::timerEventFinished(TimerEvent* timerEvent)
+{
+ // This function takes out the already timercall'd TimerEvent from the list
+ // Or resets it if restart flag is true
+
+
+ if (!initted) return;
threadLock();
- //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 4");
- TimerList::iterator i;
- Timer* currentTimer = NULL;
- for(i = timerList.begin(); i != timerList.end(); i++)
+
+ logger->log("Timers", Log::DEBUG, "timerEventFinished for %p", timerEvent->client);
+
+ for(TimerList::iterator i = timerList.begin(); i != timerList.end(); i++)
{
- currentTimer = *i;
- //logger->log("Timers", Log::DEBUG, "I: %p %i : %p %i", client, clientReference, currentTimer->client, currentTimer->clientReference);
- if ((currentTimer->client == client) && (currentTimer->clientReference == clientReference))
+ if (timerEvent != *i) continue;
+
+ if (timerEvent->restartAfterFinish)
+ {
+ logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p", timerEvent->client);
+
+ timerEvent->restartAfterFinish = false;
+ timerEvent->running = false;
+ resetThreadFlag = true;
+ threadSignalNoLock();
+ }
+ else
{
+ // The removal of a called and non-restart TimerEvent doesn't need the threadMethod to be reset
timerList.erase(i);
- logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
- break;
- // At this point currentTimer is not in the list but might still be nextTimer in the thread
+ logger->log("Timers", Log::DEBUG, "timerEventFinished for %p %i - remove done", timerEvent->client, timerEvent->clientReference);
+ delete timerEvent;
}
- }
- if (i == timerList.end())
- {
- // no timer found
- logger->log("Timers", Log::DEBUG, "No timer found in cancelTimer %p ref %i", client, clientReference);
- //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 4");
- threadUnlock();
- return 0;
- }
- resetThreadFlag = true;
- threadSignalNoLock();
- //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 4");
+ break;
+ }
+ // FIXME At this point, this should signal all threads waiting on cancelTimer
threadUnlock();
-
- return 1;
+ // Kill this thread, as it's the one started for the timer event
+ Thread_TYPE::threadSuicide();
}
void Timers::threadMethod()
{
struct timespec nextTime;
- Timer* nextTimer = NULL;
+ TimerEvent* nextTimer = NULL;
resetThreadFlag = true;
// locked here
nextTimer = NULL;
TimerList::iterator i;
- Timer* currentTimer = NULL;
+ TimerEvent* currentTimer = NULL;
for(i = timerList.begin(); i != timerList.end(); i++)
{
currentTimer = *i;
+ if (currentTimer->running) continue; // has already been timercall'd
+
if (!nextTimer)
{
nextTime.tv_sec = currentTimer->requestedTime.tv_sec;
// unlocks in the wait
}
+ // Mutex locked again here by exit of wait or timedwait above
+
// ok. we have been signalled or the time has run out
// This only gets signalled if it is to reset or die
Log::getInstance()->log("Timers", Log::DEBUG, "Timer firing for client %p ref %i", nextTimer->client, nextTimer->clientReference);
- // send this timer to the timer receiver, via the command message queue
- // so that the gui mutex is locked when it happens
-
- Message* m = new Message(); // Timer call, must be injected into master mutex (this is generated outside the mutex)
- m->from = this;
- m->to = nextTimer->client;
- m->message = Message::TIMER;
- m->parameter = nextTimer->clientReference;
-
- if (!Command::getInstance()->postMessageIfNotBusy(m))
- {
- // GUI mutex was locked
- // abort this timer delivery - it might be trying to be deleted!
- delete m;
-
- // now unlock the timers mutex for a fraction of a second
- // in case the gui thread is waiting on the timers mutex
- threadUnlock();
- //logger->log("Timers", Log::DEBUG, "un-LOCKED -TIMERS- MUTEX (3)");
- //printf("\n\n\n WOOOOO \n\n\n The anti deadlock code is working!!! \n\n\n");
- MILLISLEEP(20); // 10ms - too long? too short?
- //logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 7");
- threadLock();
- //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 7");
- resetThreadFlag = true;
- }
- else
- {
- // timer was delivered
- timerList.remove(nextTimer);
- delete nextTimer;
- nextTimer = NULL;
- resetThreadFlag = true;
- }
+ nextTimer->run(); // sets timerevent to running and starts it
+ resetThreadFlag = true; // find a new timer to wait on
}
}
-/*
-
-Avoiding deadlock using the timer class...
-
-Situation:
-timer condwait finishes
-timers is about to fire a timer
-timers locks timers-mutex
- user presses a button
- command locks gui-mutex
+// Class TimerEvent
-timers tries to get gui-mutex
-
- view receives button
- view wants to delete itself
- view tries to deletetimer
- goes into delete timer
- waits on timers mutex
-
-- deadlock
-
-
-Solution:
+TimerEvent::TimerEvent()
+{
+ running = false;
+ restartAfterFinish = false;
+ client = NULL;
+ clientReference = 0;
+ requestedTime.tv_sec = 0;
+ requestedTime.tv_nsec = 0;
+}
-timers tries to get gui mutex
-if mutex is locked already abort
-unlock timers mutex
-wait a fraction of time
-(allow other thread to lock timers mutex)
-lock timers mutex
-set reset flag to recalculate
-- if timer has been cancelled next timer will be calced
-- if timer has not been cancelled it will be called next
+void TimerEvent::threadMethod()
+{
+ Log::getInstance()->log("Timers", Log::DEBUG, "sending timer to %p with parameter %u", client, clientReference);
+ client->timercall(clientReference);
+ Timers::getInstance()->timerEventFinished(this); // does not return
+}
-*/
+void TimerEvent::run()
+{
+ running = true;
+ threadStart();
+}