multithreading problem in boost asio example - c++

I'm developing a tcp service, and I took an example from boost asio to start (https://www.boost.org/doc/libs/1_73_0/doc/html/boost_asio/example/cpp11/chat/chat_server.cpp), and I'm worried about something, as I understand, any time you want to send something you have to use the deliver function that check the status of and run some operations over the write_msgs_ queue (in my code write_msgs_ is a queue of std::byte based structures):
void deliver(const chat_message& msg)
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
do_write();
}
}
and inside the do_write() function you will see an asynchronous call wrapping a lambda function:
void do_write()
{
auto self(shared_from_this());
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
do_write();
}
}
else
{
room_.leave(shared_from_this());
}
});
}
where the call is constantly sending messages until the queue is empty.
Now, as I understand, the boost::asio::async_write make the lambda function thread safe, but, as the write_msgs_ is open to be used in the deliver function which is out of the isolation given by the io_context a mutex is needed. Now, should I put a mutex each time the write_queue is used or is cheaper to use the boost::asio::post() calling the deliver function to isolate the write_msgs_ from asynchronous calls ?
something like this:
boost::asio::io_service srvc; // this somewhere
void deliver2(const chat_message &msg)
{
srvc.post(std::bind(&chat_session::deliver,this,msg));
}

Related

Boost Beast Websocket: Application Data After Close Notify

I am using boost::beast as well as boost::asio to implement a websocket client with SSL support. My WebsocketClient class has the following members:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<tcp::resolver> resolver;
std::optional<websocket::stream<beast::ssl_stream<beast::tcp_stream>>> ws;
std::promise<void> promise_;
The io_context and ssl_context are constructed and passed by reference from main. The resolver and ws members are initialized via:
resolver.emplace(boost::asio::make_strand(io_context));
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
After calling a WebsocketClient method "run" which triggers a sequence which calls connect, handshake, etc. we enter an async loop. Also of note, we set the value of promise before entering the "on_read" loop. This promise is returned to the caller of run in main which allows the application to progress once the initial WebsocketClient::run call is made.
void WebsocketClient::on_read(
beast::error_code ec,
std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) {
fail(ec, "read");
reconnect(ec);
} else {
// business logic
ws_->async_read(
buffer_,
beast::bind_front_handler(
&WebsocketClient::on_read,
this)
);
}
}
The WebsocketClient works well for several minutes until I reach an exception:
read: application data after close notify
Upon reaching this error, the WebsocketClient proceeds to:
void WebsocketClient::reconnect(beast::error_code ec) {
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
promise_ = std::promise<void>();
resolver_.emplace(boost::asio::make_strand(io_context));
on_disconnect_cb(ec);
}
The function "on_disconnect_cb" is passed in during WebsocketClient initialization as a lambda function. This function simply calls the WebsocketClient::run function again in attempt to reconnect.
To summarize my questions:
Why am I receiving this "read: application data after close notify" error?
Why does the application fail to reconnect after calling WebsocketClient::reconnect?
In the process of debugging I have concluded that the application makes no additional progress after calling:
resolver->async_resolve(
host,
port,
beast::bind_front_handler(
&WebsocketClient::on_resolve,
this)
);
in the WebsocketClient::run function. This fails only in the case of a reconnect and run works as expected on first call. Therefore I expect the issue to be related to incorrectly resetting some of the components of the WebsocketClient.
Edit:
As requested in the comments, I'd like to provide a sketch of the "main" routine. This is indeed a sketch but should cover how the websocket is being used during the program lifecycle:
class App {
public:
App(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context) : {
ws.emplace(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context,
[this]() {
// on_read callback for ws
logic();
},
[this]() {
// on_disconnect callback for ws
on_disconnect();
}
);
}
void run() {
// call underlying websocket connect
}
void logic() {
// do stuff with inbound message
}
void on_disconnect() {
// destroy existing websocket
// If *this contains a value, destroy that value as if by value().T::~T()
ws.reset();
// Initialize new WebsocketClient
ws.emplace(io_context,
ssl_context,
[this](){
logic();
},
[this](){
on_disconnect();
};
);
// call App::run again to restart
run();
}
private:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<WebsocketClient> ws;
};
int main() {
// spinup io_context and detach thread that polls
App app(io_context, ssl_contex);
// triggers WebsocketClient::connect
app.run();
}

Replacing boost::asio::async_read_some with boost::asio::async_read

I was using the code sample provided by boost, the echo server, to try something with one of my client.
However, when I tried to replace the boost::asio::async_read_some with boost::asio::async_read, I noticed that the latter would only be called when the connection would be killed and read_some would be called any time a message would be sent from one of my client. What am I doing wrong?
Here is the original code
https://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
I replaced the content of do_read() with the following:
void do_read()
{
auto self = shared_from_this();
boost::asio::async_read(socket_, data_, [this, self](boost::system::error_code ec, size_t l) {
if (!ec)
do_write(l);
});
}

How to call a handler?

I don't understand how I could return handle in case the io_context was stopped. Minimum example:
void my_class::async_get_one_scan(
std::function<void(const boost::system::error_code& ec,
std::shared_ptr<my_chunked_packet>)> handler)
{
asio::spawn(strand_, [this, handler] (asio::yield_context yield)
{
const auto work = boost::asio::make_work_guard(io_service_);
my_chunk_buffer chunks;
while (!chunks.full()) {
std::array<uint8_t, 1000> datagram;
boost::system::error_code ec;
auto size = socket_.async_receive(asio::buffer(datagram), yield[ec]);
if (!ec)
process_datagram(datagram, size, chunks);
else {
handler(ec, nullptr);
return;
}
}
io_service_.post(std::bind(handler, boost::system::error_code, chunks.packet()));
});
}
Debug asio output:
#asio|1532525798.533266|6*7|strand#01198ff0.dispatch
#asio|1532525798.533266|>7|
#asio|1532525798.533266|>0|
#asio|1532525798.533266|0*8|socket#008e345c.async_receive
#asio|1532525798.533266|<7|
#asio|1532525798.533266|<6|
#asio|1532525799.550640|0|socket#008e34ac.close
#asio|1532525799.550640|0|socket#008e345c.close
#asio|1532525799.551616|~8|
So the last async_receive() #8 is created, after |<6| io_context.stop() is called and then I have no idea how to get the error_code from yield_context to call the handler.
question#2 is it even a correct way of async reading of chunks of data to collect the whole packet?
By definition, io_context::stop prevents the event loop from executing other handlers. So there's no way to get the exit code into the handler, because it doesn't get invoked.
You probably want to have a "soft-stop" function instead, where you stop admitting new async tasks to the io_context and optionally cancel any pending operations.
If pending operations could take too long, you will want to add a deadline timer that forces the cancellation at some threshold time interval.
The usual way to make the run loop exit is by releasing a work object. See https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/io_context__work.html

C++ wait for all async operation end

I have started N same async operations(e.g. N requests to database), so i need to do something after all this operations end. How i can do this? (After one async operation end, my callback will be called).
I use C++14
Example
i use boost.asio to write some data to socket.
for (int i = 0; i < N; ++i)
{
boost::asio::async_write(
m_socket,
boost::asio::buffer(ptr[i], len[i]),
[this, callback](const boost::system::error_code& ec, std::size_t )
{
callback(ec);
});
}
So i need to know when all my writes ends;
first of all, never call async_write in a loop. Each socket may have only one async_write and one async_read outstanding at any one time.
boost already has provision for scatter/gather io.
This snippet should give you enough information to go on.
Notice that async_write can take a vector of vectors as a 'buffer' and it will fire the handler exactly once, once all the buffers have been written.
struct myclass {
boost::asio::ip::tcp::socket m_socket;
std::vector<std::vector<char>> pending_buffers;
std::vector<std::vector<char>> writing_buffers;
void write_all()
{
assert(writing_buffers.size() == 0);
writing_buffers = std::move(pending_buffers);
boost::asio::async_write(
m_socket,
boost::asio::buffer(writing_buffers),
std::bind(&myclass::write_all_handler,
this,
std::placeholders::_1,
std::placeholders::_2));
}
void write_all_handler(const boost::system::error_code& ec, size_t bytes_written)
{
writing_buffers.clear();
// send next load of data
if (pending_buffers.size())
write_all();
// call your callback here
}
};

Correct use of Boost::asio inside of a separate thread

I am writing a DLL plugin for the Orbiter space simulator, which allows for UDP communication with an external system. I've chosen boost::asio for the task, as it allows me to abstract from the low-level stuff.
The "boundary conditions" are as follows:
I can create any threads or call any API functions from my DLL
I can modify the data inside of the simulation only inside the callback passed to my DLL (each frame), due to lack of other thread safety.
Hence, I chose the following architecture for the NetworkClient class I'm using for communications:
Upon construction, it initializes the UDP socket (boost::socket+boost::io_service) and starts a thread, which calls io_service.run()
Incoming messages are put asyncronously into a queue (thread-safe via CriticalSection)
The callback processing function can pull the messages from queue and process it
However, I have run into some strange exception upon running the implementation:
boost::exception_detail::clone_impl > at memory location 0x01ABFA00.
The exception arises in io_service.run() call.
Can anyone point me, please, am I missing something? The code listings for my classes are below.
NetworkClient declaration:
class NetworkClient {
public:
NetworkClient(udp::endpoint server_endpoint);
~NetworkClient();
void Send(shared_ptr<NetworkMessage> message);
inline bool HasMessages() {return incomingMessages.HasMessages();};
inline shared_ptr<NetworkMessage> GetQueuedMessage() {return incomingMessages.GetQueuedMessage();};
private:
// Network send/receive stuff
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint server_endpoint;
udp::endpoint remote_endpoint;
boost::array<char, NetworkBufferSize> recv_buffer;
// Queue for incoming messages
NetworkMessageQueue incomingMessages;
void start_receive();
void handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred);
void handle_send(boost::shared_ptr<std::string> /*message*/, const boost::system::error_code& /*error*/, std::size_t /*bytes_transferred*/) {}
void run_service();
NetworkClient(NetworkClient&); // block default copy constructor
};
Methods implementation:
NetworkClient::NetworkClient(udp::endpoint server_endpoint) : socket(io_service, udp::endpoint(udp::v4(), 28465)) {
this->server_endpoint = server_endpoint;
boost::thread* th = new boost::thread(boost::bind(&NetworkClient::run_service,this));
start_receive();
}
void NetworkClient::start_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), remote_endpoint,
boost::bind(&NetworkClient::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
);
}
void NetworkClient::run_service()
{
this->io_service.run();
}
There's nothing wrong with your architecture that I can see. You should catch exceptions thrown from io_service::run(), that is likely the source of your problem.
void NetworkClient::run_service()
{
while(1) {
try {
this->io_service.run();
} catch( const std::exception& e ) {
std::cerr << e.what << std::endl;
}
}
}
You'll also want to fix whatever is throwing the exception.