Boost-Asio io_context post after run - c++

I am new to Boost::asio and I am currently looking at io_context.
In the docs https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/io_context.html shown is the following example:
{
...
}
...
boost::asio::io_context io_context;
// Submit a function to the io_context.
boost::asio::post(io_context, my_task);
// Submit a lambda object to the io_context.
boost::asio::post(io_context,
[]()
{
...
});
// Run the io_context until it runs out of work.
io_context.run();
However, I would like to be able to post even after io_context.run() has been called.
Essentially, something like this:
#include <boost/asio.hpp>
int value = -1;
void my_task()
{
value = 42;
}
int main() {
boost::asio::io_context io_context;
// Submit a function to the io_context.
//boost::asio::post(io_context, my_task);
// Run the io_context until it runs out of work.
io_context.run();
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work(io_context.get_executor());
// Submit a lambda object to the io_context.
boost::asio::post(io_context,
[]()
{
my_task();
});
assert(value == 42);
}
After compiling the above with g++ -o a example.cpp -lboost_system -lpthread I am getting an assertion failure. What is the "right" way to accomplish this?

Either run the io_context on a separate thread, or, indeed use the execution context that already has that built in.
Using a manual thread
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
int value = -1;
void my_task() { value = 42; }
int main() {
boost::asio::io_context io_context;
auto work = make_work_guard(io_context);
std::thread thread([&] { io_context.run(); });
// submit to the io_context
post(io_context, my_task);
work.reset();
thread.join();
std::cout << value << "\n";
}
Prints
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
42
Using asio::thread_pool:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
int value = -1;
void my_task() { value = 42; }
int main() {
boost::asio::thread_pool io(1);
post(io, my_task);
io.join();
std::cout << value << "\n";
}
As you can see it's basically the same, but
less code
less work (literally)
more correct (see e.g. Should the exception thrown by boost::asio::io_service::run() be caught?)
trivial to make multi-threaded thread_pool io(16); or just using the default thread_pool io;

io_context::run() blocks as long as there is work to do. In your first example, you could just wait until both the posted tasks are finished. After that, io_context::run() returns and you can submit new work and call run() again. If you don't want this sequential behavior but rather submit tasks while the io_context works on tasks, you need a second thread, that executes io_conext::run(). You can create one for example like this:
#include <thread>
//...
std::thread my_thread( [&](){ io_context.run(); } );
// post more work here
// wait for it to finish
my_thread.join();
Make sure that the io_context object lives longer than the thread, if you use the lamda capture by reference like in this example.

Related

boost::process async IO example doesn't work?

The following program:
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
int main() {
boost::asio::io_service ios;
std::vector<char> buf(4096);
bp::async_pipe ap(ios);
bp::child c("/bin/ls", bp::std_out > ap);
boost::asio::async_read(ap, boost::asio::buffer(buf),
[](const boost::system::error_code &ec, std::size_t size){});
ios.run();
int result = c.exit_code();
std::cout << result << std::endl;
}
outputs 383. I would expect it to output 0.
This is very nearly a copy-and-paste of the example from:
https://www.boost.org/doc/libs/1_71_0/doc/html/boost_process/tutorial.html#boost_process.tutorial.async_io
There may be a few issues here. Firstly, I think the comment in the documentation...
Passing an instance of boost::asio::io_service to the launching
function automatically cause it to wait asynchronously for the exit,
so no call of wait is needed
...refers to the example after the code you've shown. Specifically...
boost::asio::io_service ios;
std::vector<char> buf(4096);
bp::child c(bp::search_path("g++"), "main.cpp", bp::std_out > boost::asio::buffer(buf), ios);
ios.run();
int result = c.exit_code();
Where the io_service is passed by reference to the child ctor.
The comment is also slightly misleading. While it's true that the subsequent call to ios.run() does wait asynchronously for the exit it also appears (boost 1.71.0) that the exit code is not fixed up as one might hope. The exit code is stored within the child class as...
std::shared_ptr<std::atomic<int>> _exit_status;
From a quick scan of the source code it seems _exit_status->store(...) is only invoked from the following members...
boost::process::child::running
boost::process::child::wait
boost::process::child::wait_until
So, even though the process has exited (assuming all went well) when ios.run() returns one or more of running, wait or wait_until must be called to make the exit code available.
As commented elsewhere by #sehe this looks like it's possibly a regression. If I can find a bug report I'll update this. In the meantime the workaround is to simply call c.wait() before c.exit_code().

Boost::Asio - Passing socket to second class

I am currently trying to get the following application to work:
Await incoming client connection.
Start async. timer in another class.
While the timer runs repeatedly, do other stuff such as async_read and async_write.
Current source code:
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <WinSock2.h>
#include <Mswsock.h>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include "TimerClass.hpp"
using namespace boost::asio;
using namespace boost::asio::ip;
TimerClass *timerClass;
void acceptHandler(const boost::system::error_code &errorCode, tcp::socket *socket) {
timerClass = new TimerClass(socket);
timerClass->startTimer();
while(true) {
// Do other suff such as async_write, ...
}
}
int main(int argc, char** argv) {
io_service ioService;
tcp::socket socket(ioService);
tcp::acceptor acceptor{ ioService, tcp::endpoint{ tcp::v4(), 12345 } };
acceptor.listen();
acceptor.async_accept(socket, boost::bind(acceptHandler, _1, &socket));
ioService.run();
return EXIT_SUCCESS;
}
TimerClass.hpp:
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::asio;
using namespace boost::posix_time;
class TimerClass {
public:
TimerClass(ip::tcp::socket *socket);
void startTimer();
void timerHandler(const boost::system::error_code& errorCode);
deadline_timer timer;
};
TimerClass.cpp:
#include <boost/bind.hpp>
#include "TimerClass.hpp"
TimerClass::TimerClass(ip::tcp::socket *socket) : timer(socket->get_io_service(), boost::posix_time::seconds(1)) {}
void TimerClass::startTimer() {
timer.async_wait(boost::bind(&TimerClass::timerHandler, this, boost::asio::placeholders::error));
}
void TimerClass::timerHandler(const boost::system::error_code& errorCode) {
timer.expires_at(timer.expires_at() + boost::posix_time::seconds(1));
timer.async_wait(boost::bind(&TimerClass::timerHandler, this, boost::asio::placeholders::error));
}
Handler Tracking Output:
#asio|1461070492.111630|0*1|socket#000000000021FBD0.async_accept
#asio|1461070498.527997|>1|ec=system:0
Questions:
Why won't it even call async_wait in startTimer? Debugging shows that startTimer gets called but I can't find anything in the Handler Tracking output. Why is that?
Am I correctly passing the socket to the TimerClass?
Without the infinite while(true) loop in the acceptHandler the acceptHandler returns but the application crashes before the io_service properly returns. How is that?
I compiled your code and it works for me (using boost version 1.54).
With your code I get the following output:
#asio|1461081908.437388|0*1|socket#003BFE2C.async_accept
#asio|1461081983.220840|>1|ec=system:0
#asio|1461081983.221817|1*2|deadline_timer#001C1318.async_wait
To make it run properly I had to remove the while(true) on your acceptHandler, obtaining the following output (added a std::cout inside the handler):
#asio|1461083707.104424|0*1|socket#0030FB6C.async_accept
#asio|1461083709.061824|>1|ec=system:0
#asio|1461083709.062803|1*2|deadline_timer#00641318.async_wait
#asio|1461083709.062803|<28158494073611763|
#asio|1461083710.064992|>2|ec=system:0
#asio|1461083710.064992|2|deadline_timer#00641318.cancel
#asio|1461083710.064992|2*3|deadline_timer#00641318.async_wait
TimerHandler executed...
#asio|1461083710.065971|<28169626628843099|
#asio|1461083711.065223|>3|ec=system:0
#asio|1461083711.065223|3|deadline_timer#00641318.cancel
#asio|1461083711.065223|3*4|deadline_timer#00641318.async_wait
TimerHandler executed...
I actually did this test using only the header TimerClass.hpp (defining the methods directly within it -I was lazy-) and it worked like a charm, the problem seems to be when using the .cpp file, that's why I asked if you were using include guards (not the issue though, already tested).
You should consider changing your design approach though, i.e. do not use blocking loops in you handlers, just call another asynchronous operation if needed (like async_read or async_write).
Take a look at this question and corresponding accepted answer for a nice server implementation idea. Or try to adapt some of the boost examples to your needs.
As per the segmentation fault you get when separating declaration from definition in the corresponding header and implementation files, you might want to check this other question.

Stop boost::io_service after certain amount of time

I have a boost::asio::io_service which is doing some work. Now I want to stop this service after a certain amount of time. My first approach was by using boost::thread(io_service.run()), but then I get errors.
Are there other ways to stop io_service?
Thank you!
You can use a deadline_timer.
You can also run the service on another thread like you tried:
boost::thread t = boost::thread(boost::bind(&boost::asio::io_service::run, boost::ref(io_service));
// sometime
io_service.stop(); // io_service is threadsafe
t.join();
Here's a deadline_timer example complete in C++03: Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <iostream>
using namespace boost::asio;
using namespace boost;
io_service svc;
deadline_timer timer(svc);
void work()
{
this_thread::sleep_for(chrono::milliseconds(100));
std::cout << "Work done, rescheduling\n";
svc.post(work);
}
void expiration_handler(system::error_code ec)
{
if (ec != error::operation_aborted)
svc.stop();
}
int main()
{
svc.post(work);
timer.expires_from_now(posix_time::seconds(2));
timer.async_wait(expiration_handler);
svc.run();
}
This prints
Work done, rescheduling
until the deadline is reached after 2 seconds
std::this_thread::sleep_for(std::chrono::seconds(10));
io_service.stop();
Isn't it?
Use deadline_timer.
boost::asio::deadline_timer stop_timer(io_service);
...
// If require stopping
stop_timer.expires_from_now(boost::posix_time::seconds(10));
stop_timer.async_wait(
[&io_service](const boost::system::error_code &ec)
{
io_service.stop();
});

ThreadPool with boost::asio does not quit?

I have the following minmal example of a thread pool made with boost::asio.
#include <queue>
#include <map>
#include <boost/shared_ptr.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp> // remove me (only for io)
class ThreadPool
{
public:
void work_as_mainthread(void) { m_io_service.run(); }
ThreadPool(int poolSize = 4) : timer(m_io_service)
{
timer.expires_from_now(boost::posix_time::seconds(1)); // this line does not affect the problem
m_pWork.reset( new boost::asio::io_service::work(m_io_service) );
for ( int i = 0; i < poolSize; ++i)
m_threadGroup.create_thread( boost::bind(&boost::asio::io_service::run, &m_io_service) );
}
~ThreadPool()
{
m_pWork.reset();
m_threadGroup.join_all();
}
private:
boost::asio::io_service m_io_service;
boost::asio::deadline_timer timer;
boost::shared_ptr<boost::asio::io_service::work> m_pWork;
boost::thread_group m_threadGroup;
};
int main()
{
int n_threads = 2;
ThreadPool pool(n_threads);
pool.work_as_mainthread();
// this line is never reached...
return 0;
}
If you like, you can compile it like this:
g++ -Wall -g -lboost_thread -lboost_date_time -lboost_system main.cpp -o main
What makes me wonder is that the program does not stop. What I do is calling io_service::run, but without any "work" for it. io_services without work quit themselves, as said in the boost::asio docs. Now, why does my program never quit?
When you create a boost::asio::io_service::work object, that keeps the io_service from completing.
// This line keeps the io_service running
m_pWork.reset( new boost::asio::io_service::work(m_io_service) );
If you want it to stop, you would need to destroy that work object, like this:
// stop the worker(s)
m_pWork.reset();
It's up to you to find an appropriate time/place to do this. I would suggest calling timer.async_wait(), then in the handler you can reset your work object to see how this all should be working together.
See this portion of the documentation.

Async Resolve with Boost.Asio

I'm trying to asynchronously resolve a ftp host using Boost.Asio.
Here's what I've tried so far:
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class FtpSession {
public:
void Connect(std::string& host) {
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(host, "ftp");
resolver.async_resolve(query,
boost::bind(&FtpSession::OnResolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
private:
void OnResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator) {
if (!err)
{
std::cout << "resolved!";
}
else
{
std::cout << "error.";
}
}
};
int main() {
FtpSession session;
std::string host("ftp.remotesensing.org");
session.Connect(host);
return 0;
}
But for some reason, when I execute it, it just doesn't print anything:
alon#alon-GA-73PVM-S2H:~/Desktop$ g++ -o test -lboost_system test.cc
alon#alon-GA-73PVM-S2H:~/Desktop$ ./test
alon#alon-GA-73PVM-S2H:~/Desktop$
No errors or warnings at the compilation though.
How can I fix this?
You need to call io_service.run() to actually do the work methinks. Think of the async_resolve as a request in a request queue - you need something (the io_service) to process the requests in the queue, and to do that, you actually need to run() it! In this case, it will see one request, execute it, call the handler and then exit.
Your io_service and ip::tcp::resolver object are going out of scope. Move both of them into members of the FtpSession class, then invoke io_service::run inside of main after session.Connect(host) to start the event loop.
I answered a similar question a few days ago that may help you as well.