0

im trying to connect to a server via boost asio and beast. I need to send heartbeats to the server every 40 seconds, but when I try to, my write requests get stuck in a queue and never get executed, unless the server sends something first.

I have this code to look for new messages that come in.

this->ioContext.run();

thread heartbeatThread(&client::heartbeatCycle, this);

while (this->p->is_socket_open()) {
    this->ioContext.restart();
    this->p->asyncQueue("", true);
    this->ioContext.run();
}

The asyncQueue function just calls async_read, and blocks the io context. The heartbeatCycle tries to send heartbeats, but gets stuck in the queue. If I force it to send anyways, I get

Assertion failed: (id_ != T::id), function try_lock, file soft_mutex.hpp, line 89.

When the server sends a message, the queue is unblocked, and all the queued messages go through, until there is no more work, and the io_context starts blocking again.

So my main question is, is there any way to unblock the io context without having the server send a message? If not, is there a way to emulate the server sending a message?

Thanks!

EDIT:

I have this queue function that queues messages being sent called asyncQueue.

void session::asyncQueue(const string& payload, const bool& madeAfterLoop)
{
    if(!payload.empty())
    {
        queue_.emplace_back(payload);
    }

    if(payload.empty() && madeAfterLoop)
    {
        queue_.emplace_back("KEEPALIVE");
    }

    // If there is something to write, write it.
    if(!currentlyQueued_ && !queue_.empty() && queue_.at(0) != "KEEPALIVE")
    {
        currentlyQueued_ = true;
        ws_.async_write(
                net::buffer(queue_.at(0)),
                beast::bind_front_handler(
                        &session::on_write,
                        shared_from_this()));
        queue_.erase(queue_.begin());
    }

    // If there is nothing to write, read the buffer to keep stream alive
    if(!currentlyQueued_ && !queue_.empty())
    {
        currentlyQueued_ = true;
        ws_.async_read(
                buffer_,
                beast::bind_front_handler(
                        &session::on_read,
                        shared_from_this()));
        queue_.erase(queue_.begin());
    }
}

The problem is when the code has nothing no work left to do, it calls async read, and gets stuck until the server sends something.

In the function where I initialized the io_context, I also created a separate thread to send heartbeats every x seconds.

void client::heartbeatCycle()
{
    while(this->p->is_socket_open())
    {
        this->p->asyncQueue(bot::websocket::sendEvents::getHeartbeatEvent(cache_), true );
        this_thread::sleep_for(chrono::milliseconds(10000));
    }
}

Lastly, I have these 2 lines in my on_read function that runs whenever async read is called.

currentlyQueued_ = false;
asyncQueue();

Once there is no more work to do, the program calls async_read but currentlyQueued_ is never set to false.

The problem is the io_context is stuck looking for something to read. What can I do to stop the io_context from blocking the heartbeats from sending?

The only thing I have found that stops the io_context from blocking is when the server sends me a message. When it does, currentlyQueued_ is set to false, and the queue able to run and the queue is cleared.

That is the reason im looking for something that can emulate the server sending me a message. So is there a function that can do that in asio/beast? Or am I going about this the wrong way. Thanks so much for your help.

soey_sause
  • 23
  • 5
  • 1
    You say "I have this code to look for new messages that come in" but none of that code apparently even reads. Please make your code minimal and self-contained, so we can see what you need. – sehe Dec 08 '22 at 00:28
  • Oh, ok. I updated the message to give details and clarify what is wrong with the program. Thanks for the tips! – soey_sause Dec 08 '22 at 02:42
  • 1
    Oof. There's still so much we can't tell. What is the type of `queue_` exactly? Right now it seems very very [UB](https://en.wikipedia.org/wiki/Undefined_behavior) to `erase` from it immediately after initiating the `async_write` on `queue_.at(0)`. The many bespoke state variables, both locally, member *and* even passed as argument seem a recipe for confusion. Are you looking for a [simpler outgoing queue pattern](https://stackoverflow.com/search?tab=newest&q=user%3a85371%20%5bboost-asio%5d%20outbox_%20)? – sehe Dec 08 '22 at 03:09
  • 1
    Note that in principle full-duplex means that both 1 `async_read` and 1 `async_write` can be in flight at the same time (for the same IO object). It looks a bit as if you are trying to conditionally schedule a read. The usual pattern is to *always* have a read pending, and initiate write as soon as _the first_ outgoing message is enqueued. [Optionally, have timers to enqueue heartbeats or cancel on idle timeout; All of these without any need for extra threads] – sehe Dec 08 '22 at 03:11
  • Hmm, I think i need to redo the queue! If I try to `async_write` while an async_read is pending I get the error `Assertion failed: (id_ != T::id), function try_lock, file soft_mutex.hpp, line 89.`. My write function calls `async_read` right after writing. Could it be giving this error because as soon as I run the function to write , it tries to read twice? – soey_sause Dec 08 '22 at 05:08
  • 1
    omg that was it!!! Thanks sooo much!! i had no idea that websockets were full-duplex, that makes so much sense. – soey_sause Dec 08 '22 at 05:20

1 Answers1

1

The idea is to run the io_service elsewhere (on a thread, or in main, after starting an async chain).

Right now you're calling restart() on it which simply doesn't afford continuous operation. Why stop() or let it run out of work at all?

Note, manually starting threads is atypical and unsafe.

I would give examples, but lots already exist (also on this site). I'd need to see question code with more detail to give concrete suggestions.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I tried to let it never run out of work by calling `ws_.async_read` when there was nothing else to read, but I got the same result of the program waiting for something to read before doing anything else. I updated my post to give specific details about what the problem is. Thanks for the help! – soey_sause Dec 08 '22 at 02:52