Everything you need to know about timers and periodic scheduling in the server framework

| 0 Comments
Often when you're writing a server or client application with The Server Framework you will find yourself needing to perform operations periodically or after a timeout. The Server Framework provides a light-weight timer queue that can be used to schedule timers and that uses minimal system resources so that there's no problem with having multiple timers per connection; even on systems with 10,000 connections.

The class used to manage per connection timers is the CThreadedCallbackTimerQueue class. This has been written about here where the design and testing of the class is discussed in depth.

You start by creating an instance of the CThreadedCallbackTimerQueue object, when you do this you get to select the implementation of the timer queue and the timer dispatch policy. The best options for this are to use BestForPlatformNoLock as this will automatically use the most appropriate timer queue implementation and it will dispatch timers in such a way that there is no risk of deadlock in your code when actioning timers and setting timers.

There are several timer queue implementations in the framework and future releases may bring more so it's always best to operate on the timer queue via its interface, IQueueTimers, doing this will mean that it's easy for you to swap out this implementation for a new one in a later release if it seems appropriate to do so.

Each timer that you wish to set is identified by a handle and these are created by calling CreateTimer(). This returns you a handle which can then be used in calls to SetTimer() and CancelTimer(). When you're finished with a timer for good, i.e. when you don't need to reset it again in the future, you should call DestroyTimer() to release the resources used.

To create a timer per connection you might do something like this. Note that we use per connection user data to store each connection's timer handle.

void CSocketServer::OnConnectionEstablished(
   IStreamSocket &socket,
   const IAddress & /*address*/)
{
   Output(_T("OnConnectionEstablished"));
 
   IQueueTimers::Handle timerHandle = m_timerQueue.CreateTimer();
 
   socket.SetUserData(m_userDataIndex, timerHandle);
 
   if (socket.TryWrite(
      m_welcomeMessage.c_str(),
      GetStringLengthAsDWORD(m_welcomeMessage)))
   {
      socket.TryRead();
   }
 
   SetTimer(socket);
}
 
void CSocketServer::OnSocketReleased(
   IIndexedOpaqueUserData &userData)
{
   IQueueTimers::Handle timerHandle = userData.GetUserData(m_userDataIndex);
 
   m_timerQueue.DestroyTimer(timerHandle);
}

To be able to set a timer you need two things, a timer handle (which we've just learnt how to obtain) and a class that implements the IQueueTimers::Timer callback interface. This interface is very simple:

class IQueueTimers::Timer
{
   public :
       
      /// User data that can be passed to Timer via the OnTimer() call when 
      /// the timeout expires.
       
      typedef IQueueTimers::UserData UserData;
 
      /// Called after the timer expires.
       
      virtual void OnTimer(
         UserData userData) = 0;
 
   protected :
 
      /// We never delete instances of this interface; you must manage the 
      /// lifetime of the class that implements it.
 
      ~Timer() {}
};

Derived classes simply implement OnTimer() and this is called when a timer expires. The timer callback is passed 'user data' which can be used to communicate between the place where you set the timer and the place where the timer executes.

In a socket server scenario you might use the connection's socket as the user data. So to set a timer for a connection you might make a call like this:

void CSocketServer::SetTimer(
   IStreamSocket &socket) 
{
   IQueueTimers::Handle timerHandle = socket.GetUserData(m_userDataIndex);
 
   socket.AddRef();  // Take a reference to hold until the timer is cancelled or expires...
 
   static const Milliseconds timeout = 2000;  // 2 seconds
 
   m_timerQueue.SetTimer(timerHandle, *this, timeout, reinterpret_cast(&socket));
}
Note that it is VERY important that you increment the socket's reference count before you pass the socket into the timer queue as user data. The reason for this is that the connection may terminate before the timer expires or is cancelled and if that happens and you haven't taken a reference to the socket then the socket may well have been destroyed (or reused!) before it is used in the timer handler. The timer handler might then look something like this:
void CSocketServer::OnTimer(
   UserData userData)
{
   Output(_T("OnTimer"));
 
   IStreamSocket &socket = *reinterpret_cast(userData);
 
   static const string message("Periodic per connection message\r\n");
 
   socket.Write(message.c_str(), GetStringLengthAsDWORD(message));
 
   SetTimer(socket);
 
   socket.Release(); // Release the reference that we took when we set this
                      // timer.
}
Note that we release the reference that we took when we set the timer. This is VERY important or you would be leaking socket references which would cause a memory leak within the server and might prevent sockets from closing completely or at the correct time. This particular example sets the timer again once it expires, you may or may not need to do this. If you needed to cancel the timer for a reason, perhaps because the client had disconnected, then you'd do something like this:
void CSocketServer::OnConnectionClientClose(
    IStreamSocket &socket)
{
 
   if (m_timerQueue.CancelTimer(timerHandle))
   {
      socket.Release();  // The timer was active when it was cancelled, release the reference 
                         // that we took when we called SetTimer().
   }
}
More information

Leave a comment