Introduction to the UCMA API - Part 16 - TimerWheel
Let’s say you are not sure a particular endpoint exists at a certain time, so you would like to continually retry at specific intervals or you need to refresh a connection at a certain interval. To help with this situation, the UCMA API provides the TimerWheel class.
TimerWheel differs from standard timers in how it determines particular items are ready to fire. Individual events are divided into sectors, where each sector encompasses a distinct period of time. Rather than checking which events are ready at a particular moment to fire, it checks only whether each event belongs to the current time interval, or sector. This means that TimerWheel only needs to check once per time interval. You can set this interval in the constructor for TimerWheel.
We are going to add the capability for our client to continually retry our server until it is available. Our work today will be entirely within the client – in order to simulate this situation simply start running the client before the server. First, let’s add a new field for our timer wheel.
private TimerWheel _timerWheel;
Now let’s handle the case where our INVITE fails because the server is not there – which corresponds to error response code 480. Within ParticipateCallback, modify the code under the catch for FailureResponseException as in the following example.
if (e.ResponseData != null)
{
Error("The Notification could not be received by {0}; Error Code={1} Error Text={2}.",
Settings.OurServerName,
e.ResponseData.ResponseCode,
e.ResponseData.ResponseText);
// Retry if the server is not there
if (e.ResponseData.ResponseCode == 480)
{
RecordProgress("Retrying the INVITE");
if (null == _timerWheel)
{
_timerWheel = new TimerWheel();
}
TimerItem item = new TimerItem(_timerWheel, new TimeSpan(0, 0, 15));
item.Expired += new EventHandler(item_Expired);
item.Start();
}
}
else
{
Error("The Notification could not be received by {0}.", Settings.OurServerName);
}
If we failed to connect to the server because the server is not available, we create a new TimerItem and set it to fifteen seconds. By calling Start() on the TimerItem, we start its timer. The following is our expired event handler
void item_Expired(object sender, EventArgs e)
{
RecordProgress("Ready to retry the INVITE");
lock (_syncObject)
{
if (false == _shuttingDown)
{
_session.BeginParticipate(new AsyncCallback(ParticipateCallback), _session);
}
}
}
Basically we take the easy way out here and just call BeginParticipate again, which will call our callback method that will retry again if we fail due to a 480 response. We need to also check if we are shutting down, in which case we should not call BeginParticipate again. We could have also placed the original BeginParticipate call in the expired handler and then made the original call through the TimerItem call. If we don’t want to wait fifteen seconds for the first attempt, we can modify the time span after the first attempt. Then, our failure code changes simply to calling Reset() on the TimerItem. I will let you decide which you prefer.
We now have a nifty client that will continually retry the server if the server is not available and will connect to it once it comes online. This is very useful for creating a robust product and there are a number of scenarios where customers prefer an application to retry if a connection is lost.