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));
}
}
Related
I'm working on a timer class to perform operations on a different thread, sample code below is a copy from another SO question HERE
#include <thread>
#include <chrono>
#include <functional>
class Timer
{
public:
~Timer();
Timer() noexcept;
typedef std::chrono::milliseconds Interval;
typedef std::function<void(void)> Timeout;
public:
void start(const Interval& interval, const Timeout& timeout);
void stop();
private:
std::thread mThread; /** Timer thread */
bool mRunning = false; /** Timer status */
};
Implementation with a comment where the problem will occur:
Timer::~Timer()
{
}
Timer::Timer() noexcept
{
}
void Timer::start(const Interval& interval, const Timeout& timeout)
{
mRunning = true;
mThread = std::thread([&]()
{
while (mRunning == true)
{
std::this_thread::sleep_for(interval);
// std::abort will be called here
timeout();
}
});
}
void Timer::stop()
{
mRunning = false;
mThread.join();
}
Sample to test the timer:
#include <iostream>
int main()
{
Timer tm;
tm.start(std::chrono::milliseconds(1000), []
{
std::cout << "Hello!" << std::endl;
});
std::this_thread::sleep_for(std::chrono::seconds(4));
tm.stop();
}
I'm not able to understand why std::abort is called while executing the std::function within lambda and how do I resolve this?
Arguments to your start function are passed by reference. In your lambda, you capture them by reference. By the time you come around calling that lambda, everything you've captured is destroyed, thus you're causing undefined behavior.
Additionally, make sure to either use atomic<bool> instead of a regular bool:
#include <thread>
#include <chrono>
#include <functional>
#include <cstdio>
#include <atomic>
class Timer {
public:
~Timer() {
if (mRunning) {
stop();
}
}
typedef std::chrono::milliseconds Interval;
typedef std::function<void(void)> Timeout;
void start(const Interval &interval, const Timeout &timeout) {
mRunning = true;
mThread = std::thread([this, interval, timeout] {
while (mRunning) {
std::this_thread::sleep_for(interval);
timeout();
}
});
}
void stop() {
mRunning = false;
mThread.join();
}
private:
std::thread mThread{};
std::atomic_bool mRunning{};
};
int main() {
Timer tm;
tm.start(std::chrono::milliseconds(1000), [] {
std::puts("Hello!");
});
std::this_thread::sleep_for(std::chrono::seconds(4));
}
P.S. you might want to look into coroutines depending on where this idea is going.
When trying to learn threads most examples suggests that I should put std::mutex, std::condition_variable and std::queue global when sharing data between two different threads and it works perfectly fine for simple scenario. However, in real case scenario and bigger applications this may soon get complicated as I may soon lose track of the global variables and since I am using C++ this does not seem to be an appropriate option (may be I am wrong)
My question is if I have a producer/consumer problem and I want to put both in separate classes, since they will be sharing data I would need to pass them the same mutex and queue now how do I share these two variables between them without defining it to be global and what is the best practice for creating threads?
Here is a working example of my basic code using global variables.
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable cond;
const int MAX_BUFFER_SIZE = 50;
class Producer
{
public:
void run(int val)
{
while(true) {
std::unique_lock locker(mtx) ;
cond.wait(locker, []() {
return buffer.size() < MAX_BUFFER_SIZE;
});
buffer.push(val);
std::cout << "Produced " << val << std::endl;
val --;
locker.unlock();
// std::this_thread::sleep_for(std::chrono::seconds(2));
cond.notify_one();
}
}
};
class Consumer
{
public:
void run()
{
while(true) {
std::unique_lock locker(mtx);
cond.wait(locker, []() {
return buffer.size() > 0;
});
int val = buffer.front();
buffer.pop();
std::cout << "Consumed " << val << std::endl;
locker.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
cond.notify_one();
}
}
};
int main()
{
std::thread t1(&Producer::run, Producer(), MAX_BUFFER_SIZE);
std::thread t2(&Consumer::run, Consumer());
t1.join();
t2.join();
return 0;
}
Typically, you want to have synchronisation objects packaged alongside the resource(s) they are protecting.
A simple way to do that in your case would be a class that contains the buffer, the mutex, and the condition variable. All you really need is to share a reference to one of those to both the Consumer and the Producer.
Here's one way to go about it while keeping most of your code as-is:
class Channel {
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable cond;
// Since we know `Consumer` and `Producer` are the only entities
// that will ever access buffer, mtx and cond, it's better to
// not provide *any* public (direct or indirect) interface to
// them, and use `friend` to grant access.
friend class Producer;
friend class Consumer;
public:
// ...
};
class Producer {
Channel* chan_;
public:
explicit Producer(Channel* chan) : chan_(chan) {}
// ...
};
class Consumer {
Channel* chan_;
public:
explicit Consumer(Channel* chan) : chan_(chan) {}
// ...
};
int main() {
Channel channel;
std::thread t1(&Producer::run, Producer(&channel), MAX_BUFFER_SIZE);
std::thread t2(&Consumer::run, Consumer(&channel));
t1.join();
t2.join();
}
However, (Thanks for the prompt, #Ext3h) a better way to go about this would be to encapsulate access to the synchronisation objects as well, i.e. keep them hidden in the class. At that point Channel becomes what is commonly known as a Synchronised Queue
Here's what I'd subjectively consider a nicer-looking implementation of your example code, with a few misc improvements thrown in as well:
#include <cassert>
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <optional>
#include <condition_variable>
template<typename T>
class Channel {
static constexpr std::size_t default_max_length = 10;
public:
using value_type = T;
explicit Channel(std::size_t max_length = default_max_length)
: max_length_(max_length) {}
std::optional<value_type> next() {
std::unique_lock locker(mtx_);
cond_.wait(locker, [this]() {
return !buffer_.empty() || closed_;
});
if (buffer_.empty()) {
assert(closed_);
return std::nullopt;
}
value_type val = buffer_.front();
buffer_.pop();
cond_.notify_one();
return val;
}
void put(value_type val) {
std::unique_lock locker(mtx_);
cond_.wait(locker, [this]() {
return buffer_.size() < max_length_;
});
buffer_.push(std::move(val));
cond_.notify_one();
}
void close() {
std::scoped_lock locker(mtx_);
closed_ = true;
cond_.notify_all();
}
private:
std::size_t max_length_;
std::queue<value_type> buffer_;
bool closed_ = false;
std::mutex mtx_;
std::condition_variable cond_;
};
void producer_main(Channel<int>& chan, int val) {
// Don't use while(true), it's Undefined Behavior
while (val >= 0) {
chan.put(val);
std::cout << "Produced " << val << std::endl;
val--;
}
}
void consumer_main(Channel<int>& chan) {
bool running = true;
while (running) {
auto val = chan.next();
if (!val) {
running = false;
continue;
}
std::cout << "Consumed " << *val << std::endl;
};
}
int main()
{
// You are responsible for ensuring the channel outlives both threads.
Channel<int> channel;
std::thread producer_thread(producer_main, std::ref(channel), 13);
std::thread consumer_thread(consumer_main, std::ref(channel));
producer_thread.join();
channel.close();
consumer_thread.join();
return 0;
}
I want to create a thread that can be interrupted while waiting (it waits data from other processes and I want to stop the process in nice way).
I've read the 9.2 part of C++ Concurrency in Action 2nd Edition, and I've tried to implement that ideas, but I've some problem and I don't know where to check.
This is my code based on that example:
#include <iostream>
#include <stdexcept>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <future>
// Exception that should be raised when there's an interruption.
// It's raised when the thread is interrupted, so we can catch
// it and finish the thread execution.
class InterruptedException : public std::runtime_error {
public:
InterruptedException(const std::string& message) : std::runtime_error(message) {}
virtual ~InterruptedException() {}
};
// Interrupt flag. This class represents a local-thread flag that
// tells if the thread is interrupted or not.
class InterruptFlag {
public:
InterruptFlag() :
m_threadConditionVariable(nullptr),
m_threadConditionVariableAny(nullptr) {}
void set() {
m_flag.store(true, std::memory_order_relaxed);
std::lock_guard<std::mutex> lk(m_setClearMutex);
if (m_threadConditionVariable) {
m_threadConditionVariable->notify_all();
}
else if (m_threadConditionVariableAny) {
m_threadConditionVariableAny->notify_all();
}
}
template <typename Lockable>
void wait(std::condition_variable_any& cv, Lockable& lk) {
struct CustomLock {
InterruptFlag* m_self;
Lockable& m_lk;
CustomLock(InterruptFlag* self, std::condition_variable_any& cond, Lockable& lk) :
m_self(self),
m_lk(lk) {
m_self->m_setClearMutex.unlock();
m_self->m_threadConditionVariableAny = &cond;
}
void unlock() {
m_lk.unlock();
m_self->m_setClearMutex.unlock();
}
void lock() {
std::lock(m_self->m_setClearMutex, lk);
}
~CustomLock() {
m_self->m_threadConditionAny = nullptr;
m_self->m_setClearMutex.unlock();
}
};
CustomLock cl(this, cv, lk);
InterruptPoint();
cv.wait(cl);
InterruptPoint();
}
void setConditionVariable(std::condition_variable& cv) {
std::lock_guard<std::mutex> lk(m_setClearMutex);
m_threadConditionVariable = &cv;
}
void clearConditionVariable() {
std::lock_guard<std::mutex> lk(m_setClearMutex);
m_threadConditionVariable = nullptr;
}
bool isSet() const {
return m_flag.load(std::memory_order_relaxed);
}
private:
std::atomic<bool> m_flag;
std::condition_variable* m_threadConditionVariable;
std::condition_variable_any* m_threadConditionVariableAny;
std::mutex m_setClearMutex;
};
// Thread-local interrupt flag instance. The variable should be
// created for every thread, since it's thread_local.
thread_local InterruptFlag ThisThreadInterruptFlag;
// Convenience class for cleaning the flag due to RAII.
struct ClearConditionVariableOnDestruct {
~ClearConditionVariableOnDestruct() {
ThisThreadInterruptFlag.clearConditionVariable();
}
};
// Function that throws the exception that tells that the thread
// is interrupted. For doing it checks the state of ThisThreadInterruptFlag.
void InterruptionPoint() {
if (ThisThreadInterruptFlag.isSet()) {
throw InterruptedException("Interrupted");
}
}
// Function that must be used inside the thread function body for waiting.
// It waits for the condition variable, when it notifies from other threads,
// but it also notifies if the thread is interrupted.
void InterruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lk) {
InterruptionPoint();
ThisThreadInterruptFlag.setConditionVariable(cv);
ClearConditionVariableOnDestruct guard;
InterruptionPoint();
cv.wait_for(lk, std::chrono::milliseconds(1));
InterruptionPoint();
}
// This class represents the interruptible thread. It adds a interrupt()
// method that when called interupts the thread execution, if it's waiting
// at some point where InterruptibleWait function is locked.
class Interruptible {
public:
template <typename FunctionType>
Interruptible(FunctionType f) {
std::promise<InterruptFlag*> p;
m_internalThread = std::thread([f, &p]() {
p.set_value(&ThisThreadInterruptFlag);
try {
f();
}
catch (InterruptedException) {
}
});
m_flag = p.get_future().get();
}
void join() {
m_internalThread.join();
}
void detach() {
m_internalThread.detach();
}
bool joinable() const {
return m_internalThread.joinable();
}
void interrupt() {
if (m_flag) {
m_flag->set();
}
}
private:
std::thread m_internalThread;
InterruptFlag* m_flag;
};
std::mutex mtx;
std::unique_lock<std::mutex> lk(mtx);
int main(int argc, char* argv[]) {
std::cout << "Interrupting thread example" << std::endl;
bool test = false;
std::condition_variable cv;
auto f = [&cv, &test]() {
test = true;
InterruptibleWait(cv, lk);
// Since it locks forever, it should never reach this point.
test = false;
};
Interruptible interruptibleThread(f);
std::this_thread::sleep_for(std::chrono::milliseconds(30));
// We interrupt the function while it's blocked in InterruptibleWait
interruptibleThread.interrupt();
interruptibleThread.join();
std::cout << "test value is " << std::boolalpha << test << ". It should be true." << std::endl;
return 0;
}
Basically I create a Interruptible class representing a thread that can be interrupted. I interrupt it during its execution by calling its interrupt() method. The thread can be interrupted if it's locked with in a InterruptibleWait function call. This function behave like a std::condition.wait(), in fact it wants a reference to it, but it also handle the interruption flag.
If I start the program. I obtain an error from Visual Studio when running.
I don't know what I'm doing wrong. What should I do in order to make InterruptibleWait work correctly?
My best guess based on the given information:
The exception isn't caught in the thread entry point function, and escapes that function. When this happens in a thread started by std::thread, abort is called for you (indirectly through std::terminate) by the std::thread implementation, as required by the standard. To fix this, try catching all exceptions in the function passed to std::thread.
See the cppreference articles on std::thread and std::terminate
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.
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.