Search code examples
c++linuxwindowssignalsboost-asio

Why doesn't boost::asio::ip::udp::socket::receive_from throw interruption exception in Windows?


volatile std::sig_atomic_t running = true;

int main()
{
  boost::asio::thread_pool tpool;
  boost::asio::signal_set signals(tpool, SIGINT, SIGTERM);
  signals.async_wait([](auto && err, int) { if (!err) running = false; });

  while(running)
  {
    std::array<std::uint8_t, 1024> data;
    socket.recieve_from(boost::asio::buffer(data), ....); // (1)
    // calc(data);
  }
  return 0;
}

If my code is blocked in the (1) line in Linux and I try raise the signal, for example, with htop then the line (1) throws exception about the interruption but in Windows it doesn't. The problem in what I don't know how to exit the application.

What needs to do my program works equally in both OSs? Thanks.

Use Windows 10 (msvc 17), Debian 11 (gcc-9), Boost 1.78.


Solution

  • Regardless of the question how you "raise the signal" on Windows there's the basic problem that you're relying on OS specifics to cancel a synchronous operation.

    Cancellation is an ASIO feature, but only for asynchronous operations. So, consider:

    signals.async_wait([&socket](auto&& err, int) {
        if (!err) {
            socket.cancel();
        }
    });
    

    Simplifying without a thread_pool gives e.g.:

    Live On Coliru

    #define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
    #include <boost/asio.hpp>
    namespace asio = boost::asio;
    using asio::ip::udp;
    using boost::system::error_code;
    
    struct Program {
        Program(asio::any_io_executor executor)
            : signals_{executor, SIGINT, SIGTERM}
            , socket_{executor} //
        {
            signals_.async_wait([this](error_code ec, int) {
                if (!ec) {
                    socket_.cancel();
                }
            });
    
            socket_.open(udp::v4());
            socket_.bind({{}, 4444});
            receive_loop();
        }
    
      private:
        asio::signal_set signals_;
        udp::socket      socket_;
    
        std::array<std::uint8_t, 1024> data_;
        udp::endpoint                  ep_;
    
        void receive_loop() {
            socket_.async_receive_from( //
                asio::buffer(data_), ep_, [this](error_code ec, size_t) {
                    if (!ec)
                        receive_loop();
                });
        }
    };
    
    int main() {
        asio::io_context ioc;
        Program app(ioc.get_executor());
    
        using namespace std::chrono_literals;
        ioc.run_for(10s); // for COLIRU
    }
    

    Prints (on coliru):

    @asio|1663593973.457548|0*1|signal_set@0x7ffe0b639998.async_wait
    @asio|1663593973.457687|0*2|socket@0x7ffe0b6399f0.async_receive_from
    @asio|1663593973.457700|.2|non_blocking_recvfrom,ec=system:11,bytes_transferred=0
    @asio|1663593974.467205|.2|non_blocking_recvfrom,ec=system:0,bytes_transferred=13
    @asio|1663593974.467252|>2|ec=system:0,bytes_transferred=13
    @asio|1663593974.467265|2*3|socket@0x7ffe0b6399f0.async_receive_from
    @asio|1663593974.467279|.3|non_blocking_recvfrom,ec=system:11,bytes_transferred=0
    @asio|1663593974.467291|<2|
    @asio|1663593975.481800|.3|non_blocking_recvfrom,ec=system:0,bytes_transferred=13
    @asio|1663593975.481842|>3|ec=system:0,bytes_transferred=13
    @asio|1663593975.481854|3*4|socket@0x7ffe0b6399f0.async_receive_from
    @asio|1663593975.481868|.4|non_blocking_recvfrom,ec=system:11,bytes_transferred=0
    @asio|1663593975.481878|<3|
    @asio|1663593976.494097|.4|non_blocking_recvfrom,ec=system:0,bytes_transferred=13
    @asio|1663593976.494138|>4|ec=system:0,bytes_transferred=13
    @asio|1663593976.494150|4*5|socket@0x7ffe0b6399f0.async_receive_from
    @asio|1663593976.494164|.5|non_blocking_recvfrom,ec=system:11,bytes_transferred=0
    @asio|1663593976.494176|<4|
    @asio|1663593976.495085|>1|ec=system:0,signal_number=2
    @asio|1663593976.495119|1|socket@0x7ffe0b6399f0.cancel
    @asio|1663593976.495129|<1|
    @asio|1663593976.495151|>5|ec=system:125,bytes_transferred=0
    @asio|1663593976.495162|<5|
    @asio|1663593976.495184|0|socket@0x7ffe0b6399f0.close
    @asio|1663593976.495244|0|signal_set@0x7ffe0b639998.cancel
    

    So that's 3 successful receives, followed by a signal 2 (INT) and cancellation which results in ec=125 (asio::error:operation_aborted) and shutdown.

    enter image description here

    Multi-threading

    There's likely no gain for using multiple threads, but if you do, use a strand to synchronize access to the IO objects:

    asio::thread_pool ioc;
    Program app(make_strand(ioc));