Showing posts with label timer. Show all posts
Showing posts with label timer. Show all posts

Monday, April 05, 2010

Timeouts by analogy

Most networking-enabled applications have to deal with timeouts. Read or write operations may continue indefinitely, and programs need a way to determine when to tear down connections, resend requests, or take whatever other measures are necessary.

Asio includes the deadline_timer class for managing timeouts. This class aims to provide a minimal interface for scheduling events. Of course, minimalism gives little in the way of design guidance, so some users struggle in finding an elegant way to incorporate timers and timeouts into their programs.

From the minimalist perspective of Asio, there's no one true right way to do it. (Perhaps there's no better proof of that than my design preferences having changed over the years.) Yet that answer doesn't get programs written, so in this post I will try to present a simple mental model for managing timers.

Parking meters

High-traffic, commercial areas near where I live have limited on-street parking. The street parking that is available is metered. It's the usual drill:


  • Park your vehicle.

  • Feed some coins into the parking meter (or, as is more likely these days, swipe your credit card or send an SMS).

  • Go do whatever you came to do.

  • Make sure you return to your vehicle before the meter expires.

If you don't get back in time, you'd better hope your vehicle hasn't had a visit from the parking inspector. A visit means a ticket under the wipers and a nasty fine due.

Parking meters are a good analogy for reasoning about timeouts because it's easy to identify the two actors:


  • The driver of the vehicle.

  • The parking inspector.

The driver performs the following steps:


  1. Feeds the meter.

  2. Leaves the vehicle to run some errands.

  3. Returns to the vehicle.

  4. If no ticket has been issued, repeats from step 1.

  5. If a fine has been issued, goes home.

The parking inspector's job is simple:


  1. Checks whether the meter has expired.

  2. If the meter has expired, writes up a ticket.

  3. If the meter has not expired, notes how much time is remaining.

  4. Goes off for a walk until the remaining time has elapsed.

Using the analogy to inform program design

Hopefully you've already guessed how these actors map to networked applications:


  • The driver represents your protocol handling code.

  • The parking inspector corresponds to your timeout management logic.

Let's take a look at how this works in a very simple use case.

// The "driver" actor.
void session::handle_read(error_code ec, size_t length)
{
// On entering this function we have returned to the vehicle.

if (!ec)
{
// Phew, no ticket. Feed the meter.
my_timer.expires_from_now(seconds(5));

// Process incoming data.
// ...

// Run some more errands.
my_socket.async_read_some(buffer(my_buffer),
bind(&session::handle_read, this, _1, _2));
}
else
{
// We got a ticket. Go home.
}
}

// The "parking inspector" actor.
void session::handle_timeout(error_code ec)
{
// On entering this function we are checking the meter.

// Has the meter expired?
if (my_timer.expires_from_now() < seconds(0))
{
// Write up a ticket.
my_socket.close();
}
else
{
// Note remaining time and go for a walk.
my_timer.async_wait(
bind(&session::handle_timeout, this, _1));
}
}

It's important to remember that the driver may need to run multiple errands each time they leave the vehicle. In protocol terms, you might have a fixed-length header followed by a variable-length body. You only want to "feed the meter" once you have received a complete message:

// First part of the "driver" actor.
void session::handle_read_header(error_code ec)
{
// We're not back at the vehicle yet.

if (!ec)
{
// Process header.
// ...

// Run some more errands.
async_read(my_socket, buffer(my_body),
bind(&session::handle_read_body, this, _1));
}
}

// Second part of the "driver" actor.
void session::handle_read_body(error_code ec)
{
// On entering this function we have returned to the vehicle.

if (!ec)
{
// Phew, no ticket. Feed the meter.
my_timer.expires_from_now(seconds(5));

// Process complete message.
// ...

// Run some more errands.
async_read(my_socket, buffer(my_header),
bind(&session::handle_read_header, this, _1));
}
else
{
// We got a ticket. Go home.
}
}

There are many variations on this theme. For example, you may feed the meter between consecutive errands, varying the amount of money inserted (i.e. setting different length timeouts) depending on which errand comes next. In protocol terms, that might mean allowing up to 30 seconds between messages, but only a further 5 seconds is permitted once the message header has been received.

As I indicated earlier, there's no single right way to manage timeouts. In fact, there are many different facets to this problem that are probably worth exploring in their own right. However, I think that the approach shown here is probably suited to most applications and I would recommend it as a starting point when designing your timeout handling.

Wednesday, August 08, 2007

Time Travel

Many event-driven programs involve state changes that are triggered according to the system clock. You might be coding for:

  • A share market that opens at 10:00am and closes at 4:00pm.

  • An off-peak phone billing rate that starts after 7:00pm.

  • An interest calculation that is run on the last day of every month.

The asio::deadline_timer class lets you handle this easily. For example:
using namespace boost::posix_time;
typedef boost::date_time::c_local_adjustor<ptime> local_adj;

...

asio::deadline_timer timer(io_service);

ptime open_time(second_clock::local_time().date(), hours(10));
timer.expires_at(local_adj::local_to_utc(open_time));
timer.async_wait(open_market);
There's a catch: to test that your timer events work correctly, you have to run your program at the right time of day. It usually isn't practical to sit around all day (or, worse, all year) waiting for the timers to expire.

Time Traits


You may have noticed that the asio::deadline_timer class is actually a typedef:
typedef basic_deadline_timer<boost::posix_time::ptime>
deadline_timer;
where the basic_deadline_timer class template is declared as follows:
template <
typename Time,
typename TimeTraits
= asio::time_traits<Time>,
typename TimerService
= deadline_timer_service<Time, TimeTraits> >
class basic_deadline_timer;
In the context of our problem, the most interesting template parameter is the second one: TimeTraits. An implementation of TimeTraits lets us customise the treatment of the template's Time parameter, and consequently the behaviour of the timer itself.

A TimeTraits class must implement an interface that matches the following:
class TimeTraits
{
public:
// The type used to represent an absolute time, i.e. the same
// as the Time template parameter to basic_deadline_timer.
typedef ... time_type;

// The type used to represent the difference between two
// absolute times.
typedef ... duration_type;

// Returns the current time.
static time_type now();

// Returns a new absolute time resulting from adding the
// duration d to the absolute time t.
static time_type add(time_type t, duration_type d);

// Returns the duration resulting from subtracting t2 from t1.
static duration_type subtract(time_type t1, time_type t2);

// Returns whether t1 is to be treated as less than t2.
static bool less_than(time_type t1, time_type t2);

// Returns a "posix" duration corresponding to the duration d.
static boost::posix_time::time_duration to_posix_duration(
duration_type d);
};
As you can see from the declaration of the basic_deadline_timer class template, Asio provides a default TimeTraits implementation called asio::time_traits<>.

Offsetting Now


To test our timer events at any time of our choosing, we simply need to change the definition of "now" using a custom TimeTraits class.

Since we want to use the same time types as the regular deadline_timer class, we'll start by reusing the default traits implementation:
class offset_time_traits
: public asio::deadline_timer::traits_type
{
};
The value returned by the now() function will be offset from the system clock by a specified duration:
class offset_time_traits
: public asio::deadline_timer::traits_type
{

private:
static duration_type offset_;
};
which is simply added to the system clock:
class offset_time_traits
: public asio::deadline_timer::traits_type
{
public:
static time_type now()
{
return add(asio::deadline_timer::traits_type::now(), offset_);
}


private:
static duration_type offset_;
};
Of course, we will also need to provide a way to set the offset, which can be done by setting an initial value for "now":
class offset_time_traits
: public asio::deadline_timer::traits_type
{
public:
static time_type now()
{
return add(asio::deadline_timer::traits_type::now(), offset_);
}

static void set_now(time_type t)
{
offset_ =
subtract(t, asio::deadline_timer::traits_type::now());
}


private:
static duration_type offset_;
};

Creating a Timer


To use our custom traits type with the basic_deadline_timer template, we simply need to add the following typedef:
typedef asio::basic_deadline_timer<
boost::posix_time::ptime, offset_time_traits> offset_timer;
To see the offset timer in action, let's create a timer to fire precisely at the start of the coming new year. So we don't have to wait until then, we'll set "now" to be just ten seconds prior to midnight:
offset_time_traits::set_now(
boost::posix_time::from_iso_string("20071231T235950"));

offset_timer timer(io_service);
timer.expires_at(
boost::posix_time::from_iso_string("20080101T000000"));
timer.async_wait(handle_timeout);

io_service.run();
When the program is run, it will take just ten seconds to complete.

Jumping Through Time


One feature not supported by the above solution is the ability to change the definition of "now" after the timers have been started. However, if your timer events are spread across a long period of time, then this is likely to be something you would want.

Let's say that the next timer does not expire for several hours, but in an attempt to speed things up we call set_now() to move to just seconds before. The problem with the above traits class is that the existing asynchronous wait operation does not know about the change to "now", and so will continue to run for the remaining hours.

Fortunately, Asio provides a way around this: by customising the to_posix_duration() function in our traits class.

The to_posix_duration() function is normally used to convert from a user-defined duration type to a type that Asio knows about (namely boost::posix_time::time_duration). The key point here is that this converted duration value is used by Asio to determine how long to wait until the timer expires. Furthermore, it doesn't matter if this function returns a duration that is smaller (even substantially so) than the actual duration. The timer won't fire early, because Asio guarantees that it won't expire until the following condition holds true:
!TimeTraits::less_than(Time_Traits::now(), timer.expires_at())
So, by adding the to_posix_duration() function to our traits class:
class offset_time_traits
: public asio::deadline_timer::traits_type
{
public:
static time_type now()
{
return add(asio::deadline_timer::traits_type::now(), offset_);
}

static void set_now(time_type t)
{
offset_ =
subtract(t, asio::deadline_timer::traits_type::now());
}

static boost::posix_time::time_duration to_posix_duration(
duration_type d)
{
return d < boost::posix_time::seconds(5)
? d : boost::posix_time::seconds(5);
}


private:
static duration_type offset_;
};
we can ensure that Asio detects changes to the offset within seconds.