Extension of boost::asio hangs after being interrupted - c++

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.

Related

Boost socket acceptor segmentation fault

Im trying write tcp server. From boost guide link enter link description here. When im trying open acceptor - segmentation fault, IDE show me this line object_pool_access::prev(live_list_) = o;
What im doing wrong? Im trying another way for example run threads for context but that doesnt work. Sometime when im try to fix i get this one message -
tpp.c:82: __pthread_tpp_change_priority: Assertion `new_prio == -1 || (new_prio >= fifo_min_prio && new_prio <= fifo_max_prio)' failed.
Aborted (core dumped)
The code is contained in the 1st file.
Application class with io_context, he is here because ill use him in another parts of program:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <memory>
using namespace boost::asio;
class api_application
{
public:
virtual ~api_application() {}
virtual void start() = 0;
virtual boost::asio::io_context& get_context() = 0;
};
class application : public api_application
{
public:
application();
~application() override {}
public:
virtual void start() override;
virtual boost::asio::io_context& get_context() override;
void stop();
private:
boost::asio::io_context _context;
std::shared_ptr<tcp_server> _server;
};
application::application()
:
_context(),
_server(std::make_shared<tcp_server>(*this))
{}
void application::start()
{
_server->start();
_context.run();
}
void application::stop()
{
_context.stop();
}
boost::asio::io_context& application::get_context()
{
return _context;
}
Networking code with error line:
class connection : public std::enable_shared_from_this<connection>
{
public:
typedef std::shared_ptr<connection> con_ptr;
static con_ptr create(boost::asio::io_context& io_service)
{
return con_ptr(new connection(io_service));
}
ip::tcp::socket& socket();
void send(std::string message);
private:
connection(boost::asio::io_context& io_service);
void handle_write(const boost::system::error_code& err, size_t s);
private:
ip::tcp::socket _socket;
};
class tcp_server
{
public:
tcp_server(api_application& app);
void start();
private:
void start_accept();
void handle_accept(connection::con_ptr new_connection, const boost::system::error_code& error);
api_application& _app;
ip::tcp::endpoint _endpoint;
ip::tcp::acceptor _acceptor;
};
tcp_server::tcp_server(api_application& app)
:
_app(app),
_endpoint(boost::asio::ip::address_v4::any(), 80),
_acceptor(app.get_context())
{}
void tcp_server::start()
{
if(_acceptor.is_open())
return;
_acceptor.open(_endpoint.protocol()); // Here segfault
_acceptor.set_option(ip::tcp::socket::reuse_address(true));
_acceptor.bind(_endpoint);
_acceptor.listen();
start_accept();
}
void tcp_server::start_accept()
{
connection::con_ptr new_connection =
connection::create((boost::asio::io_context&)_acceptor.get_executor().context());
_acceptor.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void tcp_server::handle_accept(connection::con_ptr new_connection, const boost::system::error_code& error)
{
if (!error)
{
new_connection->send("Success connection");
}
start_accept();
}
void connection::send(std::string message)
{
boost::asio::async_write(_socket, boost::asio::buffer(message),
boost::bind(&connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
connection::connection(boost::asio::io_context& io_service)
: _socket(io_service)
{}
int main(int argc, const char** argv)
{
application app();
app.start();
return 0;
}
I found the likely issue while rewriting the code to avoid context references. See Update
I have no problem with the code, imagining the missing bits:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <memory>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
class connection : public std::enable_shared_from_this<connection> {
public:
typedef std::shared_ptr<connection> con_ptr;
static con_ptr create(asio::io_context& io_service) {
return con_ptr{new connection(io_service)};
}
tcp::socket& socket() { return _socket; }
void send(std::string message);
private:
connection(asio::io_context& io_service);
void handle_write(error_code err, size_t s) {
std::cerr << "handle_write: " << err.message() << " " << s << std::endl;
}
private:
tcp::socket _socket;
};
struct api_application {
virtual ~api_application() {}
virtual void start() = 0;
virtual asio::io_context& get_context() = 0;
};
class tcp_server {
public:
tcp_server(api_application& app);
void start();
private:
void start_accept();
void handle_accept(connection::con_ptr new_connection,
error_code error);
api_application& _app;
tcp::endpoint _endpoint;
tcp::acceptor _acceptor;
};
class application : public api_application {
public:
application();
~application() override {}
public:
virtual void start() override;
virtual asio::io_context& get_context() override;
void stop();
private:
asio::io_context _context;
std::shared_ptr<tcp_server> _server;
};
application::application()
: _context()
, _server(std::make_shared<tcp_server>(*this)) {}
void application::start() {
_server->start();
_context.run();
}
tcp_server::tcp_server(api_application& app)
: _app(app)
, _endpoint({}, 8989)
, _acceptor(app.get_context()) {}
void tcp_server::start() {
if (_acceptor.is_open())
return;
_acceptor.open(_endpoint.protocol()); // Here segfault
_acceptor.set_option(tcp::acceptor::reuse_address(true));
_acceptor.bind(_endpoint);
_acceptor.listen();
start_accept();
}
void tcp_server::start_accept() {
connection::con_ptr new_connection = connection::create(
(asio::io_context&)_acceptor.get_executor().context());
_acceptor.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this,
new_connection,
asio::placeholders::error));
}
void tcp_server::handle_accept(connection::con_ptr new_connection,
error_code error) {
if (!error) {
new_connection->send("Success connection");
}
start_accept();
}
void connection::send(std::string message) {
async_write(_socket, asio::buffer(message),
boost::bind(&connection::handle_write,
shared_from_this(),
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
connection::connection(asio::io_context& io_service)
: _socket(io_service) {}
void application::stop() { _context.stop(); }
asio::io_context& application::get_context() { return _context; }
int main() {
application app;
app.start();
}
Prints e.g.
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp
./a.out&
for a in {1..10}; do sleep 0.5; nc 127.0.0.1 8989 <<<"Hello world"; done
kill %1
Success connectionhandle_write: Success 18
handle_write: Success connectionSuccess 18
handle_write: Success Success connection18
Success connectionhandle_write: Success 18
Success connectionhandle_write: Success 18
Success connectionhandle_write: Success 18
Success connectionhandle_write: Success 18
Success connectionhandle_write: Success 18
Success connectionhandle_write: Success 18
Success connectionhandle_write: Success 18
Summary
Your problem is elsewhere. Permissions, exception handling, ODR, maybe you're not running the code you think you are.
Also, this code seems unnecessarily complicated and a bit dated (io_service has been deprecated for quite some time).
UPDATE
This is highly suspicious code
connection::con_ptr new_connection = connection::create(
(asio::io_context&)_acceptor.get_executor().context());
If it works it is due to undocumented implementation details. It is technically Undefined Behaviour because it does a hard re-interpret-cast. Instead, just use the executor as intended!
auto new_connection = connection::create(_acceptor.get_executor());
Here's the whole thing reworked to avoid context references:
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <memory>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
class connection : public std::enable_shared_from_this<connection> {
public:
typedef std::shared_ptr<connection> con_ptr;
static con_ptr create(asio::any_io_executor ex) {
return con_ptr{new connection(ex)};
}
tcp::socket& socket() { return _socket; }
void send(std::string message);
private:
connection(asio::any_io_executor ex);
void handle_write(error_code err, size_t s) {
std::cerr << "handle_write: " << err.message() << " " << s << std::endl;
}
private:
tcp::socket _socket;
};
struct api_application {
virtual ~api_application() {}
virtual void start() = 0;
virtual asio::any_io_executor get_executor() = 0;
};
class tcp_server {
public:
tcp_server(api_application& app);
void start();
private:
void start_accept();
void handle_accept(connection::con_ptr new_connection,
error_code error);
api_application& _app;
tcp::endpoint _endpoint;
tcp::acceptor _acceptor;
};
class application : public api_application {
public:
application();
~application() override {}
public:
virtual void start() override;
virtual asio::any_io_executor get_executor() override;
void stop();
private:
asio::io_context _context;
std::shared_ptr<tcp_server> _server;
};
application::application()
: _context()
, _server(std::make_shared<tcp_server>(*this)) {}
void application::start() {
_server->start();
_context.run();
}
tcp_server::tcp_server(api_application& app)
: _app(app)
, _endpoint({}, 8989)
, _acceptor(app.get_executor()) {}
void tcp_server::start() {
if (_acceptor.is_open())
return;
_acceptor.open(_endpoint.protocol()); // Here segfault
_acceptor.set_option(tcp::acceptor::reuse_address(true));
_acceptor.bind(_endpoint);
_acceptor.listen();
start_accept();
}
void tcp_server::start_accept() {
auto new_connection = connection::create(_acceptor.get_executor());
_acceptor.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this,
new_connection,
asio::placeholders::error));
}
void tcp_server::handle_accept(connection::con_ptr new_connection,
error_code error) {
if (!error) {
new_connection->send("Success connection");
}
start_accept();
}
void connection::send(std::string message) {
async_write(_socket, asio::buffer(message),
boost::bind(&connection::handle_write,
shared_from_this(),
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
connection::connection(asio::any_io_executor ex) : _socket(ex) {}
void application::stop() { _context.stop(); }
asio::any_io_executor application::get_executor() {
return _context.get_executor();
}
int main() {
application app;
app.start();
}
Still printing the same Live On Coliru

Stopping scheduled task with SIGINT

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.

shared_from_this throws bad_weak_ptr with boost::asio

First, I have read all related questions listed.
They say, "you must have an existing shared_ptr to this before you can use shared_from_this." As far as I can see, there is no way I am violating that condition. I create the instance of Foo as a shared_ptr and enforced that it is always created as a shared_ptr. I then, stored the shared_ptr in a collection. Yet, I still get the bad_weak_ptr exception when shared_from_this is called.
#pragma once
#include <memory>
#include <vector>
//--------------------------------------------------------------------
class Foo : std::enable_shared_from_this<Foo>
{
public:
typedef std::shared_ptr<Foo> SharedPtr;
// Ensure all instances are created as shared_ptr in order to fulfill requirements for shared_from_this
static Foo::SharedPtr Create()
{
return Foo::SharedPtr(new Foo());
};
Foo(const Foo &) = delete;
Foo(Foo &&) = delete;
Foo & operator = (const Foo &) = delete;
Foo & operator = (Foo &&) = delete;
~Foo() {};
// We have to defer the start until we are fully constructed because we share_from_this()
void Start()
{
DoStuff();
}
private:
Foo() {}
void DoStuff()
{
auto self(shared_from_this());
}
};
//--------------------------------------------------------------------
int main()
{
std::vector<Foo::SharedPtr> foos;
Foo::SharedPtr foo = Foo::Create();
foos.emplace_back(foo);
foo->Start();
return 0;
}
You must inherit enable_shared_from_this with public specifier according to
Publicly inheriting from std::enable_shared_from_this provides the type T with a member function shared_from_this.
from http://en.cppreference.com/w/cpp/memory/enable_shared_from_this.
So write
class Foo : public std::enable_shared_from_this<Foo>
First off, you start the threads before ever posting work, so the io_service::run() is prone to complete before DoAccept is actually done.
Next, the base class must be PUBLIC for enable_shared_from_this to work:
class Connection : public std::enable_shared_from_this<Connection> {
Working self-contained code:
#include <iostream>
#include <mutex>
namespace SomeNamespace{
struct Logger {
enum { LOGGER_SEVERITY_INFO };
void Log(std::string const& msg, std::string const& file, unsigned line, int level) const {
static std::mutex mx;
std::lock_guard<std::mutex> lk(mx);
std::cout << file << ":" << line << " level:" << level << " " << msg << "\n";
}
template <typename... Args>
void LogF(std::string const& msg, Args const&... args) const {
static std::mutex mx;
std::lock_guard<std::mutex> lk(mx);
static char buf[2048];
snprintf(buf, sizeof(buf)-1, msg.c_str(), args...);
std::cout << buf << "\n";
}
static Logger &GetInstance() {
static Logger This;
return This;
}
};
} // namespace Somenamespace
#include <boost/asio.hpp>
#include <atomic>
#include <condition_variable>
#include <memory>
//--------------------------------------------------------------------
class ConnectionManager;
//--------------------------------------------------------------------
class Connection : public std::enable_shared_from_this<Connection> {
public:
typedef std::shared_ptr<Connection> SharedPtr;
// Ensure all instances are created as shared_ptr in order to fulfill requirements for shared_from_this
static Connection::SharedPtr Create(ConnectionManager *connectionManager, boost::asio::ip::tcp::socket &socket);
Connection(const Connection &) = delete;
Connection(Connection &&) = delete;
Connection &operator=(const Connection &) = delete;
Connection &operator=(Connection &&) = delete;
~Connection();
// We have to defer the start until we are fully constructed because we share_from_this()
void Start();
void Stop();
void Send(const std::vector<char> &data);
private:
ConnectionManager *m_owner;
boost::asio::ip::tcp::socket m_socket;
std::atomic<bool> m_stopped;
boost::asio::streambuf m_receiveBuffer;
mutable std::mutex m_sendMutex;
std::shared_ptr<std::vector<boost::asio::const_buffer> > m_sendBuffers;
bool m_sending;
std::vector<char> m_allReadData; // for testing
Connection(ConnectionManager *connectionManager, boost::asio::ip::tcp::socket socket);
void DoReceive();
void DoSend();
};
//--------------------------------------------------------------------
//#include "Connection.h"
//#include "ConnectionManager.h"
//**ConnectionManager.h **
//#pragma once
//#include "Connection.h"
// Boost Includes
#include <boost/asio.hpp>
// Standard Includes
#include <thread>
#include <vector>
//--------------------------------------------------------------------
class ConnectionManager {
public:
ConnectionManager(unsigned port, size_t numThreads);
ConnectionManager(const ConnectionManager &) = delete;
ConnectionManager(ConnectionManager &&) = delete;
ConnectionManager &operator=(const ConnectionManager &) = delete;
ConnectionManager &operator=(ConnectionManager &&) = delete;
~ConnectionManager();
void Start();
void Stop();
void OnConnectionClosed(Connection::SharedPtr connection);
protected:
boost::asio::io_service m_io_service;
boost::asio::ip::tcp::acceptor m_acceptor;
boost::asio::ip::tcp::socket m_listenSocket;
std::vector<std::thread> m_threads;
mutable std::mutex m_connectionsMutex;
std::vector<Connection::SharedPtr> m_connections;
void IoServiceThreadProc();
void DoAccept();
};
//--------------------------------------------------------------------
#include <boost/bind.hpp>
#include <algorithm>
//--------------------------------------------------------------------
Connection::SharedPtr Connection::Create(ConnectionManager *connectionManager, boost::asio::ip::tcp::socket &socket) {
return Connection::SharedPtr(new Connection(connectionManager, std::move(socket)));
}
//--------------------------------------------------------------------
Connection::Connection(ConnectionManager *connectionManager, boost::asio::ip::tcp::socket socket)
: m_owner(connectionManager), m_socket(std::move(socket)), m_stopped(false), m_receiveBuffer(), m_sendMutex(),
m_sendBuffers(), m_sending(false), m_allReadData() {}
//--------------------------------------------------------------------
Connection::~Connection() {
// Boost uses RAII, so we don't have anything to do. Let thier destructors take care of business
}
//--------------------------------------------------------------------
void Connection::Start() { DoReceive(); }
//--------------------------------------------------------------------
void Connection::Stop() {
// The entire connection class is only kept alive, because it is a shared pointer and always has a ref count
// as a consequence of the outstanding async receive call that gets posted every time we receive.
// Once we stop posting another receive in the receive handler and once our owner release any references to
// us, we will get destroyed.
m_stopped = true;
m_owner->OnConnectionClosed(shared_from_this());
}
//--------------------------------------------------------------------
void Connection::Send(const std::vector<char> &data) {
std::lock_guard<std::mutex> lock(m_sendMutex);
// If the send buffers do not exist, then create them
if (!m_sendBuffers) {
m_sendBuffers = std::make_shared<std::vector<boost::asio::const_buffer> >();
}
// Copy the data to be sent to the send buffers
m_sendBuffers->emplace_back(boost::asio::buffer(data));
DoSend();
}
//--------------------------------------------------------------------
void Connection::DoSend() {
// According to the boost documentation, we cannot issue an async_write while one is already outstanding
//
// If that is the case, it is OK, because we've added the data to be sent to a new set of buffers back in
// the Send method. Notice how the original buffer is moved, so therefore will be null below and how Send
// will create new buffers and accumulate data to be sent until we complete in the lamda
//
// When we complete in the lamda, if we have any new data to be sent, we call DoSend once again.
//
// It is important though, that DoSend is only called from the lambda below and the Send method.
if (!m_sending && m_sendBuffers) {
m_sending = true;
auto copy = std::move(m_sendBuffers);
auto self(shared_from_this());
boost::asio::async_write(m_socket, *copy,
[self, copy](const boost::system::error_code &errorCode, size_t bytes_transferred) {
std::lock_guard<std::mutex> lock(self->m_sendMutex);
self->m_sending = false;
if (errorCode) {
// An error occurred
return;
}
self->DoSend();
});
}
}
//--------------------------------------------------------------------
void Connection::DoReceive() {
SomeNamespace::Logger::GetInstance().Log(__PRETTY_FUNCTION__, __FILE__, __LINE__, SomeNamespace::Logger::LOGGER_SEVERITY_INFO);
auto self(shared_from_this()); // ***EXCEPTION HERE****
boost::asio::async_read_until(m_socket, m_receiveBuffer, '#',
[self](const boost::system::error_code &errorCode, size_t bytesRead) {
if (errorCode) {
// Notify our masters that we are ready to be destroyed
self->m_owner->OnConnectionClosed(self);
// An error occured
return;
}
// Grab the read data
std::istream stream(&self->m_receiveBuffer);
std::string data;
std::getline(stream, data, '#');
// Issue the next receive
if (!self->m_stopped) {
self->DoReceive();
}
});
}
//--------------------------------------------------------------------
//**ConnectionManager.cpp **
//#include "ConnectionManager.h"
//#include "Logger.h"
#include <boost/bind.hpp>
#include <system_error>
//------------------------------------------------------------------------------
ConnectionManager::ConnectionManager(unsigned port, size_t numThreads)
: m_io_service(), m_acceptor(m_io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
m_listenSocket(m_io_service), m_threads(numThreads) {}
//------------------------------------------------------------------------------
ConnectionManager::~ConnectionManager() { Stop(); }
//------------------------------------------------------------------------------
void ConnectionManager::Start() {
if (m_io_service.stopped()) {
m_io_service.reset();
}
DoAccept();
for (auto &thread : m_threads) {
if (!thread.joinable()) {
thread = std::thread(&ConnectionManager::IoServiceThreadProc, this);
}
}
}
//------------------------------------------------------------------------------
void ConnectionManager::Stop() {
{
std::lock_guard<std::mutex> lock(m_connectionsMutex);
m_connections.clear();
}
// TODO - Will the stopping of the io_service be enough to kill all the connections and ultimately have them get
// destroyed?
// Because remember they have outstanding ref count to thier shared_ptr in the async handlers
m_io_service.stop();
for (auto &thread : m_threads) {
if (thread.joinable()) {
thread.join();
}
}
}
//------------------------------------------------------------------------------
void ConnectionManager::IoServiceThreadProc() {
try {
// Log that we are starting the io_service thread
{
const std::string msg("io_service socket thread starting.");
SomeNamespace::Logger::GetInstance().Log(msg, __FILE__, __LINE__,
SomeNamespace::Logger::LOGGER_SEVERITY_INFO);
}
// Run the asynchronous callbacks from the socket on this thread
// Until the io_service is stopped from another thread
m_io_service.run();
} catch (std::system_error &e) {
SomeNamespace::Logger::GetInstance().LogF("System error caught in io_service socket thread. Error Code: %d", e.code().value());
} catch (std::exception &e) {
SomeNamespace::Logger::GetInstance().LogF("Standard exception caught in io_service socket thread. Exception: %s", e.what());
} catch (...) {
SomeNamespace::Logger::GetInstance().LogF("Unhandled exception caught in io_service socket thread.");
}
SomeNamespace::Logger::GetInstance().LogF("io_service socket thread exiting.");
}
//------------------------------------------------------------------------------
void ConnectionManager::DoAccept() {
SomeNamespace::Logger::GetInstance().Log(__PRETTY_FUNCTION__, __FILE__, __LINE__, SomeNamespace::Logger::LOGGER_SEVERITY_INFO);
m_acceptor.async_accept(m_listenSocket, [this](const boost::system::error_code errorCode) {
if (errorCode) {
return;
}
{
// Create the connection from the connected socket
Connection::SharedPtr connection = Connection::Create(this, m_listenSocket);
{
std::lock_guard<std::mutex> lock(m_connectionsMutex);
m_connections.push_back(connection);
connection->Start();
}
}
DoAccept();
});
}
//------------------------------------------------------------------------------
void ConnectionManager::OnConnectionClosed(Connection::SharedPtr connection) {
std::lock_guard<std::mutex> lock(m_connectionsMutex);
auto itConnection = std::find(m_connections.begin(), m_connections.end(), connection);
if (itConnection != m_connections.end()) {
m_connections.erase(itConnection);
}
}
//------------------------------------------------------------------------------
//**main.cpp**
//#include "ConnectionManager.h"
#include <cstring>
#include <iostream>
#include <string>
int main() {
ConnectionManager connectionManager(4000, 2);
connectionManager.Start();
std::this_thread::sleep_for(std::chrono::minutes(1));
connectionManager.Stop();
}

c++ saving bound object and using it after asio

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).

When I need to run io_service and why

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));
}
}