Dear Boost community,
We're having a problem using the ASIO class with multiple IO service
threads and throwing exceptions inside the handle_xxx functions, as used
by the async_xxx functions of the ASIO library.
It seems that when any error is thrown in a normal/non-handler function,
the try/catch block of the main program is able to catch the error.
However, when the handler functions are called (I would assume by the io
service, now running in a separate thread), the main function's
try/catch blocks never receive the execeptions, and a std::terminate()
is called automatically.
To demonstrate this, we've taken the HTTP Server 2 example from the ASIO
examples page and reduced its complexity. Below are the sources of the
example.
Basically, to duplicate the problem, run the main executable and then in
another terminal, simply "telnet 127.0.0.1 60000" to see the
std::terminate() being called, even though there are try/catch blocks in
the main function (main.cpp). Here is an output:
$ ./main
terminate called after throwing an instance of 'int'
Aborted
$
We've also taken the HTTP Server 2 example "as is" and added a "throw
123;" at the top of handle_accept in server.cpp and a "catch (...)"
block in posix_main.cpp. The same issue occurred here, which indicates
that the exceptions are not propagated through all the way (maybe
because the io service runs in a separate thread).
Does anyone have a suggestion on how we can throw exceptions inside the
handler functions of the async functions used? We'd like to handle all
our errors with custom exception classes (e.g. ExceptionAccept is thrown
if there is an error detected in handle_accept, which can then be
handled inside the main function of main.cpp).
We've compiled the sources below by typing:
g++ -I/usr/local/boost/include/boost-1_38 -L/usr/local/boost/lib
-lboost_thread-gcc41-mt-1_38 -lboost_system-gcc41-mt-1_38
io_service_pool.cpp server.cpp main.cpp -o main
where /usr/local/boost is the local boost installation path.
Many thanks in advance!
Kind regards,
Derik Thirion
main.cpp
--------
#include <iostream>
#include <string>
#include
#include
#include
#include
#include "server.hpp"
#include
#include
int main(int argc, char* argv[])
{
try
{
// Block all signals for background thread.
sigset_t new_mask;
sigfillset(&new_mask);
sigset_t old_mask;
pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask);
server s("127.0.0.1", "60000", 2); // 2 threads
boost::thread t(boost::bind(&server::run, &s)); // 2 threads
// Restore previous signals.
pthread_sigmask(SIG_SETMASK, &old_mask, 0);
// Wait for signal indicating time to shut down.
sigset_t wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGINT);
sigaddset(&wait_mask, SIGQUIT);
sigaddset(&wait_mask, SIGTERM);
pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
int sig = 0;
sigwait(&wait_mask, &sig);
s.stop();
t.join();
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
}
catch (...)
{
std::cerr << "all exception" << "\n";
}
return 0;
}
server.hpp
----------
#ifndef SERVER_HPP
#define SERVER_HPP
#include
#include <string>
#include <vector>
#include
#include
#include "io_service_pool.hpp"
/// The top-level class of the HTTP server.
class server
: private boost::noncopyable
{
public:
/// Construct the server to listen on the specified TCP address and
port, and
/// serve up files from the given directory.
explicit server(const std::string& address, const std::string& port,
std::size_t io_service_pool_size);
/// Run the server's io_service loop.
void run();
/// Stop the server.
void stop();
private:
/// Handle completion of an asynchronous accept operation.
void handle_accept(const boost::system::error_code& e);
/// The pool of io_service objects used to perform asynchronous
operations.
io_service_pool io_service_pool_;
/// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor acceptor_;
};
#endif // SERVER_HPP
server.cpp
----------
#include "server.hpp"
#include
server::server(const std::string& address, const std::string& port,
std::size_t io_service_pool_size)
: io_service_pool_(io_service_pool_size),
acceptor_(io_service_pool_.get_io_service())
{
// Open the acceptor with the option to reuse the address (i.e.
SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(acceptor_.io_service());
boost::asio::ip::tcp::resolver::query query(address, port);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
acceptor_.async_accept(*(new
boost::asio::ip::tcp::socket(io_service_pool_.get_io_service())),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
void server::run()
{
io_service_pool_.run();
}
void server::stop()
{
io_service_pool_.stop();
}
void server::handle_accept(const boost::system::error_code& e)
{
throw 123; // this one never gets caught in main
// instead std::terminate() gets called
}
io_service_pool.cpp
-------------------
#include <stdexcept>
#include
#include
#include
#include "io_service_pool.hpp"
io_service_pool::io_service_pool(std::size_t pool_size)
: next_io_service_(0)
{
if (pool_size == 0)
throw std::runtime_error("io_service_pool size is 0");
// Give all the io_services work to do so that their run() functions
will not
// exit until they are explicitly stopped.
for (std::size_t i = 0; i < pool_size; ++i)
{
io_service_ptr io_service(new boost::asio::io_service);
work_ptr work(new boost::asio::io_service::work(*io_service));
io_services_.push_back(io_service);
work_.push_back(work);
}
}
void io_service_pool::run()
{
// Create a pool of threads to run all of the io_services.
std::vectorboost::thread > threads;
for (std::size_t i = 0; i < io_services_.size(); ++i)
{
boost::shared_ptrboost::thread thread(new boost::thread(
boost::bind(&boost::asio::io_service::run, io_services_[i])));
threads.push_back(thread);
}
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
threads[i]->join();
}
void io_service_pool::stop()
{
// Explicitly stop all io_services.
for (std::size_t i = 0; i < io_services_.size(); ++i)
io_services_[i]->stop();
}
boost::asio::io_service& io_service_pool::get_io_service()
{
// Use a round-robin scheme to choose the next io_service to use.
boost::asio::io_service& io_service = *io_services_[next_io_service_];
++next_io_service_;
if (next_io_service_ == io_services_.size())
next_io_service_ = 0;
return io_service;
}
io_service_pool.hpp:
--------------------
#ifndef IO_SERVICE_POOL_HPP
#define IO_SERVICE_POOL_HPP
#include
#include <vector>
#include
#include
/// A pool of io_service objects.
class io_service_pool
: private boost::noncopyable
{
public:
/// Construct the io_service pool.
explicit io_service_pool(std::size_t pool_size);
/// Run all io_service objects in the pool.
void run();
/// Stop all io_service objects in the pool.
void stop();
/// Get an io_service to use.
boost::asio::io_service& get_io_service();
private:
typedef boost::shared_ptrboost::asio::io_service io_service_ptr;
typedef boost::shared_ptrboost::asio::io_service::work work_ptr;
/// The pool of io_services.
std::vector io_services_;
/// The work that keeps the io_services running.
std::vector work_;
/// The next io_service to use for a connection.
std::size_t next_io_service_;
};
#endif // IO_SERVICE_POOL_HPP