c++asynchronousboostudpboost-asio

Boost asio, timer inside async_receive_from wont trigger


I am trying to write a small UDP proxy using Boost asio. Within it I need to read a socket, and if that socket receives a datagram, a callback should be triggered after a specific time, forwarding the datagram. It should also immediately be ready to receive a new datagram.

My issue is that my code works when it is not inside a class, but as soon as I try to make a class of it, the code stops working. I am quite sure I am missing something crucial as I am very new to async boost.

The code that does work looks like this, and I get the output that the timer hits as expected.

#include <iostream>
#include <boost/asio.hpp>

void receive(udp::socket &socket, io_context &ioContext) {
    udp::endpoint receive_endpoint;
    std::array<char, 8192> buf;
    socket.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
                              [&](const boost::system::error_code& error, std::size_t bytesReceived) {
        if (!error && bytesReceived > 0) {
            std::cout << "Got a package" << std::endl;
            boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100));
            timer.async_wait([&](const boost::system::error_code& ec) {
                std::cout << "Timer hit" << std::endl;
            });
            receive(socket, ioContext);
        } else {
            std::cout << "Error receiving data from client" << std::endl;
        }
    });
}

int main() {
    boost::asio::io_context ioContext;
    udp::socket socket(ioContext);
    socket.open(udp::v4());
    udp::endpoint localEndpoint(ip::address::from_string("127.0.0.1"), 12345);
    socket.bind(localEndpoint);
    receive(socket, ioContext);

    ioContext.run();

    return 0;
}

When I then make a class of it, the timer never hits. My code for the main file when using the class looks like this:

#include "udp_class.h"
#include <iostream>
#include <boost/asio.hpp>

int main() {
    boost::asio::io_context ioContext;
    Proxy my_proxy(ioContext);
    my_proxy.start();
    ioContext.run();

    return 0;
}

with the class definition as

#pragma once

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <iostream>
#include <boost/asio.hpp>
#include <chrono>

using namespace boost::asio;
using ip::udp;
using std::chrono::milliseconds;

class Proxy {
public:
    Proxy(io_context& ioContext): localSocket_(ioContext) { };
    void start() {
        localSocket_.open(udp::v4());
        localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345));
        receive();
    };

private:
    void receive() {
        udp::endpoint receive_endpoint;
        std::array<char, 8192> buf;
        localSocket_.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
                [&](const boost::system::error_code& error, std::size_t bytesReceived) {
              if (!error && bytesReceived > 0) {
                  std::cout << "Got a package" << std::endl;
                  boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100));
                  timer.async_wait([&](const boost::system::error_code& ec) {
                      std::cout << "Timer hit" << std::endl;
                  });
                  receive();
              } else {
                  std::cout << "Error receiving data from client" << std::endl;
              }
          });
    }

    io_context ioContext;
    udp::socket localSocket_;
};

This receives a datagram, notes that to the console but the timer never hits.

The output from the boost asio debug looks as follows:

@asio|1720035449.638484|0*1|socket@0x16b0ef4f8.async_receive_from
@asio|1720035449.638495|.1|non_blocking_recvfrom,ec=system:35,bytes_transferred=0
@asio|1720035451.692791|.1|non_blocking_recvfrom,ec=system:0,bytes_transferred=12
@asio|1720035451.692827|>1|ec=system:0,bytes_transferred=12
@asio|1720035451.692951|1*2|deadline_timer@0x16b0ef038.async_wait
@asio|1720035451.692976|1*3|socket@0x16b0ef4f8.async_receive_from
@asio|1720035451.692987|.3|non_blocking_recvfrom,ec=system:35,bytes_transferred=0
@asio|1720035451.692992|1|deadline_timer@0x16b0ef038.cancel
@asio|1720035451.692998|<1|

I have been stuck on this and similar issues now for a few days, and cant really wrap my head around what I am doing wrong. I took my whole code and distilled down this example which hopefully represents what the issue is as concisely as possible.


Solution

  • You're creating a new io_context inside the Proxy class, but using the one passed to the constructor for the socket. This mismatch is causing problems.

    fixed class:

    #pragma once
    
    #define BOOST_ASIO_ENABLE_HANDLER_TRACKING
    #include <iostream>
    #include <boost/asio.hpp>
    #include <chrono>
    
    using namespace boost::asio;
    using ip::udp;
    using std::chrono::milliseconds;
    
    class Proxy {
    public:
        Proxy(io_context& io): ioContext(io), localSocket_(io) { };
        void start() {
            localSocket_.open(udp::v4());
            localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345));
            receive();
        };
    
    private:
        void receive() {
            udp::endpoint receive_endpoint;
            std::array<char, 8192> buf;
            localSocket_.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
                    [this](const boost::system::error_code& error, std::size_t bytesReceived) {
                  if (!error && bytesReceived > 0) {
                      std::cout << "Got a package" << std::endl;
                      auto timer = std::make_shared<boost::asio::steady_timer>(ioContext, boost::asio::chrono::milliseconds(100));
                      timer->async_wait([timer, this](const boost::system::error_code& ec) {
                          std::cout << "Timer hit" << std::endl;
                      });
                      receive();
                  } else {
                      std::cout << "Error receiving data from client" << std::endl;
                  }
              });
        }
    
        io_context& ioContext;
        udp::socket localSocket_;
    };
    

    and main:

    int main() {
        boost::asio::io_context ioContext;
        Proxy my_proxy(ioContext);
        my_proxy.start();
        ioContext.run();
    
        return 0;
    }