I develop a desktop chat with boost asio and beast (for browser support).
I use this architecture :
But, when building, I have an issue : bad_weak_ptr, I don't know what is wrong :s
Here a link to the source
https://onlinegdb.com/BkFhDGHe4
Update1 :
I remove run() function into constructor and move it into handle_accept function, tcp_server class. like this:
void tcp_server::handle_accept(const boost::system::error_code ec, websocket_session_ptr new_websocket)
{
if (!ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
new_websocket->run(); //Here
chatwebsocketsessionpointer session = chat_websocket_session::create(room, new_websocket);
room->join(session);
wait_for_connection();
}
}
I can see the chat_webocket_session is deleted, but still have issue with bad_weak_ptr
Update 2 :
I found where is the issue.
If I never call do_read() function, there is no error, and I can connect to server with ws
If I call it into wait_for_data from chat_websoket_session class, I have issue.
So I must found how call do_read()
Update 3 :
If I do
websocket_session_ptr new_websocket(new websocket_session(std::move(socket)));
acceptor.async_accept(
socket,
boost::bind(
&tcp_server::websocket_accept,
this,
boost::asio::placeholders::error,
new_websocket
));
making ref to : boost beast websocket example, I accept first the socket, and after I accept the websocket with m_ws.async_accept() but I have now Bad file descriptor which means the socket is not open.
P.S: I update the ide URL (GDB online debugger)
You're using the shared pointer to this from inside the constructor:
websocket_session::websocket_session(tcp::socket socket)
: m_ws(std::move(socket))
, strand(socket.get_executor())
{
run();
}
Inside run() you do
void websocket_session::run() {
// Accept the websocket handshake
std::cout << "Accepted connection" << std::endl;
m_ws.async_accept(boost::asio::bind_executor(
strand, std::bind(&websocket_session::on_accept, , std::placeholders::_1)));
}
That uses shared_from_this() which will try to lock the unitialized weak_ptr from enable_shared_from_this. As you can see in the documentation that throws the std::bad_weak_ptr exception (ad. 11)
The documentation to shared_from_this explicitly warns against this:
It is permitted to call shared_from_this only on a previously shared object, i.e. on an object managed by std::shared_ptr (in particular, shared_from_this cannot be called in a constructor).
Related
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()));
}
});
I am trying to wrap my head around resource management in boost::asio. I am seeing callbacks called after the corresponding sockets are already destroyed. A good example of this is in the boost::asio official example: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp
I am particularly concerned with the close method:
void close()
{
io_service_.post([this]() { socket_.close(); });
}
If you call this function and afterwards destruct chat_client instance that holds socket_, socket_ will be destructed before the close method is called on it. Also any pending async_* callbacks can be called after the chat_client has been destroyed.
How would you correctly handle this?
You can do socket_.close(); almost any time you want, but you should keep in mind some moments:
If you have threads, this call should be wrapped with strand or you can crash. See boost strand documentation.
Whenever you do close keep in mind that
io_service can already have queued handlers. And they will be called anyway with old state/error code.
close can throw an exception.
close does NOT includes ip::tcp::socket destruction. It
just closes the system socket.
You must manage object lifetime
yourself to ensure objects will be destroyed only when there is no
more handlers. Usually this is done with enable_shared_from_this
on your Connection or socket object.
Invoking socket.close() does not destroy the socket. However, the application may need to manage the lifetime of objects for which the operation and completion handlers depend upon, but this is not necessarily the socket object itself. For instance, consider a client class that holds a buffer, a socket, and has a single outstanding read operation with a completion handler of client::handle_read(). One can close() and explicitly destroy the socket, but the buffer and client instance must remain valid until at least the handler is invoked:
class client
{
...
void read()
{
// Post handler that will start a read operation.
io_service_.post([this]() {
async_read(*socket, boost::asio::buffer(buffer_);
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
});
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred
)
{
// make use of data members...if socket_ is not used, then it
// is safe for socket to have already been destroyed.
}
void close()
{
io_service_.post([this]() {
socket_->close();
// As long as outstanding completion handlers do not
// invoke operations on socket_, then socket_ can be
// destroyed.
socket_.release(nullptr);
});
}
private:
boost::asio::io_service& io_service_;
// Not a typical pattern, but used to exemplify that outstanding
// operations on `socket_` are not explicitly dependent on the
// lifetime of `socket_`.
std::unique_ptr<boost::asio::socket> socket_;
std::array<char, 512> buffer_;
...
}
The application is responsible for managing the lifetime of objects upon which the operation and handlers are dependent. The chat client example accomplishes this by guaranteeing that the chat_client instance is destroyed after it is no longer in use, by waiting for the io_service.run() to return within the thread join():
int main(...)
{
try
{
...
boost::asio::io_service io_service;
chat_client c(...);
std::thread t([&io_service](){ io_service.run(); });
...
c.close();
t.join(); // Wait for `io_service.run` to return, guaranteeing
// that `chat_client` is no longer in use.
} // The `chat_client` instance is destroyed.
catch (std::exception& e)
{
...
}
}
One common idiom is to managing object lifetime is to have the I/O object be managed by a single class that inherits from enable_shared_from_this<>. When a class inherits from enable_shared_from_this, it provides a shared_from_this() member function that returns a valid shared_ptr instance managing this. A copy of the shared_ptr is passed to completion handlers, such as a capture-list in lambdas or passed as the instance handle to bind(), causing the lifetime of the I/O object to be extended to at least as long as the handler. See the Boost.Asio asynchronous TCP daytime server tutorial for an example using this approach.
Here is part of boost's official http server example:
connection_manager::stop_all method in connection_manager.cpp
void connection_manager::stop_all()
{
std::for_each(connections_.begin(), connections_.end(),
boost::bind(&connection::stop, _1));
connections_.clear();
}
connection::stop method in connection.cpp
void connection::stop()
{
socket_.close();
}
connection_manager::stop_all method is
calling each connection::stop method from connection object list and destroying all connection objects.
connection::stop method is closing socket, and socket::close() method will invoke connection::handle_read or connection::handle_write handler with an operation_aborted error.
Right?
Then, what if the situation "invoking handler after connection objects already destroyed" ??
I think this situation can cause some unexpected error... isn't it? (and how to deal with this situation?)
Consider this test program :
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <functional>
#include <iostream>
static void callback (boost::asio::ip::tcp::socket && socket)
{
//boost::asio::ip::tcp::socket new_socket = std::move(socket);
std::cout << "Accepted" << std::endl;
}
static void on_accept (boost::asio::ip::tcp::acceptor & acceptor,
boost::asio::ip::tcp::socket & socket,
boost::system::error_code const & error)
{
if (error)
{
std::cerr << error << ' ' << error.message() << std::endl;
return ;
}
callback(std::move(socket));
acceptor.async_accept
(
socket,
std::bind(on_accept, std::ref(acceptor), std::ref(socket), std::placeholders::_1)
);
}
int main ()
{
boost::asio::io_service service;
boost::asio::io_service::work work { service };
boost::asio::ip::tcp::acceptor acceptor { service };
boost::asio::ip::tcp::socket socket { service };
boost::asio::ip::tcp::endpoint endpoint { boost::asio::ip::tcp::v4(), 5555 };
boost::system::error_code ec;
using socket_base = boost::asio::socket_base;
auto option = socket_base::reuse_address { false };
if (acceptor.open(endpoint.protocol(), ec) ||
acceptor.set_option(option, ec) ||
acceptor.bind(endpoint, ec) ||
acceptor.listen(socket_base::max_connections, ec) ||
acceptor.is_open() == false)
return 1;
acceptor.async_accept
(
socket,
std::bind(on_accept, std::ref(acceptor), std::ref(socket), std::placeholders::_1)
);
service.run();
}
When I connect a client to it, I get an error :
Accepted
system:1 Incorrect function
(The on_accept() function is called with an error code when the socket object from the callback() function is destroyed).
Also, the client is not disconnected at all.
If I uncomment the line in the callback() function, everything works fine, no error message and the client is disconnected as expected.
Now for the environment settings, I'm under Windows 8.1, using a MinGW-w64 v4.9.2 compiler with Boost.Asio v1.58.0 compiled with that same compiler.
The command line used to compile the file is as follow :
$ g++ -std=c++14 -IC:/C++/boost/1.58.0 main.cpp -LC:/C++/boost/1.58.0/lib -lboost_system-mgw49-mt-1_58 -lwsock32 -lws2_32 -o test.exe
Note that using Boost 1.57.0 results in the same behavior.
I can also remove the commented line completely, and then use this :
static void callback (boost::asio::ip::tcp::socket && socket)
{
std::cout << "Accepted" << std::endl;
socket.shutdown(socket.shutdown_both);
socket.close();
}
And the program will behave correctly too.
So, how come I need to add extra steps to not get an error here ? IIRC this behavior wasn't there a couple of months ago when I first tried that program.
The code only creates a single socket, which is an automatic variable whose lifetime will end once main() returns. std::move(socket) only returns an xvalue that can be provided to socket's move constructor; it does not construct a socket.
To resolve this, consider changing the callback() signature to accepting the socket via value, allowing the compiler to invoke the move-constructor for you when given an xvalue. Change:
static void callback (boost::asio::ip::tcp::socket && socket)
to:
static void callback (boost::asio::ip::tcp::socket socket)
Overall, the flow of the code is as follows:
void callback(socket&&); // rvalue reference.
void on_accept(acceptor&, socket&, ...) // lvalue reference.
{
...
callback(static_cast<socket&&>(socket)); // Cast to xvalue.
acceptor.async_accept(socket,
std::bind(&on_accept, std::ref:acceptor),
std::ref(socket), // lvalue reference.
...);
}
int main()
{
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
boost::asio::ip::tcp::acceptor acceptor(io_service);
boost::asio::ip::tcp::socket socket(io_service); // Constructor.
...
acceptor.async_accept(socket,
std::bind(&on_accept, std::ref:acceptor),
std::ref(socket), // lvalue reference.
...);
io_service.run();
}
Upon successfully accepting the first connection, the socket in main() is open. The on_accept() function invokes callback() with an xvalue, and does not change the state of the socket. Another async_accept() operation is initiated using the already open socket, immediately resulting in the operation's failure. The async_accept() operation fails, invoking on_accept() which will return early, stopping its call chain. As io_service::work is attached to the io_service, execution never returns from io_service::run(), preventing main() from returning and destroying the socket. The final result is no more connections are accepted (no async_accept() operations) and the client is not disconnected (socket is never destroyed).
When callback() changes the state of the socket to close, the issue is no longer present as the pre-condition for async_accept() is met. The other examples meet this pre-condition because:
Uncommenting the one line results in the move-constructor being invoking. The moved-from socket will have the same state as if constructed using the socket(io_service&) constructor.
The socket is explicitly closed via socket.close().
I am receiving this error message
"The I/O operation has been aborted because of either a thread exit or an application request"
when using boost::asio::socket::async_read_some()
What does the error mean? What should I be looking for?
Here is the relevant code:
void tcp_connection::start()
{
printf("Connected to simulator\n");
socket_.async_read_some(boost::asio::buffer(myBuffer,256),
boost::bind(&tcp_connection::read_sim_handler,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void tcp_connection::read_sim_handler(
const boost::system::error_code& error, // Result of operation.
std::size_t len ) // Number of bytes read.
{
try {
if (error == boost::asio::error::eof) {
// Connection closed cleanly by peer.
printf("Sim connection closed\n");
return;
} else if (error) {
throw boost::system::system_error(error); // Some other error. if( ! error )
}
socket_.async_read_some(boost::asio::buffer(myBuffer,256),
boost::bind(&tcp_connection::read_sim_handler,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
When I replace the call to async_read_some() with read_some() in the start() method, everything works fine ( except the server blocks waiting for a message! )
Following a comment i see that tcp_connection is going out of scope. I copied the code from http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/tutorial/tutdaytime3.html
which says this:
"We will use shared_ptr and enable_shared_from_this because we want to keep the tcp_connection object alive as long as there is an operation that refers to it."
I confess that I do not know what all that means. So I have broken it somehow?
Following further comments, the answer is
void tcp_connection::start()
{
printf("Connected to simulator\n");
socket_.async_read_some(boost::asio::buffer(myBuffer,256),
boost::bind(&tcp_connection::read_sim_handler,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
Passing shared_from_this() rather than this employs the clever ( too clever? ) keep alive infrastructure established by the server code, even though the connection manager is not in scope, by normal means. For technical details, see comments under accepted answer.
Your tcp_connection object or your buffer object is likely going out of scope prior to the async operation completing.
Since your program is based on one of the tutorial examples, why don't you check out another of the examples that reads some data as well: http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/example/echo/async_tcp_echo_server.cpp
The reason your class goes out of scope is that you are no longer using shared_from_this(). What this does is create a shared_ptr to your class that is stored by the bind handler. This means that the shared_ptr will keep your class alive until your handler is called.
This is also why you need to inherit from enable_shared_from_this.
The last shared_ptr that goes out of scope will delete your class instance.