#endif
boost::asio::spawn(make_strand(context), std::allocator_arg, make_stack_allocator(),
[this, s=std::move(stream), ssl_ctx] (boost::asio::yield_context yield) mutable {
- auto conn = boost::intrusive_ptr{new Connection(std::move(s))};
+ auto conn = boost::intrusive_ptr{new Connection(std::move(s), yield.get_executor())};
auto c = connections.add(*conn);
// wrap the tcp stream in an ssl stream
boost::asio::ssl::stream<tcp::socket&> stream{conn->socket, *ssl_ctx};
#endif // WITH_RADOSGW_BEAST_OPENSSL
boost::asio::spawn(make_strand(context), std::allocator_arg, make_stack_allocator(),
[this, s=std::move(stream)] (boost::asio::yield_context yield) mutable {
- auto conn = boost::intrusive_ptr{new Connection(std::move(s))};
+ auto conn = boost::intrusive_ptr{new Connection(std::move(s), yield.get_executor())};
auto c = connections.add(*conn);
auto timeout = timeout_timer{yield.get_executor(), request_timeout, conn};
boost::system::error_code ec;
}
// close all connections
- connections.close(ec);
+ connections.close();
pause_mutex.cancel();
}
const bool graceful_stop{ g_ceph_context->_conf->rgw_graceful_stop };
if (!graceful_stop) {
// close all connections so outstanding requests fail quickly
- connections.close(ec);
+ connections.close();
}
// pause and wait until outstanding requests complete
#include <mutex>
+#include <boost/asio/any_io_executor.hpp>
+#include <boost/asio/dispatch.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/intrusive/list.hpp>
+#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <boost/smart_ptr/intrusive_ref_counter.hpp>
namespace rgw::asio {
{
tcp::socket socket;
parse_buffer buffer;
+ // executor of the handle_connection coroutine's strand. used so that close()
+ // from outside the strand (e.g. frontend pause) serializes with coroutine
+ // operations on the socket instead of racing the reactor
+ boost::asio::any_io_executor strand;
+ Connection(tcp::socket&& socket,
+ boost::asio::any_io_executor strand) noexcept
+ : socket(std::move(socket)), strand(std::move(strand)) {}
- explicit Connection(tcp::socket&& socket) noexcept
- : socket(std::move(socket)) {}
-
- void close(boost::system::error_code& ec) {
- socket.close(ec);
+ void close() {
+ boost::asio::dispatch(strand, [c = boost::intrusive_ptr<Connection>{this}] {
+ boost::system::error_code ec;
+ c->socket.close(ec);
+ });
}
tcp::socket& get_socket() { return socket; }
std::lock_guard lock{mutex};
return connections.size();
}
- void close(boost::system::error_code& ec) {
+ void close() {
std::lock_guard lock{mutex};
for (auto& conn : connections) {
- // cancel pending reactor operations which delivers operation_aborted
- // to completion handlers and then shutdown the transport so any
- // subsequent I/O attempts fail immediately.
- conn.socket.cancel(ec);
- conn.socket.shutdown(tcp::socket::shutdown_both, ec);
+ // dispatch operations to the connection's strand so it serializes with
+ // the handle_connection coroutine.
+ boost::asio::dispatch(
+ conn.strand, [c = boost::intrusive_ptr<Connection>{&conn}] {
+ boost::system::error_code ec;
+ // cancel pending reactor operations which delivers operation_aborted
+ // to completion handlers and then shutdown the transport so any
+ // subsequent I/O attempts fail immediately.
+ c->socket.cancel(ec);
+ c->socket.shutdown(tcp::socket::shutdown_both, ec);
+ });
}
}
};
-
} // namespace rgw::asio
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/strand.hpp>
+#include <boost/asio/spawn.hpp>
#include <boost/asio/write.hpp>
#include <boost/intrusive_ptr.hpp>
TEST(BeastFrontendShutdown, CloseShutdownsButDoesNotCloseSocket)
{
asio::io_context ioctx;
- ConnectionList connections;
-
- tcp::acceptor acceptor(ioctx, tcp::endpoint(tcp::v4(), 0));
- acceptor.listen(3);
-
- tcp::socket c0(ioctx), c1(ioctx), c2(ioctx);
- c0.connect(acceptor.local_endpoint());
- auto s0 = acceptor.accept();
- c1.connect(acceptor.local_endpoint());
- auto s1 = acceptor.accept();
- c2.connect(acceptor.local_endpoint());
- auto s2 = acceptor.accept();
-
- boost::intrusive_ptr<Connection> conns[] = {
- new Connection(std::move(s0)),
- new Connection(std::move(s1)),
- new Connection(std::move(s2)),
- };
-
- {
- auto g0 = connections.add(*conns[0]);
- auto g1 = connections.add(*conns[1]);
- auto g2 = connections.add(*conns[2]);
-
- ASSERT_EQ(3u, connections.size());
-
- boost::system::error_code ec;
- connections.close(ec);
-
- // After close(): sockets must still be "open" from boost::asio's
- // perspective (fd not released, descriptor_data not zeroed).
- // socket.close() would have set is_open()=false and fd=-1, which is the
- // thread-safety violation that causes SIGSEGV.
- for (auto& conn : conns) {
- EXPECT_TRUE(conn->socket.is_open());
- EXPECT_GE(conn->socket.native_handle(), 0);
- }
-
- // But the transport is shut down — writes must fail
- for (auto& conn : conns) {
- char buf[] = "test";
- asio::write(conn->socket, asio::buffer(buf), ec);
- EXPECT_TRUE(ec.failed())
- << "Write should fail after shutdown";
- }
- } // guards destroyed here — connections removed from list
-
- EXPECT_EQ(0u, connections.size());
+ asio::spawn(
+ ioctx.get_executor(),
+ [&](asio::yield_context yield) -> void {
+ ConnectionList connections;
+
+ tcp::acceptor acceptor(ioctx, tcp::endpoint(tcp::v4(), 0));
+ acceptor.listen(3);
+
+ tcp::socket c0(ioctx), c1(ioctx), c2(ioctx);
+ c0.async_connect(acceptor.local_endpoint(), yield);
+ auto s0 = acceptor.async_accept(yield);
+ c1.async_connect(acceptor.local_endpoint(), yield);
+ auto s1 = acceptor.async_accept(yield);
+ c2.async_connect(acceptor.local_endpoint(), yield);
+ auto s2 = acceptor.async_accept(yield);
+
+ boost::intrusive_ptr<Connection> conns[] = {
+ new Connection(
+ std::move(s0), asio::make_strand(yield.get_executor())),
+ new Connection(
+ std::move(s1), asio::make_strand(yield.get_executor())),
+ new Connection(
+ std::move(s2), asio::make_strand(yield.get_executor())),
+ };
+
+ {
+ auto g0 = connections.add(*conns[0]);
+ auto g1 = connections.add(*conns[1]);
+ auto g2 = connections.add(*conns[2]);
+
+ ASSERT_EQ(3u, connections.size());
+
+ connections.close();
+
+ // After close(): sockets must still be "open" from boost::asio's
+ // perspective (fd not released, descriptor_data not zeroed).
+ // socket.close() would have set is_open()=false and fd=-1, which is the
+ // thread-safety violation that causes SIGSEGV.
+ for (auto& conn : conns) {
+ EXPECT_TRUE(conn->socket.is_open());
+ EXPECT_GE(conn->socket.native_handle(), 0);
+ }
+
+ // But the transport is shut down — writes must fail
+ for (auto& conn : conns) {
+ boost::system::error_code ec;
+ char buf[] = "test";
+ asio::async_write(conn->socket, asio::buffer(buf), yield[ec]);
+ EXPECT_TRUE(ec.failed()) << "Write should fail after shutdown";
+ }
+ } // guards destroyed here — connections removed from list
+
+ EXPECT_EQ(0u, connections.size());
+ },
+ [](std::exception_ptr eptr) {
+ if (eptr) {
+ std::rethrow_exception(eptr);
+ }
+ });
+ ioctx.run();
}