Background
I am trying to stop periodic tasks when user interrupts process with SIGINT. I have based my periodic task scheduler on this answer.
To accomplish this I tried passing PeriodicScheduler instance pointer to my InterruptHandler and calling ps->stop().
Periodic Task Scheduler header:
#ifndef __PERIODICSCHEDULER_H__
#define __PERIODICSCHEDULER_H__
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
namespace APP{
class PeriodicTask : boost::noncopyable {
public:
typedef std::function<void()> handler_fn;
PeriodicTask(boost::asio::io_service& ioService
, std::string const& name
, int interval
, handler_fn task);
void execute(boost::system::error_code const& e);
void start();
private:
void start_wait();
boost::asio::io_service& ioService;
boost::asio::deadline_timer timer;
handler_fn task;
std::string name;
int interval;
}; /* class PeriodicTask */
class PeriodicScheduler : boost::noncopyable
{
public:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
void run();
void stop();
void addTask(std::string const& name
, PeriodicTask::handler_fn const& task
, int interval);
private:
boost::asio::io_service io_service;
std::vector<std::unique_ptr<PeriodicTask>> tasks;
}; /* PeriodicScheduler */
} /* namespace Resto */
#endif /* __PERIODICSCHEDULER_H__ */
Periodic Task Scheduler source:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include "periodicScheduler.h"
APP::PeriodicTask::PeriodicTask(boost::asio::io_service& ioService
, std::string const& name
, int interval
, handler_fn task)
: ioService(ioService)
, interval(interval)
, task(task)
, name(name)
, timer(ioService){
// Schedule start to be ran by the io_service
ioService.post(boost::bind(&PeriodicTask::start, this));
}
void APP::PeriodicTask::execute(boost::system::error_code const& e){
if (e != boost::asio::error::operation_aborted) {
task();
timer.expires_at(timer.expires_at() + boost::posix_time::seconds(interval));
start_wait();
}
}
void APP::PeriodicTask::start(){
// Uncomment if you want to call the handler on startup (i.e. at time 0)
// task();
timer.expires_from_now(boost::posix_time::seconds(interval));
start_wait();
}
void APP::PeriodicTask::start_wait(){
timer.async_wait(boost::bind(&PeriodicTask::execute
, this
, boost::asio::placeholders::error));
}
void APP::PeriodicScheduler::run(){
io_service.run();
}
void APP::PeriodicScheduler::stop(){
io_service.stop();
}
void APP::PeriodicScheduler::addTask(std::string const& name
, PeriodicTask::handler_fn const& task
, int interval){
tasks.push_back(make_unique<PeriodicTask>(std::ref(io_service)
, name, interval, task));
}
The following is InterruptHandler:
#include <csignal>
#include <condition_variable>
#include <mutex>
#include <iostream>
#include <boost/asio.hpp>
#include "periodicScheduler.h"
static std::condition_variable _condition;
static std::mutex _mutex;
namespace APP {
class InterruptHandler {
public:
static void hookSIGINT() {
signal(SIGINT, handleUserInterrupt);
}
static void handleUserInterrupt(int signal){
if (signal == SIGINT) {
std::cout << "SIGINT trapped ..." << '\n';
_condition.notify_one();
}
}
static void waitForUserInterrupt(APP::PeriodicScheduler *ps) {
std::unique_lock<std::mutex> lock { _mutex };
_condition.wait(lock);
ps->stop();
std::cout << "user has signaled to interrup program..." << '\n';
lock.unlock();
}
};
}
My main()
int main(int ac, const char * av[]) {
InterruptHandler::hookSIGINT();
APP::PeriodicScheduler ps;
APP::WorkerClass wc;
// WorkerClass::someTask and WorkerClass:someOtherTask are dummy functions only with sleep(5); inside them
ps.addTask("someTask", boost::bind( &APP::WorkerClass::someTask, wc ), 60);
ps.addTask("someOtherTask", boost::bind( &APP::WorkerClass::someOtherTask, wc ), 60);
ps.run();
InterruptHandler::waitForUserInterrupt(&ps);
return 0;
}
Issue
After running my app in terminal I pressed CTRL+C to trigger interrupt. I can see SIGINT trapped ... in the terminal but, application continues to run.
If I comment out ps.run(); statement, upon pressing CTRL+C I can see SIGINT trapped ... followed by user has signaled to interrup program... and application exits.
Questions
Is my approach correct? How can I effectively stop scheduled tasks and exit application?
Did I miss something?
By all means, I'd suggest using signal_set https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/signal_set.html
Here are some examples: https://stackoverflow.com/search?q=user%3A85371+signal_set
The best part is that is insulates you from some platform specific things and removes common pitfalls related to writing async-safe handlers.
Related
I would like to handle SIGINT signal from the kernel in order to call a function that grecefully shutdown my process.
Here's a working example, a thread is waiting the signal and then call the function handle_stop:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
void handle_stop(const boost::system::error_code& error, int signal_number) {
std::cout<<"Executing Safe Shutdown"<<std::cout;
exit(0);
}
int main() {
std::cout<<"Init"<<std::cout;
boost::asio::io_service signalService;
boost::asio::signal_set signals(signalService, SIGINT, SIGTERM, SIGQUIT);
signals.async_wait(handle_stop);
boost::thread signalThread(boost::bind(&boost::asio::io_service::run, &signalService));
std::cout<<"Starting programm"<<std::cout;
while (true) {
std::cout<<"Waiting ctl-c"<<std::cout;
sleep(1);
}
}
My goal is pack inside a class the thread and the function to call for shut the process down.
Here's an non working attempt, the process shutdown immediately without wait for the signal.
What is wrong?
#include <atomic>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
class Shutdown {
public:
Shutdown():is_signal_received_ (false) {
//signals_::remove();
std::cout<<"constructor"<<std::endl;
}
~Shutdown() {
}
void init() {
std::cout<<"Init "<<std::endl;
boost::asio::signal_set signals(signalService_, SIGINT, SIGTERM, SIGQUIT);
signals.async_wait(boost::bind(boost::mem_fn(&Shutdown::handleStop), this, _1, _2));
boost::thread signalThread(boost::bind(&boost::asio::io_service::run, &signalService_));
std::cout<<"Init Completed"<<std::endl;
}
bool isSignalReceived() const {
return is_signal_received_;
}
private:
std::atomic<bool> is_signal_received_;
boost::asio::io_service signalService_;
void handleStop(const boost::system::error_code& error, int signal_number) {
is_signal_received_ = true;
myHandleStop(error, signal_number);
}
virtual void myHandleStop(const boost::system::error_code& error, int signal_number) {
}
};
class MyShutdown: public Shutdown {
private:
void myHandleStop(const boost::system::error_code& error, int signal_number) {
std::cout<<"Executing Safe Shutdown"<<std::cout;
exit(0);
}
};
int main() {
MyShutdown safeShutdown;
safeShutdown.init();
while (true) {
std::cout<<"Waiting ctl-c"<<std::cout;
sleep(1);
}
}
Here's the command for compiling:
g++ -o main main.cpp -lpthread -l boost_thread -l boost_system --std=c++11
Your issue is that your signal_set goes out of scope and is destroyed at the end of Shutdown::init. When that happens, the async_wait is cancelled. signalThread also goes out of scope at the same time without being either detached or joined. Those both need to be class members so that they stay alive until a signal can be handled:
#include <atomic>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
class Shutdown {
public:
Shutdown()
: is_signal_received_ (false),
signalService_(),
signals_(signalService_, SIGINT, SIGTERM, SIGQUIT)
{
std::cout<<"constructor"<<std::endl;
}
~Shutdown()
{
signals_.cancel();
signalService_.stop();
signalThread_.join();
}
void init() {
std::cout<<"Init "<<std::endl;
signals_.async_wait(boost::bind(&Shutdown::handleStop, this, _1, _2));
signalThread_ = boost::thread(boost::bind(&boost::asio::io_service::run, &signalService_));
std::cout<<"Init Completed"<<std::endl;
}
bool isSignalReceived() const {
return is_signal_received_;
}
private:
std::atomic<bool> is_signal_received_;
boost::asio::io_service signalService_;
boost::thread signalThread_;
boost::asio::signal_set signals_;
void handleStop(const boost::system::error_code& error, int signal_number) {
is_signal_received_ = true;
myHandleStop(error, signal_number);
}
virtual void myHandleStop(const boost::system::error_code& error, int signal_number) {
}
};
class MyShutdown: public Shutdown {
private:
void myHandleStop(const boost::system::error_code& error, int signal_number) {
std::cout<<"Executing Safe Shutdown"<<std::endl;
exit(0);
}
};
int main() {
MyShutdown safeShutdown;
safeShutdown.init();
while (true) {
std::cout<<"Waiting ctl-c"<<std::endl;
sleep(10);
}
}
I've also added calls to shut down the io_service and signal_set and to wait for the thread to terminate in ~Shutdown.
I'm trying to save the result of bind to std:function, then pass it as parameter to another function, and store it as data member. Then I use asio async_wait, but when i return from the wait, and try to operate the function i saved i get segmentation fault. any Idea why?
#include <memory>
#include <iostream>
#include <asio/io_service.hpp>
#include <functional>
#include <asio/deadline_timer.hpp>
using namespace std;
typedef std::function<void (const std::error_code& error)> TM_callback;
class Timer {
public:
Timer(asio::io_service& io_service) :_timer(io_service) {}
void start(TM_callback cb) {
_cb = cb;
_timer.expires_from_now(boost::posix_time::milliseconds(1000));
TM_callback timeoutFunc = std::bind(&Timer::onTimeout, this, std::placeholders::_1);
_timer.async_wait(timeoutFunc);
}
private:
void onTimeout(const std::error_code& error) {
(_cb)(error); // <-- here i get segmentation fault
}
TM_callback _cb;
asio::deadline_timer _timer;
};
class COL {
public:
COL(asio::io_service& io_service): _inTimer(io_service){}
void startInTimer() {
TM_callback cb = std::bind(&COL::onInTimeout, this, std::placeholders::_1);
_inTimer.start(cb);
}
private:
void onInTimeout(const std::error_code& error) {cout<<error.message();}
Timer _inTimer;
};
int main()
{
asio::io_service io_service;
COL col(io_service);
col.startInTimer();
return 0;
}
Ok, the most likely problem is in the code you don't show. As you can see #m.s. didn't "imagine" your problem. He forgot the io_service::run() too:
int main() {
asio::io_service io_service;
COL col(io_service);
col.startInTimer();
io_service.run();
}
Still no problem. Live On Coliru
The problem starts when inTimer is not guaranteed to live until the completion handler is executed:
int main() {
asio::io_service io_service;
{
COL col(io_service);
col.startInTimer();
}
io_service.run();
}
Now you have Undefined Behaviour: Live On Coliru
Solution
The easiest solution is to make the COL (what is that?) object live long enough. The more structural/idiomatic way would to let the bind keep the Timer object alive, e.g. using a shared_ptr:
Live On Coliru
#include <iostream>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
using namespace std;
typedef std::function<void(const boost::system::error_code &error)> TM_callback;
namespace asio = boost::asio;
class Timer : public boost::enable_shared_from_this<Timer> {
public:
Timer(asio::io_service &io_service) : _timer(io_service) {}
void start(TM_callback cb) {
_cb = cb;
_timer.expires_from_now(boost::posix_time::milliseconds(1000));
TM_callback timeoutFunc = boost::bind(&Timer::onTimeout, shared_from_this(), boost::asio::placeholders::error);
_timer.async_wait(timeoutFunc);
}
private:
void onTimeout(const boost::system::error_code &error) {
(_cb)(error);
}
TM_callback _cb;
asio::deadline_timer _timer;
};
class COL : public boost::enable_shared_from_this<COL> {
public:
COL(asio::io_service &io_service) : _svc(io_service) {}
void startInTimer() {
TM_callback cb = boost::bind(&COL::onInTimeout, shared_from_this(), boost::asio::placeholders::error);
boost::shared_ptr<Timer> _inTimer = boost::make_shared<Timer>(_svc);
_inTimer->start(cb);
}
private:
void onInTimeout(const boost::system::error_code &error) { cout << error.message(); }
asio::io_service& _svc;
};
int main() {
asio::io_service io_service;
{
boost::make_shared<COL>(io_service)->startInTimer();
}
io_service.run();
}
Note that this subtly also fixes the problem that more than one timer couldn't be in flight at a give time (scheduling a new timer would cancel the pending one).
I have the following code:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time.hpp>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <map>
#include <utility>
namespace koicxx {
template <typename T>
class temp_storage : private boost::noncopyable
{
typedef boost::shared_ptr<boost::asio::deadline_timer> shared_timer_t;
typedef std::map<T, shared_timer_t> timer_map_t;
typedef std::pair<T, shared_timer_t> timer_pair_t;
typedef boost::function<void(const T&, const boost::system::error_code&)> callback_t;
public:
temp_storage(boost::asio::io_service& io_service) :
_io_service(io_service) {}
bool add(const T& element, const boost::asio::deadline_timer::duration_type& timeout, callback_t callback = callback_t())
{
boost::lock_guard<boost::mutex> lock(_sync);
const std::pair<timer_map_t::iterator, bool>& res =
_internal_storage.insert(
timer_pair_t(
element
, shared_timer_t(new boost::asio::deadline_timer(_io_service, timeout))
));
if (!res.second)
{
return false;
}
const timer_map_t::iterator& itr = res.first;
if (callback)
{
itr->second->async_wait(
boost::bind(
callback
, itr->first
, boost::asio::placeholders::error
));
}
itr->second->async_wait(
boost::bind(
&temp_storage::remove_callback
, this
, itr->first
, boost::asio::placeholders::error
));
return true;
}
bool remove(const T& element)
{
boost::lock_guard<boost::mutex> lock(_sync);
const timer_map_t::iterator& itr = _internal_storage.find(element);
if (itr == _internal_storage.end())
{
return false;
}
itr->second->cancel();
_internal_storage.erase(itr);
return true;
}
bool contains(const T& element)
{
boost::lock_guard<boost::mutex> lock(_sync);
return _internal_storage.find(element) != _internal_storage.end();
}
void clear()
{
boost::lock_guard<boost::mutex> lock(_sync);
for (timer_map_t::value_type& i : _internal_storage)
{
i.second->cancel();
}
_internal_storage.clear();
}
private:
void remove_callback(const T& element, const boost::system::error_code& e)
{
if (e == boost::asio::error::operation_aborted)
{
return;
}
remove(element);
}
boost::asio::io_service& _io_service;
timer_map_t _internal_storage;
boost::mutex _sync;
};
} // namespace koicxx
int main()
{
boost::asio::io_service io_service;
koicxx::temp_storage<int> some_storage(io_service);
some_storage.add(0, boost::posix_time::seconds(2));
some_storage.add(1, boost::posix_time::seconds(3));
some_storage.add(2, boost::posix_time::seconds(5));
while (true)
{
if (some_storage.contains(0))
{
std::cout << 0 << ' ';
}
if (some_storage.contains(1))
{
std::cout << 1 << ' ';
}
if (some_storage.contains(2))
{
std::cout << 2 << ' ';
}
std::cout << '\n';
boost::this_thread::sleep_for(boost::chrono::seconds(1));
}
}
When I need to run io_service and why? Could I make io_service member of the class? Is there smth wrong with this code?
Thanks in advance.
You never see your timers expire.
When calling async_wait what you are telling Asio is this: When the timer expires, I want you to schedule this callback for execution. Note that 'schedule' here does not mean 'execute immediately', but rather 'insert it into a queue of stuff that is ready for execution'. Said queue is part of io_service's internals. Calling run on io_service will block until all pending work has been scheduled and executed.
The problem here is that run waits for both callbacks that have been scheduled (ie. those that are already ready for execution) and those that are still waiting to be scheduled (ie. those where you have called async_wait but where the timer has not expired yet). So just calling run from the main thread will simply block until all three of your timers have expired, which is probably not what you want.
You have two options now: You can either open a second thread to call run. This would work, but you would end up with two threads mostly doing nothing (the main thread, which is mainly sleeping in the loop, and the worker thread mainly sleeping on the run call).
A more lightweight approach is to call poll instead from the loop. Unlike run, poll only exeuctes callbacks that have been scheduled for execution already, but not those that are still waiting. If no such callbacks are available, poll returns immediately instead of blocking:
template <typename T>
class temp_storage : private boost::noncopyable
{
public:
void do_poll() {
io_service_.poll();
}
[...]
};
int main()
{
[...]
while (true)
{
[...]
some_storage.do_poll();
boost::this_thread::sleep_for(boost::chrono::seconds(1));
}
}
Boris' article shows us how to create extension of boost::asio. I try to add signal_set and async_wait on registered signals. Then the program hangs until a second SIGINT is triggered. Though, I would like to finish it properly within one signal only.
Here is my code. I test it with gcc-4.6.3 and boost-1.52.0 on Ubuntu.
To compile -
gcc -I/boost_inc -L/boot_lib main.cpp -lpthread -lboost_system -lboost_thread
#include <boost/asio.hpp>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <cstddef>
template <typename Service>
class basic_timer
: public boost::asio::basic_io_object<Service>
{
public:
explicit basic_timer(boost::asio::io_service &io_service)
: boost::asio::basic_io_object<Service>(io_service)
{}
void wait(std::size_t seconds)
{ return this->service.wait(this->implementation, seconds); }
template <typename Handler>
void async_wait(std::size_t seconds, Handler handler)
{ this->service.async_wait(this->implementation, seconds, handler); }
};
class timer_impl;
template <typename TimerImplementation = timer_impl>
class basic_timer_service
: public boost::asio::io_service::service
{
public:
static boost::asio::io_service::id id;
explicit basic_timer_service(boost::asio::io_service &io_service)
: boost::asio::io_service::service(io_service),
async_work_(new boost::asio::io_service::work(async_io_service_)),
async_thread_(
boost::bind(&boost::asio::io_service::run, &async_io_service_))
{}
~basic_timer_service()
{
async_work_.reset();
async_io_service_.stop();
async_thread_.join(); // program is blocked here until the second
// signal is triggerd
async_io_service_.reset();
}
typedef boost::shared_ptr<TimerImplementation> implementation_type;
void construct(implementation_type &impl)
{
impl.reset(new TimerImplementation());
}
void destroy(implementation_type &impl)
{
impl->destroy();
impl.reset();
}
void wait(implementation_type &impl, std::size_t seconds)
{
boost::system::error_code ec;
impl->wait(seconds, ec);
boost::asio::detail::throw_error(ec);
}
template <typename Handler>
class wait_operation
{
public:
wait_operation(
implementation_type &impl,
boost::asio::io_service &io_service,
std::size_t seconds, Handler handler)
: impl_(impl),
io_service_(io_service),
work_(io_service),
seconds_(seconds),
handler_(handler)
{}
void operator()() const
{
implementation_type impl = impl_.lock();
if (!io_service_.stopped() && impl)
{
boost::system::error_code ec;
impl->wait(seconds_, ec);
this->io_service_.post(
boost::asio::detail::bind_handler(handler_, ec));
}
else
{
this->io_service_.post(
boost::asio::detail::bind_handler(
handler_, boost::asio::error::operation_aborted));
}
}
private:
boost::weak_ptr<TimerImplementation> impl_;
boost::asio::io_service &io_service_;
boost::asio::io_service::work work_;
std::size_t seconds_;
Handler handler_;
};
template <typename Handler>
void async_wait(
implementation_type &impl,
std::size_t seconds, Handler handler)
{
this->async_io_service_.post(
wait_operation<Handler>(
impl, this->get_io_service(), seconds, handler));
}
private:
void shutdown_service()
{}
boost::asio::io_service async_io_service_;
boost::scoped_ptr<boost::asio::io_service::work> async_work_;
boost::thread async_thread_;
};
class timer_impl
{
public:
timer_impl()
{}
~timer_impl()
{}
void destroy()
{}
void wait(std::size_t seconds, boost::system::error_code &ec)
{
sleep(seconds);
ec = boost::system::error_code();
}
};
typedef basic_timer<basic_timer_service<> > timer;
template <typename TimerImplementation>
boost::asio::io_service::id basic_timer_service<TimerImplementation>::id;
void wait_handler(const boost::system::error_code &ec)
{
std::cout << "5 s." << std::endl;
}
int main()
{
{
boost::asio::io_service io_service;
boost::asio::signal_set signals(io_service);
timer t(io_service);
signals.add(SIGINT);
signals.async_wait(
boost::bind(&boost::asio::io_service::stop, &io_service));
t.async_wait(2, wait_handler);
std:: cout << "async called\n" ;
io_service.run();
}
{ // this block will not be executed
boost::asio::io_service io_service;
timer t(io_service);
t.async_wait(2, wait_handler);
std:: cout << "async called\n" ;
io_service.run();
}
return 0;
}
After tried an example offered by the author of asio, I confronted the same behavior. Consequently, I dig into the library source and found that the source use io_service_impl's interfaces rather than ones of io_service. Furthermore, an operation functor posted to the io_service_impl is different from ones invoked by the io_service. Altogether, I decided to rewrite the timer example according to the internal interfaces of asio.
I hereby present the rewritten timer example.
#include <boost/asio.hpp>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <cstddef>
#define get_service_impl(X) \
ba::use_service<bad::io_service_impl>(X)
namespace ba = boost::asio;
namespace bad = boost::asio::detail;
// Nothing changed
template <typename Service>
class basic_timer
: public boost::asio::basic_io_object<Service>
{
public:
explicit basic_timer(boost::asio::io_service &io_service)
: boost::asio::basic_io_object<Service>(io_service)
{}
void wait(std::size_t seconds)
{ return this->service.wait(this->implementation, seconds); }
template <typename Handler>
void async_wait(std::size_t seconds, Handler handler)
{ this->service.async_wait(this->implementation, seconds, handler); }
};
// Nothing changed
class timer_impl
{
public:
void wait(std::size_t seconds, boost::system::error_code &ec)
{
sleep(seconds);
ec = boost::system::error_code();
}
};
// ----- Change a lot! --------
class basic_timer_service
: public boost::asio::io_service::service
{
public:
typedef boost::asio::detail::socket_ops::shared_cancel_token_type
implementation_type;
static boost::asio::io_service::id id;
explicit basic_timer_service(boost::asio::io_service &io_service)
: boost::asio::io_service::service(io_service),
io_service_impl_(get_service_impl(io_service)),
work_io_service_( new boost::asio::io_service ),
work_io_service_impl_(get_service_impl(*work_io_service_)),
work_(new ba::io_service::work(*work_io_service_)),
work_thread_() // do not create thread here
{}
~basic_timer_service()
{ shutdown_service(); }
void construct(implementation_type &impl)
{ impl.reset(new timer_impl()); }
void cancel(implementation_type &impl)
{
impl.reset((void*)0, boost::asio::detail::socket_ops::noop_deleter());
}
void destroy(implementation_type &impl)
{ impl.reset(); }
void shutdown_service()
{
work_.reset();
if(work_io_service_.get()){
work_io_service_->stop();
if (work_thread_.get()){
work_thread_->join();
work_thread_.reset();
}
}
work_io_service_.reset();
}
void wait(implementation_type &impl, std::size_t seconds)
{
boost::system::error_code ec;
// XXX I not sure this is safe
timer_impl *impl_ptr = static_cast<timer_impl*>(impl.get());
impl_ptr->wait(seconds, ec);
boost::asio::detail::throw_error(ec);
}
template <typename Handler>
class wait_operation
: public boost::asio::detail::operation
{
public:
BOOST_ASIO_DEFINE_HANDLER_PTR(wait_operation);
// namespace ba = boost::asio
// namespace bad = boost::asio::detail
wait_operation(
bad::socket_ops::weak_cancel_token_type cancel_token,
std::size_t seconds,
bad::io_service_impl& ios,
Handler handler)
: bad::operation(&wait_operation::do_complete),
cancel_token_(cancel_token),
seconds_(seconds),
io_service_impl_(ios),
handler_(handler)
{}
static void do_complete(
bad::io_service_impl *owner,
bad::operation *base,
boost::system::error_code const & /* ec */ ,
std::size_t /* byte_transferred */ )
{
wait_operation *o(static_cast<wait_operation*>(base));
ptr p = { boost::addressof(o->handler_), o, o};
// Distinguish between main io_service and private io_service
if(owner && owner != &o->io_service_impl_)
{ // private io_service
// Start blocking call
bad::socket_ops::shared_cancel_token_type lock =
o->cancel_token_.lock();
if(!lock){
o->ec_ = boost::system::error_code(
ba::error::operation_aborted,
boost::system::system_category());
}else{
timer_impl *impl = static_cast<timer_impl*>(lock.get());
impl->wait(o->seconds_, o->ec_);
}
// End of blocking call
o->io_service_impl_.post_deferred_completion(o);
p.v = p.p = 0;
}else{ // main io_service
bad::binder1<Handler, boost::system::error_code>
handler(o->handler_, o->ec_);
p.h = boost::addressof(handler.handler_);
p.reset();
if(owner){
bad::fenced_block b(bad::fenced_block::half);
boost_asio_handler_invoke_helpers::invoke(
handler, handler.handler_);
}
}
}
private:
bad::socket_ops::weak_cancel_token_type cancel_token_;
std::size_t seconds_;
bad::io_service_impl &io_service_impl_;
Handler handler_;
boost::system::error_code ec_;
};
template <typename Handler>
void async_wait(
implementation_type &impl,
std::size_t seconds, Handler handler)
{
typedef wait_operation<Handler> op;
typename op::ptr p = {
boost::addressof(handler),
boost_asio_handler_alloc_helpers::allocate(
sizeof(op), handler), 0};
p.p = new (p.v) op(impl, seconds, io_service_impl_, handler);
start_op(p.p);
p.v = p.p = 0;
}
protected:
// Functor for runing background thread
class work_io_service_runner
{
public:
work_io_service_runner(ba::io_service &io_service)
: io_service_(io_service) {}
void operator()(){ io_service_.run(); }
private:
ba::io_service &io_service_;
};
void start_op(bad::operation* op)
{
start_work_thread();
io_service_impl_.work_started();
work_io_service_impl_.post_immediate_completion(op);
}
void start_work_thread()
{
bad::mutex::scoped_lock lock(mutex_);
if (!work_thread_.get())
{
work_thread_.reset(new bad::thread(
work_io_service_runner(*work_io_service_)));
}
}
bad::io_service_impl& io_service_impl_;
private:
bad::mutex mutex_;
boost::scoped_ptr<ba::io_service> work_io_service_;
bad::io_service_impl &work_io_service_impl_;
boost::scoped_ptr<ba::io_service::work> work_;
boost::scoped_ptr<bad::thread> work_thread_;
};
boost::asio::io_service::id basic_timer_service::id;
typedef basic_timer<basic_timer_service> timer;
void wait_handler(const boost::system::error_code &ec)
{
if(!ec)
std::cout << "wait_handler is called\n" ;
else
std::cerr << "Error: " << ec.message() << "\n";
}
int main()
{
{
boost::asio::io_service io_service;
boost::asio::signal_set signals(io_service);
timer t(io_service);
signals.add(SIGINT);
signals.async_wait(
boost::bind(&boost::asio::io_service::stop, &io_service));
t.async_wait(2, wait_handler);
std:: cout << "async called\n" ;
io_service.run();
std:: cout << "exit loop\n";
}
{
boost::asio::io_service io_service;
timer t(io_service);
t.async_wait(2, wait_handler);
std:: cout << "async called\n" ;
io_service.run();
}
return 0;
}
To compile
gcc -I/boost_inc -L/boot_lib main.cpp -lpthread -lboost_system -lboost_thread
The new timer works fine. Still I would like to know how to write a non-intrusive extension of asio.
I want to implement a java-like Timer by asio's timer, it used to execute code periodically.
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
void print()
{
std::cout << "Hello, world!\n";
}
class WorldTimer
{
public:
boost::posix_time::ptime now()
{
return _timer.expires_at();
}
void update()
{
_f();
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(1000));
_timer.async_wait(boost::bind(&WorldTimer::update, this));
}
WorldTimer(boost::asio::io_service& io, void (*f)()) : _f(f), _timer(io){}
private:
void (*_f)();
boost::asio::deadline_timer _timer;
};
int main() {
boost::asio::io_service io;
WorldTimer timer(io, print);
timer.update();
io.run();
return 0;
}
Program only output Hello, world! once and pending there. asio doc has a example, it works but I can not figure out what's the difference.
Yeah... timer has not been init a expire time, this is revised version:
class WorldTimer
{
public:
boost::posix_time::ptime now()
{
return _timer.expires_at();
}
WorldTimer(boost::asio::io_service& io, void (*f)()) : _f(f), _timer(io, boost::posix_time::microseconds(0))
{
_timer.async_wait(boost::bind(&WorldTimer::update, this));
}
private:
void (*_f)();
boost::asio::deadline_timer _timer;
void update()
{
_f();
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(1000));
_timer.async_wait(boost::bind(&WorldTimer::update, this));
}
};
int main() {
boost::asio::io_service io;
WorldTimer timer(io, print);
io.run();
return 0;
}
Your deadline timer constructor is different from the one in the example. You need to explicitly set the expiry time.
The example code uses the other constructor which sets a particular expiry time relative to now.
So the print-out you are seeing is related to your call to update, which calls
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(1000));
and _timer.expires_at() has not been set yet...