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.
asio::deadline_timer
class lets you handle this easily. For example:
using namespace boost::posix_time;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.
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);
Time Traits
You may have noticed that the
asio::deadline_timer
class is actually a typedef:
typedef basic_deadline_timer<boost::posix_time::ptime>where the basic_deadline_timer class template is declared as follows:
deadline_timer;
template <In the context of our problem, the most interesting template parameter is the second one:
typename Time,
typename TimeTraits
= asio::time_traits<Time>,
typename TimerService
= deadline_timer_service<Time, TimeTraits> >
class basic_deadline_timer;
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 TimeTraitsAs you can see from the declaration of the
{
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);
};
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_traitsThe value returned by the
: public asio::deadline_timer::traits_type
{
};
now()
function will be offset from the system clock by a specified duration:
class offset_time_traitswhich is simply added to the system clock:
: public asio::deadline_timer::traits_type
{
private:
static duration_type offset_;
};
class offset_time_traitsOf course, we will also need to provide a way to set the offset, which can be done by setting an initial value for "now":
: 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_;
};
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<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:
boost::posix_time::ptime, offset_time_traits> offset_timer;
offset_time_traits::set_now(When the program is run, it will take just ten seconds to complete.
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();
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_traitswe can ensure that Asio detects changes to the offset within seconds.
: 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_;
};
Thursday, April 26, 2007
New home heating solution
For quite some time I have wanted to take a really good look at improving Boost.Asio's scalability across multiple processors. Unfortunately, getting unlimited access to this sort of hardware has been somewhat problematic. What I really needed was a decent multiprocessor box at home :)
The arrival of Intel's Clovertown quad core CPUs gave me the opportunity. The primary goal was to maximise parallelism while minimising the cost, and the lowest spec quad cores were cheap enough for me to justify spending the money. So, after months of thinking (and weeks of waiting for parts), last week I finally completed my home-built server.
Here are the headline specs:
- Two Intel Xeon E5310 quad core processors (1.6 GHz)
- Tyan Tempest i5000XL motherboard
- 2GB DDR2-667 fully buffered ECC DIMMs
- OCZ GameXStream 700W power supply (OK, OK, it's a little overpowered, but it was in stock!)
- Other stuff like case, hard disk, DVD drive, video card and wireless LAN card
- Lots of fans to provide soothing ambient noise
Monday, January 15, 2007
Unbuffered socket iostreams
Boost.Asio includes an iostreams-based interface to TCP sockets, ip::tcp::iostream
, for simple use cases. However, like the file iostreams provided by the standard library, ip::tcp::iostream
buffers input and output data. This can lead to problems if you forget to explicitly flush the stream. For example, consider the following code to perform an HTTP request:
ip::tcp::iostream stream("www.boost.org", "http");The code will be stuck on the
stream << "GET / HTTP/1.0\r\n"
<< "Host: www.boost.org\r\n"
<< "\r\n";
std::string response_line;
std::getline(stream, response_line);
...
getline()
call waiting for the response, because the request will still be sitting
stream
's output buffer. The correct code looks like this:
ip::tcp::iostream stream("www.boost.org", "http");The
stream << "GET / HTTP/1.0\r\n"
<< "Host: www.boost.org\r\n"
<< "\r\n"
<< std::flush;
std::flush
will cause the stream to send the entire contents of its output buffer at that point.
Boost.Asio now supports an alternative solution: turn off the stream's output buffering. This is accomplished as follows:
ip::tcp::iostream stream("www.boost.org", "http");Now you can send and receive to your heart's content, without having to worry about whether your message is stuck in the output buffer, but be warned: an unbuffered stream is a lot less efficient in terms of system calls. Don't use this feature if you care about performance.
stream.rdbuf()->pubsetbuf(0, 0);
stream << "GET / HTTP/1.0\r\n"
<< "Host: www.boost.org\r\n"
<< "\r\n";