I use UDP client-server for IPC in my application
It works fine, but when trying to shutdown the client side some race condition happen, which causes application crash or deadlock.
UDP client:
// IOServiceBase class contain boost::asio::io_service instance
// it is accessible by service() protected method
class AsyncUDPClient : public IOServiceBase
{
public:
/// @brief Create a network client
AsyncUDPClient(const std::string& host, const std::string port)
: _host(host)
, _port(port)
, _reply()
, _work(service())
, _sock(service(), ba_ip::udp::endpoint(ba_ip::udp::v4(), 0)) {
run();
}
/// @brief Start async packets processing
void run(){
std::thread t([&]{ service().run(); });
t.detach();
}
/// @brief Async request to server
void send(uint8_t* message, size_t length) {
std::vector<uint8_t> packet(message, message + length);
service().post(boost::bind(&AsyncUDPClient::do_send, this, packet));
}
/// @brief Cleanup io_service to dismiss already putted tasks
~AsyncUDPClient() {
close();
// trying to wait until service is stopped, but it does not help
while (!service().stopped()){
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
/// @brief Cleanup io_service to dismiss already putted tasks
void close(){
std::thread t([&]{ service().stop(); });
t.join();
}
protected:
// send-response methods are pretty standard
private:
std::string _host;
std::string _port;
std::array<uint8_t, max_length> _reply;
ba::io_service::work _work;
ba_ip::udp::socket _sock;
};
Usage example:
{
AsyncUDPClient ipc(addr, port);
ipc.send(&archive_data[0], archive_data.size());
// it seems client is destroyed before some internal processing is finished?
}
Behaviour is not deterministic, sometimes works fine, sometimes crashes, sometimes freeze. Stacktrace shows the crash point somewhere in boost.asio internals
The destruction of AsyncUDPClient
does not properly synchronize with the thread(s) running the io_service
. This can result in undefined behavior being invoked when a thread processing the io_service
attempts to interact with the AsyncUDPClient
and its io_service
after their lifetime has ended.
To resolve this, do not detach from the threads processing the io_service
, and explicitly join the thread(s) once the io_service
has been stopped.
class AsyncUDPClient
: public IOServiceBase
{
public:
// ...
void run()
{
_threads.emplace_back([&]{ service().run(); })
}
// ...
~AsyncUDPClient()
{
close();
}
void close()
{
// Stop the io_service. This changes its state and return immediately.
service().stop();
// Explicitly synchronize with threads running the io_service.
for (auto& thread: _threads)
{
thread.join();
}
}
private:
// ...
std::vector<std::thread> _threads;
};
As hinted in the comments above, io_service::stop()
does not block. It changes the state of io_service
to stopped, returns immediately, and causes all invocations of run()
and run_one()
to return as soon as possible. Calls to io_service::stopped()
immediately return the state of the io_service
. Neither of these calls indicate if there are threads curretly within the invocation of run()
or run_one()
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.