Reference:
https://www.boost.org/doc/libs/1_78_0/libs/beast/example/websocket/client/async/websocket_client_async.cpp
https://www.boost.org/doc/libs/1_78_0/libs/beast/doc/html/beast/using_io/timeouts.html
https://www.boost.org/doc/libs/1_78_0/libs/beast/doc/html/beast/ref/boost__beast__tcp_stream.html
void on_resolve(beast::error_code ec, tcp::resolver::results_type results)
{
if(ec) return fail(ec, "resolve");
// Set the timeout for the operation
beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
// Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(ws_).async_connect(
results, beast::bind_front_handler(
&session::on_connect, shared_from_this()));
}
void on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep)
{
if(ec) return fail(ec, "connect");
// Turn off the timeout on the tcp_stream, because
// the websocket stream has its own timeout system.
// beast::get_lowest_layer(ws_).expires_never(); // Note: do NOT call this line for this question!!!
...
host_ += ':' + std::to_string(ep.port());
// Perform the websocket handshake
ws_.async_handshake(host_, "/",
beast::bind_front_handler(&session::on_handshake, shared_from_this()));
}
Question 1>
Will the timeout of beast::tcp_stream continue to work after a previous asynchronous operation finishes on time?
For example,
In above example, the timeout will expire after 30 seconds. If async_connect doesn't finish within 30 seconds, session::on_connect will receive an error::timeout as the value of ec. Let's assume the async_connect takes 10 seconds,
can I assume that async_handshake needs to finish within 20(i.e. 30-10) seconds otherwise a error::timeout will be sent to session::on_handshake? I infer to this idea based on the comments within on_connect function(i.e.
Turn off the timeout on the tcp_stream
). In other words, a timeout will only be turned off after it finishes the specified expiration period or is disabled by expires_never. Is my understanding correct?
Question 2> Also I want to know what a good pattern I should use for timeout in both async_calling and async_callback functions.
When we call an async_calling operation:
void func_async_calling()
{
// set some timeout here(i.e. XXXX seconds)
Step 1> beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(XXXX));
Step 2> ws_.async_operation(..., func_async_callback, )
Step 3> beast::get_lowest_layer(ws_).expires_never();
}
When we define a async_callback handle for an asynchronous operation:
void func_async_callback()
{
Step 1>Either call
// Disable the timeout for the next logical operation.
beast::get_lowest_layer(ws_).expires_never();
or
// Enable a new timeout
beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(YYYY));
Step 2> call another asynchronous function
Step 3> beast::get_lowest_layer(ws_).expires_never();
}
Does this make sense?
Thank you
Question 1
Yes that's correct. The linked page has the confirmation:
// The timer is still running. If we don't want the next
// operation to time out 30 seconds relative to the previous
// call to `expires_after`, we need to turn it off before
// starting another asynchronous operation.
stream.expires_never();
Question 2
That looks fine. The only subtleties I can think of are
often, because of Thread Safety often the initiation as well as the completion happen on the same (implicit) strand.
If that's the case, then in your completion handler example, the expires_never(); would be redundant.
If the completion handler is not on the same strand, you want to actively avoid touching the expiry, because that would be a data race
An alternative pattern is to set the expiry only once for a lengthier episode (e.g. an multi-message conversation between client/server). Obviously in this pattern, nobody would touch the expiry after initial setting. This seems pretty obvious, but I thought I'd mention it before someone casts this pattern in stone to never think about it again.
Always do what you need, prefer simple code. I think your basic understanding of the feature is right. (No wonder, this documentation is a piece of art).
Related
My goal is to register to a websocket service to get real-time company quotations.
So I based my code on the following example, by mostly calling (again) async_read, once we receive a quotation to accept futures quotation:
https://www.boost.org/doc/libs/master/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp
The problem is when I am waiting for a new quotation (who could take sometimes minutes or hours for small companies), the program is blocked waiting for a message and I do not have the opportunity to ask for another company.
I tried to use the "post" function to call again async_write in the good context thread but the program crashed.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message?
Here is the function I modified (simplified without mutexes):
void
on_read(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail2(ec, "read");
std::string mycontent = beast::buffers_to_string(buffer_.data());
cout << mycontent << endl;
buffer_.clear();
ws_.async_read(
buffer_,
beast::bind_front_handler(
&session::on_read,
shared_from_this()));
}
void subscribe(const std::string &symbol)
{
// We save the message in the queue
std::string text = "{\"action\": \"subscribe\", \"symbols\": \"" + symbol + "\"}";
msgqueue_.push_back(text);
boost::asio::post(ioc_, beast::bind_front_handler(&session::_subscription_to_post, shared_from_this()));
}
void _subscription_to_post()
{
if (msgqueue_.empty())
return;
// We send the message
ws_.async_write(
net::buffer(msgqueue_.front()),
beast::bind_front_handler(
&session::on_write,
shared_from_this()));
msgqueue_.pop_front();
}
And the program crashes immediately when trying to async_write.
The problem is when I am waiting for a new quotation [...] the program is blocked waiting for a message
It isn't technically blocked because you are using async_read
I tried to use the "post" function to call again async_write in the good context thread, but the program crashed.
That means you're doing something wrong. You can post a question with your self-contained minimal code, and we can tell you what is wrong.
In general, you can use a single read operation and a single write operation concurrently (as in: in flight, asynchronously, you still need to synchronize threads accessing all related resources).
Typically, you have a single async-read-chain active at all times, and a outbound message-queue that is drained by a single async-write chain (that obviously ends when the queue is empty, so needs to be initiated when the first outbound message is queued).
I have many answers on this site (literally dozens) that you may be able to find by search for outbox or outbox_ e.g. Keep in mind that the majority of them will deal with plain (ssl) sockets intead of websockets, but the pattern practically the same.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message ?
You can technically cancel() it, which completes it with operation_aborted. But that is not what you need. You want full-duplex, so writing cancel() is the opposite of what you want.
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.
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.
I'm referring to this sample program from the Beast repository: https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/server/fast/http_server_fast.cpp
I've made some changes to the code to check the ability to process multiple requests simultaneously.
boost::asio::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
std::list<http_worker> workers;
for (int i = 0; i < 10; ++i)
{
workers.emplace_back(acceptor, doc_root);
workers.back().start();
}
ioc.run();
My understanding with the above is that I will now have 10 worker objects to run I/O, i.e. handle incoming connections.
So, my first question is the above understanding correct?
Assuming that the above is correct, I've made some changes to the lambda (handler) passed to the tcp::acceptor:
void accept()
{
// Clean up any previous connection.
boost::beast::error_code ec;
socket_.close(ec);
buffer_.consume(buffer_.size());
acceptor_.async_accept(
socket_,
[this](boost::beast::error_code ec)
{
if (ec)
{
accept();
}
else
{
boost::system::error_code ec2;
boost::asio::ip::tcp::endpoint endpoint = socket_.remote_endpoint(ec2);
// Request must be fully processed within 60 seconds.
request_deadline_.expires_after(
std::chrono::seconds(60));
std::cerr << "Remote Endpoint address: " << endpoint.address() << " port: " << endpoint.port() << "\n";
read_request();
}
});
}
And also in process_request():
void process_request(http::request<request_body_t, http::basic_fields<alloc_t>> const& req)
{
switch (req.method())
{
case http::verb::get:
std::cerr << "Simulate processing\n";
std::this_thread::sleep_for(std::chrono::seconds(30));
send_file(req.target());
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
send_bad_response(
http::status::bad_request,
"Invalid request-method '" + req.method_string().to_string() + "'\r\n");
break;
}
}
And here's my problem: If I send 2 simultaneous GET requests to my server, they're being processed sequentially, and I know this because the 2nd "Simulate processing" statement is printed ~30 seconds after the previous one which would mean that execution gets blocked on the first thread.
I've tried to read the documentation of boost::asio to better understand this, but to no avail.
The documentation for acceptor::async_accept says:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be >invoked from within this function. Invocation of the handler will be performed in a manner equivalent to >using boost::asio::io_service::post().
And the documentation for boost::asio::io_service::post() says:
The io_service guarantees that the handler will only be called in a thread in which the run(), >run_one(), poll() or poll_one() member functions is currently being invoked.
So, if 10 workers are in the run() state, then why would the two requests get queued?
And also, is there a way to workaround this behavior without adapting to a different example? (e.g. https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/server/async/http_server_async.cpp)
io_context does not create threads internally to execute the tasks, but rather uses the threads that call io_context::run explicitly. In the example the io_context::run is called just from one thread (main thread). So you have just one thread for task executions, which (thread) gets blocked in sleep and there is no other thread to execute other tasks.
To make this example work you have to:
Add more thread into the pool (like in the second example you referred to)
size_t const threads_count = 4;
std::vector<std::thread> v;
v.reserve(threads_count - 1);
for(size_t i = 0; i < threads_count - 1; ++i) { // add thraed_count threads into the pool
v.emplace_back([&ioc]{ ioc.run(); });
}
ioc.run(); // add the main thread into the pool as well
Add synchronization (for example, using strand like in the second example) where it is needed (at least for socket reads and writes), because now your application is multi-threaded.
UPDATE 1
Answering to the question "What is the purpose of a list of workers in the Beast example (the first one that referred) if in fact io_context is only running on one thread?"
Notice, regardless of thread count IO operations here are asynchronous, meaning http::async_write(socket_...) does not block the thread. And notice, that I explain here the original example (not your modified version). One worker here deals with one round-trip of 'request-response'. Imagine the situation. There are two clients client1 and client2. Client1 has poor internet connection (or requests a very big file) and client2 has the opposite conditions. Client1 makes request. Then client2 makes request. So if there was just one worker client2 would had to wait until client1 finished the whole round-trip 'request-response`. But, because there are more than one workers client2 gets response immediately not waiting the client1 (keep in mind IO does not block your single thread). The example is optimized for situation where bottleneck is IO but not the actual work. In your modified example you have quite the opposite situation - the work (30s) is very expensive compared to IO. For that case better use the second example.
I am using boost::asio::connect on a tcp::socket. When all goes fine, the connect returns immediately but on a poor network, the connect times out after a log wait of 15 seconds. I cannot afford to wait that long and so want to reduce the timeout. Unfortunately I have not come across any solution so far.
I see solutions where async_wait is been used together with deadline_timer but all those examples are for receive / send operations and not for connect.
Can anyone help me with a sample code for boost::asio::connect(socket, endpoints);. Requirement is that it should timeout in 5 seconds instead of 15.
Have you take a look to the following example? It contains a sample code an async_connect with timeout.
The connect with timeout method could be implemented using the following code:
void connect(const std::string& host, const std::string& service,
boost::posix_time::time_duration timeout) {
// Resolve the host name and service to a list of endpoints.
tcp::resolver::query query(host, service);
tcp::resolver::iterator iter = tcp::resolver(io_service_).resolve(query);
// Set a deadline for the asynchronous operation. As a host name may
// resolve to multiple endpoints, this function uses the composed operation
// async_connect. The deadline applies to the entire operation, rather than
// individual connection attempts.
deadline_.expires_from_now(timeout);
// Set up the variable that receives the result of the asynchronous
// operation. The error code is set to would_block to signal that the
// operation is incomplete. Asio guarantees that its asynchronous
// operations will never fail with would_block, so any other value in
// ec indicates completion.
boost::system::error_code ec = boost::asio::error::would_block;
// Start the asynchronous operation itself. The boost::lambda function
// object is used as a callback and will update the ec variable when the
// operation completes. The blocking_udp_client.cpp example shows how you
// can use boost::bind rather than boost::lambda.
boost::asio::async_connect(socket_, iter, var(ec) = _1);
// Block until the asynchronous operation has completed.
do io_service_.run_one(); while (ec == boost::asio::error::would_block);
// Determine whether a connection was successfully established. The
// deadline actor may have had a chance to run and close our socket, even
// though the connect operation notionally succeeded. Therefore we must
// check whether the socket is still open before deciding if we succeeded
// or failed.
if (ec || !socket_.is_open())
throw boost::system::system_error(
ec ? ec : boost::asio::error::operation_aborted);
}
Single-threaded application.
It happens not every time, only after 1.5 hours of high load.
tcp::socket::async_connect
tcp::socket::close (by deadline_timer)
async_connect_handler gives success error_code (one of a million times), but socket is closed by(2). 99.999% of time it gives errno=125 (ECANCELED).
Is it possible that socket implementation or boost asio somehow do this:
async_connect
async success posted to io_service
close by timer
async success handled by me, not affected by close
Right now solved by saving state in my variables, ignoring accept success.
Linux 2.6 (fedora).
Boost 1.46.0
PS: ofcouse possible bug on my part... But runs smoothly for days if not this.
As Igor mentions in the comments, the completion handler is already queued.
This scenario is the result of a separation in time between when an operation executes and when a handler is invoked. The documentation for io_service::run(), io_service::run_one(), io_service::poll(), and io_service::poll_one() is specific to mention handlers, and not operations. In the scenario, the socket::async_connect() operation and deadline_timer::async_wait() operation complete in the same event loop iteration. This results in both handlers being added to the io_service for deferred invocation, in an unspecified order.
Consider the following snippet that accentuates the scenario:
void handle_wait(const boost::system::error_code& error)
{
if (error) return;
socket_.close();
}
timer_.expires_from_now(boost::posix_time::seconds(30));
timer_.async_wait(&handle_wait);
socket_.async_connect(endpoint_, handle_connect);
boost::this_thread::sleep(boost::posix_time::seconds(60));
io_service_.run_one();
When io_service_.run_one() is invoked, both socket::async_connect() and deadline_timer::async_wait() operations may have completed, causing handle_wait and handle_connect to be ready for invocation from within the io_service in an unspecified order. To properly handle this unspecified order, additional logic need to occur from within handle_wait() and handle_connect() to query the current state, and determine if the other handler has been invoked, rather than depending solely on the status (error_code) of the operation.
The easiest way to determine if the other handler has invoked is:
In handle_connect(), check if the socket is still open via is_open(). If the socket is still open, then handle_timer() has not been invoked. A clean way to indicate to handle_timer() that handle_connect() has ran is to update the expiry time.
In handle_timer(), check if the expiry time has passed. If this is true, then handle_connect() has not ran, so close the socket.
The resulting handlers could look like the following:
void handle_wait(const boost::system::error_code& error)
{
// On error, return early.
if (error) return;
// If the timer expires in the future, then connect handler must have
// first.
if (timer_.expires_at() > deadline_timer::traits_type::now()) return;
// Timeout has occurred, so close the socket.
socket_.close();
}
void handle_connect(const boost::system::error_code& error)
{
// The async_connect() function automatically opens the socket at the start
// of the asynchronous operation. If the socket is closed at this time then
// the timeout handler must have run first.
if (!socket_.is_open()) return;
// On error, return early.
if (error) return;
// Otherwise, a connection has been established. Update the timer state
// so that the timeout handler does not close the socket.
timer_.expires_at(boost::posix_time::pos_infin);
}
Boost.Asio provides some examples for handling timeouts.
I accept twsansbury's answer, just want to add some more info.
About shutdown():
void async_recv_handler( boost::system::error_code ec_recv, std::size_t count )
{
if ( !m_socket.is_open() )
return; // first time don't trust to ec_recv
if ( ec_recv )
{
// oops, we have error
// log
// close
return;
}
// seems that we are just fine, no error in ec_recv, we can gracefully shutdown the connection
// but shutdown may fail! this check is working for me
boost::system::error_code ec_shutdown;
// second time don't trusting to ec_recv
m_socket.shutdown( t, ec_shutdown );
if ( !ec_shutdown )
return;
// this error code is expected
if ( ec_shutdown == boost::asio::error::not_connected )
return;
// other error codes are unexpected for me
// log << ec_shutdown.message()
throw boost::system::system_error(ec_shutdown);
}