Cancel pending task callback invocation when the task owner is destructed - c++

I am implementing something looks like a HTTP server, the design is: for a already established connection, I want to reuse it for several requests, so I start another reading task with async_read on it when a request is finished, and also start a deadline_timer. If there is no input in 60 seconds, the timer will be triggered and the connection will be destructed. The thing that annoys me is that before the invocation of the connection's destructor, the callback we set to async_read will be invoked.
So, my question is, is there any way to cancel the pending reading task, that is, destruct the connection without the callback function invoked?
If the generic description above is not clear, the detail work flow is as below(code is attached at the bottom):
cleanup() is called when a request finished;
start the timer and another reading task in cleanup();
if time is out, HandleTimeout() is called, and it calls stop();
in stop(), do the clean work, and after it, the connection instance will be destructed.
but, after step 4, the callback() function will be called, which is registered in AsyncRead(), so, is there any way to cancel the invocation of callback()?
code:
class Connection : public boost::enable_shared_from_this<Connection>,
private boost::noncopyable {
public:
typedef Connection this_type;
void cleanup() {
timer_.expires_from_now(boost::posix_time::seconds(kDefaultTimeout));
timer_.async_wait(boost::bind(&this_type::HandleTimeout,
shared_from_this(),
boost::asio::placeholders::error));
AsyncRead();
}
void AsyncRead() {
boost::asio::async_read(*socket_, in_, boost::asio::transfer_at_least(1),
boost::bind(&this_type::callback,
shared_from_this(),
boost::asio::placeholders::error));
}
void callback(const boost::system::error_code& e) {
// ...
}
void HandleTimeout(const boost::system::error_code& e) {
if(e == boost::asio::error::operation_aborted)
LDEBUG << "The timeout timer is cancelled.";
else if(e)
LERROR << "Error occurred with the timer, message: " << e.message();
else if(timer_.expires_at()
<= boost::asio::deadline_timer::traits_type::now()) {
LDEBUG << "Connection timed out, close it.";
stop();
}
}
virtual void stop() {
connected_ = false;
socket_->close();
connection_manager_.stop(shared_from_this());
}
private:
// ...
boost::asio::deadline_timer timer_;
};

There is no clean way to accomplish this. The only way to guarantee that ready-to-run handlers, such as Connection::callback(), will not be invoked is to either:
Stop processing the io_service event loop.
Destroy the io_service, as the io_service's destructor will cause all outstanding handlers to be destroyed.
In the example code, consider returning in Connection::callback() if the socket is no longer open:
void callback(const boost::system::error_code& error)
{
if (!socket_.is_open()) return;
// ...
}
Also note that the error_code argument is not enough to deduce whether the timeout has occurred. It is possible that Connection::callback() is queued for invocation with an error_code of boost::system::errc::success when socket::close() is invoked. Hence, there are no operations to cancel.

Related

Boost Beast Websocket: Application Data After Close Notify

I am using boost::beast as well as boost::asio to implement a websocket client with SSL support. My WebsocketClient class has the following members:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<tcp::resolver> resolver;
std::optional<websocket::stream<beast::ssl_stream<beast::tcp_stream>>> ws;
std::promise<void> promise_;
The io_context and ssl_context are constructed and passed by reference from main. The resolver and ws members are initialized via:
resolver.emplace(boost::asio::make_strand(io_context));
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
After calling a WebsocketClient method "run" which triggers a sequence which calls connect, handshake, etc. we enter an async loop. Also of note, we set the value of promise before entering the "on_read" loop. This promise is returned to the caller of run in main which allows the application to progress once the initial WebsocketClient::run call is made.
void WebsocketClient::on_read(
beast::error_code ec,
std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) {
fail(ec, "read");
reconnect(ec);
} else {
// business logic
ws_->async_read(
buffer_,
beast::bind_front_handler(
&WebsocketClient::on_read,
this)
);
}
}
The WebsocketClient works well for several minutes until I reach an exception:
read: application data after close notify
Upon reaching this error, the WebsocketClient proceeds to:
void WebsocketClient::reconnect(beast::error_code ec) {
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
promise_ = std::promise<void>();
resolver_.emplace(boost::asio::make_strand(io_context));
on_disconnect_cb(ec);
}
The function "on_disconnect_cb" is passed in during WebsocketClient initialization as a lambda function. This function simply calls the WebsocketClient::run function again in attempt to reconnect.
To summarize my questions:
Why am I receiving this "read: application data after close notify" error?
Why does the application fail to reconnect after calling WebsocketClient::reconnect?
In the process of debugging I have concluded that the application makes no additional progress after calling:
resolver->async_resolve(
host,
port,
beast::bind_front_handler(
&WebsocketClient::on_resolve,
this)
);
in the WebsocketClient::run function. This fails only in the case of a reconnect and run works as expected on first call. Therefore I expect the issue to be related to incorrectly resetting some of the components of the WebsocketClient.
Edit:
As requested in the comments, I'd like to provide a sketch of the "main" routine. This is indeed a sketch but should cover how the websocket is being used during the program lifecycle:
class App {
public:
App(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context) : {
ws.emplace(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context,
[this]() {
// on_read callback for ws
logic();
},
[this]() {
// on_disconnect callback for ws
on_disconnect();
}
);
}
void run() {
// call underlying websocket connect
}
void logic() {
// do stuff with inbound message
}
void on_disconnect() {
// destroy existing websocket
// If *this contains a value, destroy that value as if by value().T::~T()
ws.reset();
// Initialize new WebsocketClient
ws.emplace(io_context,
ssl_context,
[this](){
logic();
},
[this](){
on_disconnect();
};
);
// call App::run again to restart
run();
}
private:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<WebsocketClient> ws;
};
int main() {
// spinup io_context and detach thread that polls
App app(io_context, ssl_contex);
// triggers WebsocketClient::connect
app.run();
}

Cancelling boost::asio::async_read gracefully

I have a class that looks like this:
class MyConnector : public boost::noncopyable, public boost::enable_shared_from_this<MyConnector>
{
public:
typedef MyConnector this_type;
boost::asio::ip::tcp::socket _plainSocket;
boost::shared_ptr<std::vector<uint8_t>> _readBuffer;
// lot of obvious stuff removed....
void readProtocol()
{
_readBuffer = boost::make_shared<std::vector<uint8_t>>(12, 0);
boost::asio::async_read(_plainSocket, boost::asio::buffer(&_readBuffer->at(0), 12),
boost::bind(&this_type::handleReadProtocol, shared_from_this(),
boost::asio::placeholders::bytes_transferred, boost::asio::placeholders::error));
}
void handleReadProtocol(size_t bytesRead,const boost::system::error_code& error)
{
// handling code removed
}
};
This class instance is generally waiting to receive 12 bytes protocol, before trying to read the full message. However, when I try to cancel this read operation and destroy the object, it doesn't happen. When I call _plainSocket.cancel(ec), it doesn't call handleReadProtocol with that ec. Socket disconnects, but the handler is not called.
boost::system::error_code ec;
_plainSocket.cancel(ec);
And the shared_ptr of MyConnector object that was passed using shared_from_this() is not released. The object remains like a zombie in the heap memory. How do I cancel the async_read() in such a way that the MyConnector object reference count is decremented, allowing the object to destroy itself?
Two things: one, in handleReadProtocol, make sure that, if there is an error, that readProtocol is not called. Canceled operations still call the handler, but with an error code set.
Second, asio recommends shutting down and closing the socket if you're finished with the connection. For example:
asio::post([this] {
if (_plainSocket.is_open()) {
asio::error_code ec;
/* For portable behaviour with respect to graceful closure of a connected socket, call
* shutdown() before closing the socket. */
_plainSocket.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
if (ec) {
Log(fmt::format("Socket shutdown error {}.", ec.message()));
ec.clear();
}
_plainSocket.close(ec);
if (ec)
Log(fmt::format("Socket close error {}.", ec.message()));
}
});

Boost.Asio socket::close not throwing boost::asio::error::operation_aborted error

Boost's official site socket::close() function, see the description
"This function causes all outstanding asynchronous connect, send and receive operations to finish immediately, and the handlers for cancelled operations will be passed the boost::asio::error::operation_aborted error."
But strangely, when i call chat_session::close(), socket::close() passing ERROR_CONNECTION_ABORTED (1236) error instead of boost::asio::error::operation_aborted (995).
Why is this happens?
here is my chat_session class.
class chat_session
: public boost::enable_shared_from_this<chat_session>
{
public:
chat_session(boost::asio::io_service& io, chat_server* room)
: m_sock(io), m_room(room)
{
}
~chat_session()
{
}
void start()
{
m_room.join(shared_from_this());
m_sock.async_read_some(
boost::asio::buffer(m_recv_data),
boost::bind(&chat_session::handle_read, shared_from_this(),
boost::asio::placeholders::error));
}
void close()
{
// closing socket. chat_session::handle_read will receive
// boost::asio::error::operation_aborted error.
m_sock.close();
}
boost::asio::ip::tcp::socket& socket()
{
return m_sock;
}
private:
void handle_read(const boost::system::error_code& error)
{
if (!error)
{
printf("RECV -> %s.\n", m_recv_data);
m_sock.async_read_some(
boost::asio::buffer(m_recv_data),
boost::bind(&chat_session::handle_read, shared_from_this(),
boost::asio::placeholders::error));
}
else
{
// when i call chat_session::close(),
// ERROR_CONNECTION_ABORTED (1236) error occurred
// instead of boost::asio::error::operation_aborted error over here
...
m_room.leave(shared_from_this());
}
}
boost::asio::ip::tcp::socket m_sock;
chat_room& m_room;
char m_recv_data[50];
};
Try calling shutdown on the socket first before you close it, as the boost basic_stream_socket::close documentation specifies in the Remarks here:
Remarks
For portable behaviour with respect to graceful closure of a connected socket, call shutdown() before closing the socket.
Try something like the following in your close function:
m_sock.shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
m_sock.close();
If you want to shut down both send and receive, use "shutdown_both" instead of "shutdown_receive".
Funny enough, I've seen this error happen on Windows but not Linux when using an implementation without the call to shutdown.

How to make asynchronous call with timeout

I want to make an asynchronous call in C++ with timeout, meaning I want to achieve sth like that.
AsynchronousCall(function, time);
if(success)
//call finished succesfully
else
//function was not finished because of timeout
EDIT : Where function is a method that takes a lot of time and I want to interrupt it when it takes too much time.
I' ve been looking for how to achieve it and I thinki boost::asio::deadline_timer is way to go. I guess calling timer.async_wait(boost::bind(&A::fun, this, args)) is what I need, but I do not know how to find if the call was success or was aborted due to timeout.
EDIT: after the answer from ForEveR my code now looks like this.
boost::asio::io_service service;
boost::asio::deadline_timer timer(service);
timer.expires_from_now(boost::posix_time::seconds(5));
timer.async_wait(boost::bind(&A::CheckTimer, this, boost::asio::placeholders::error));
boost::thread bt(&A::AsynchronousMethod, this, timer, args); //asynchronous launch
void A::CheckTimer(const boost::system::error_code& error)
{
if (error != boost::asio::error::operation_aborted)
{
cout<<"ok"<<endl;
}
// timer is cancelled.
else
{
cout<<"error"<<endl;
}
}
I wanted to pass the timer by reference and cancel it in the end of asynchronous method, but I got an error that I cannot access private member declared in class ::boost::asio::basic_io_object.
Maybe using the deadline timer is not that good idea ? I would really appreciate any help. I am passing the timer to the function, because the method that calls the asynchronous method is asynchronous itself and thus I cannot have one timer for whole class or sth like that.
You should use boost::asio::placeholders::error
timer.async_wait(boost::bind(
&A::fun, this, boost::asio::placeholders::error));
A::fun(const boost::system::error_code& error)
{
// timeout, or some other shit happens
if (error != boost::asio::error::operation_aborted)
{
}
// timer is cancelled.
else
{
}
}

Boost.Asio: Correct way to close socket and stop the timer and call the handler

suppose i have class X which encapsulates asio::ip::tcp::socket and deadline_timer. timer is used to break the connection if it is being too slow. Here is X:
class X
{
public:
typedef boost::function<void(const error_code&)> Handler;
void f(Handler);
void close();
private:
void on_connected(const error_code&);
void on_timeout(const error_code&);
void on_work_is_done(const error_code&);
Handler h_;
socket s_;
deadline_timer t_;
};
The function f does some work (send, receive, ...) and then calls handler like this:
void on_work_is_done(const error_code& e)
{
//swapping handlers to release handler after function exits
Handler h;
h.swap(h_);
h(e);
}
During that time the timer t is ticking. So my problem is:
what is a good way to make X::close? close() must close socket s and stop timer t, and it seems very natural that it must call handler h but only after all async operations (on s and t) are cancelled. If there wasn't timer, then the problem is solved:
void X::close() { s.close(); }
the async operations will be cancelled and on_work_is_done() will be called with err == operation_aborted, this will be passed to handler and then the handler will be released. Everything is ok. But since there are 2 objects and each of them might have some pending async operations, the problem appears to be more complex. Because if we do
void X::close()
{
s.close();
t.cancel();
}
both handlers (on_work_is_done, on_timeout) will be invoked, but we may call swapped handler_(err) from the handler that was invoked last.
Is there some straightforward solution?
I see the following approaches:
a) add size_t counter_ which will be set to 2 in X::close() and each of handlers will decrease it by 1. So the handler that made counter_ == 0 will invoke handler_(operation_aborted);
b) run t.cancel() and from on_timeout(err) if err == operation_aborted call s.cancel and from on_work_is_done() invoke handler_(operation_aborted)
Or the whole approach is bad and there is a better way?