Below is some code from a boost::asio example. Why is it okay to move the socket_ member when constructing a chat_session if the recursive call at the bottom of the handler is going to hand this same tcp::socket out next time an accept happens? I thought that after a move operation, an object was no longer safe to use.
class chat_server
{
public:
chat_server(boost::asio::io_service& io_service,
const tcp::endpoint& endpoint)
: acceptor_(io_service, endpoint),
socket_(io_service)
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec)
{
std::make_shared<chat_session>(std::move(socket_), room_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
chat_room room_;
};
The code is equivalent to doing the following:
some_class o;
while ( true )
{
// assign a new instance of some_class to the o variable, calling o.bar() is valid
o = some_class(...);
foo(std::move(o));
// o is no longer valid calling o.bar() would fail
}
The call to async_accept re-initialises the socket back to a valid value which can be used. A moved object is in an unspecified (but valid) state, it is up to the implementer of that object what that state is. In the case of an asio::tcp::socket the state is an uninitialised socket which can be used for new connections.
And you're correct, the socket object isn't usable after the move.
But the code calling your lambda will create a new socket and initialize your variable socket_ with that new socket. So the next time your lambda is called it's actually a different socket.
The Standard says that a "moved-from" object must be, at least, in a valid unspecified state.
The moved-from socket is safe to use, because its state is explicitly specified in the documentation:
Following the move, the moved-from object is in the same state as if constructed using the basic_stream_socket(io_context&) constructor.
Related
Reference: https://www.boost.org/doc/libs/1_35_0/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept/overload1.html
boost::asio::ip::tcp::acceptor acceptor(io_service);
...
boost::asio::ip::tcp::socket socket(io_service);
// you have to initialize socket with io_service first before
//you can use it as a parameter on async_accept.
acceptor.async_accept(socket, accept_handler);
Reference:
https://github.com/vinniefalco/CppCon2018/blob/master/listener.cpp
listener:: listener(
net::io_context & ioc,
tcp::endpoint endpoint,
std::shared_ptr < shared_state >
const & state): acceptor_(ioc), socket_(ioc), state_(state) {
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
tcp::acceptor acceptor_;
tcp::socket socket_;
...
}
// Handle a connection
void listener:: on_accept(error_code ec) {
if (ec)
return fail(ec, "accept");
else
// Launch a new session for this connection
std::make_shared < http_session > (
std::move(socket_), // socket_ is moved here?
state_) -> run();
// Accept another connection
acceptor_.async_accept(
socket_, // why we still can use it here?
[self = shared_from_this()](error_code ec) {
self -> on_accept(ec);
});
}
Based on my understanding, std::move(socket_) allows the compiler to cannibalize socket_. In other word, the listener::socket_ originally initialized by socket_(ioc) will become uninitialized.
Question> How can we give an uninitialized socket_ to acceptor_.async_accept?
Thank you
It all depends on the implementation of the types.
We can loosely describe the intent of a move as "the compiler is allowed to cannibalize". But really, for user-defined types we're going to have to tell it how to do that, exactly.
In language "doctrine" a moved-from object may only be assumed safe to destruct, but in practice many libraries make more lenient guarantees (e.g. keeping all the invariants, or making sure that a moved-from object is comparable to a newly constructed one).
Indeed, ASIO documents this:
Remarks
Following the move, the moved-from object is in the same state as if constructed using the basic_stream_socket(const executor_type&) constructor.
In this source file there are two classes : tcp_connection and tcp_server. I've seleceted the relevant bits of code in my opinion but you might want to refer to the full source code for more information.
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
void start()
{
message_ = make_daytime_string();
boost::asio::async_write(socket_, boost::asio::buffer(message_),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
}
};
class tcp_server
{
private:
void start_accept()
{
tcp_connection::pointer new_connection =
tcp_connection::create(acceptor_.get_io_service());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
};
My question is simple : what would we use shared_from_this as a bindargument within the async_write function and use this as a bindargument within the
async_acceptfunction?
Shared pointers govern the lifetime of a dynamically allocated object. Each held pointer increases a reference count and when all held pointers are gone the referred to object is freed.
The Server
There's only one server, and it's not dynamically allocated. Instead, the instance lives longer than the acceptor (and possibly the io_service) so no all async operations can trust the object to stay alive long enough.
The Connections
Each client spawns a new connection, dynamically allocating (make_shared) a tcp_connection instance, and then starting asynchronous operations on it.
The server does not keep a copy of the shared-pointer, so when all async operations on the connection complete (e.g. because the connection was dropped) the tcp_connection object will be freed.
However because the object must not be destroyed when an async operation is in progress, you need to bind the completion handler to the shared pointer (shared_from_this) instead of this.
I'm writing a simple tcp socket server capable of handling multiple concurrent connections. The idea is that the main listening thread will do a blocking accept and offload socket handles to a worker thread (in a thread pool) to handle the communication asynchronously from there.
void server::run() {
{
io_service::work work(io_service);
for (std::size_t i = 0; i < pool_size; i++)
thread_pool.push_back(std::thread([&] { io_service.run(); }));
boost::asio::io_service listener;
boost::asio::ip::tcp::acceptor acceptor(listener, ip::tcp::endpoint(ip::tcp::v4(), port));
while (listening) {
boost::asio::ip::tcp::socket socket(listener);
acceptor.accept(socket);
io_service.post([&] {callback(std::move(socket));});
}
}
for (ThreadPool::iterator it = thread_pool.begin(); it != thread_pool.end(); it++)
it->join();
}
I'm creating socket on the stack because I don't want to have to repeatedly allocate memory inside the while(listening) loop.
The callback function callback has the following prototype:
void callback(boost::asio::socket socket);
It is my understanding that calling callback(std::move(socket)) will transfer ownership of socket to callback. However when I attempt to call socket.receive() from inside callback, I get a Bad file descriptor error, so I assume something is wrong here.
How can I transfer ownership of socket to the callback function, ideally without having to create sockets on the heap?
Undefined behavior is potentially being invoked, as the lambda may be invoking std::move() on a previously destroyed socket via a dangling reference. For example, consider the case where the loop containing the socket ends its current iteration, causing socket to be destroyed, before the lambda is invoked:
Main Thread | Thread Pool
-----------------------------------+----------------------------------
tcp::socket socket(...); |
acceptor.accept(socket); |
io_service.post([&socket] {...}); |
~socket(); // end iteration |
... // next iteration | callback(std::move(socket));
To resolve this, one needs to transfer socket ownership to the handler rather than transfer ownership within the handler. Per documentation, Handlers must be CopyConstructible, and hence their arguments, including the non-copyable socket, must be as well. Yet, this requirement can be relaxed if Asio can eliminate all calls to the handler's copy constructor and one has defined BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS.
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>
void callback(boost::asio::ip::tcp::socket socket);
...
// Transfer ownership of socket to the handler.
io_service.post(
[socket=std::move(socket)]() mutable
{
// Transfer ownership of socket to `callback`.
callback(std::move(socket));
});
For more details on Asio's type checking, see this answer.
Here is a complete example demonstrating a socket's ownership being transferred to a handler:
#include <functional> // std::bind
#include <utility> // std::move
#include <vector> // std::vector
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>
const auto noop = std::bind([]{});
void callback(boost::asio::ip::tcp::socket socket)
{
const std::string actual_message = "hello";
boost::asio::write(socket, boost::asio::buffer(actual_message));
}
int main()
{
using boost::asio::ip::tcp;
// Create all I/O objects.
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket client_socket(io_service);
// Connect the sockets.
client_socket.async_connect(acceptor.local_endpoint(), noop);
{
tcp::socket socket(io_service);
acceptor.accept(socket);
// Transfer ownership of socket to the handler.
assert(socket.is_open());
io_service.post(
[socket=std::move(socket)]() mutable
{
// Transfer ownership of socket to `callback`.
callback(std::move(socket));
});
assert(!socket.is_open());
} // ~socket
io_service.run();
// At this point, sockets have been conencted, and `callback`
// should have written data to `client_socket`.
std::vector<char> buffer(client_socket.available());
boost::asio::read(client_socket, boost::asio::buffer(buffer));
// Verify the correct message was read.
const std::string expected_message = "hello";
assert(std::equal(
begin(buffer), end(buffer),
begin(expected_message), end(expected_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.
I have a class called overlay_server which has a public method
void member_list_server(boost::asio::io_service &io_service){
Now I want to run this in a new thread. So I create a new thread, and use bind in its constructor.
Before I was creating an io_service inside the void member_list_server function.
But Now I am trying create an io_service object in main and pass it to this thread where i get the error?
int main(){
boost::asio::io_service io_service_;
overlay_server overlay_server_(8002);
boost::thread thread_(
boost::bind(&overlay_server::member_list_server, overlay_server_, io_service_)
);
Why do I get the error
error C2248: 'boost::noncopyable_::noncopyable::noncopyable' : cannot access private member declared in class 'boost::noncopyable_::noncopyable'
I will create instances of other classes also, which need io_service.
What is the best way, should I create one io_service object in main and pass it to all threads?
or create a separate one inside all the threads I create in future?
The problem is that you are passing the boost::asio::io_service object by value, rather than by reference. This implies that the boost::asio::io_service default copy constructor should be invoked, something that is not allowed for io_service.
One option is to pass a pointer to the boost::asio::io_service object. Following is some code of mine that does just that:
void Model::TcpPortListener (boost::asio::io_service &io_service,
boost::asio::ip::tcp::acceptor &a,
boost::asio::ip::tcp::endpoint &lep,
SocketObject *readSocketObject)
{
boost::asio::ip::tcp::acceptor *b = &a;
boost::asio::io_service *s = &io_service;
. . .
boost::asio::ip::tcp::socket *sock (new boost::asio::ip::tcp::socket (io_service));
a.async_accept (*sock, boost::bind (&Model::HandleRemoteAccept, this, s, b, sock, lep, boost::asio::placeholders::error));
}
void Model::HandleRemoteAccept (boost::asio::io_service *io_service,
boost::asio::ip::tcp::acceptor *a,
boost::asio::ip::tcp::socket *sock,
boost::asio::ip::tcp::endpoint &lep,
const boost::system::error_code& error)
{
. . .
// Continue listening
TcpPortListener (*io_service, *a, lep, 0);
}
The points to observe are:
In Model::TcpPortListener(), 's' is assigned the address of io_service
In the same function, 's' is passed as the third argument to boost::bind(). Since it is an address rather than an object, there's no invocation of the default copy constructor.
In Model::HandleRemoteAccept(), io_service is dereferenced and passed to the TcpPortListener() function.
The point raised by others about dangling references is an important one and should be well accounted for by you.