Skip to content

Commit ff2b4fc

Browse files
authored
Add accept_socket() to tcp and tls servers (#380)
* Add accept_socket() to tcp and tls servers This will allow for a graceful shutdown via the socket.shutdown(how) functions * skip test on __APPLE__ Closes #377
1 parent fb9f7b7 commit ff2b4fc

File tree

7 files changed

+93
-7
lines changed

7 files changed

+93
-7
lines changed

include/coro/net/socket.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class socket
6868

6969
/**
7070
* @param how Shuts the socket down with the given operations.
71-
* @param Returns true if the sockets given operations were shutdown.
71+
* @return Returns true if the sockets given operations were shutdown.
7272
*/
7373
auto shutdown(poll_op how = poll_op::read_write) -> bool;
7474

include/coro/net/tcp/server.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,19 @@ class server
5656

5757
/**
5858
* Accepts an incoming tcp client connection. On failure the tls clients socket will be set to
59-
* and invalid state, use the socket.is_value() to verify the client was correctly accepted.
59+
* and invalid state, use the socket.is_valid() to verify the client was correctly accepted.
6060
* @return The newly connected tcp client connection.
6161
*/
6262
auto accept() -> coro::net::tcp::client;
6363

64+
/**
65+
* @return The tcp accept socket this server is using.
66+
* @{
67+
**/
68+
[[nodiscard]] auto accept_socket() -> net::socket& { return m_accept_socket; }
69+
[[nodiscard]] auto accept_socket() const -> const net::socket& { return m_accept_socket; }
70+
/** @} */
71+
6472
private:
6573
friend client;
6674
/// The io scheduler for awaiting new connections.

include/coro/net/tls/server.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ class server
6666
*/
6767
auto accept(std::chrono::milliseconds timeout = std::chrono::seconds{30}) -> coro::task<coro::net::tls::client>;
6868

69+
/**
70+
* @return The tcp accept socket this server is using.
71+
* @{
72+
**/
73+
[[nodiscard]] auto accept_socket() -> net::socket& { return m_accept_socket; }
74+
[[nodiscard]] auto accept_socket() const -> const net::socket& { return m_accept_socket; }
75+
/** @} */
76+
6977
private:
7078
/// The io scheduler for awaiting new connections.
7179
std::shared_ptr<io_scheduler> m_io_scheduler{nullptr};

src/detail/io_notifier_epoll.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ auto io_notifier_epoll::watch(fd_t fd, coro::poll_op op, void* data, bool keep)
6565
auto io_notifier_epoll::watch(detail::poll_info& pi) -> bool
6666
{
6767
auto event_data = event_t{};
68-
event_data.events = static_cast<uint32_t>(pi.m_op) | EPOLLONESHOT | EPOLLRDHUP;
68+
event_data.events = static_cast<uint32_t>(pi.m_op) | EPOLLONESHOT | EPOLLRDHUP | EPOLLHUP;
6969
event_data.data.ptr = static_cast<void*>(&pi);
7070
return ::epoll_ctl(m_fd, EPOLL_CTL_ADD, pi.m_fd, &event_data) != -1;
7171
}

src/detail/io_notifier_kqueue.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,17 @@ auto io_notifier_kqueue::event_to_poll_status(const event_t& event) -> poll_stat
160160
{
161161
return poll_status::event;
162162
}
163-
else if (event.flags & EV_ERROR)
163+
164+
if (event.flags & EV_EOF)
164165
{
165-
return poll_status::error;
166+
return poll_status::closed;
166167
}
167-
else if (event.flags & EV_EOF)
168+
169+
if (event.flags & EV_ERROR)
168170
{
169-
return poll_status::closed;
171+
return poll_status::error;
170172
}
173+
171174
throw std::runtime_error{"invalid kqueue state"};
172175
}
173176

test/bench.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,13 @@ TEST_CASE("benchmark tcp::server echo server thread pool", "[benchmark]")
404404
while (true)
405405
{
406406
auto pstatus = co_await client.poll(coro::poll_op::read);
407+
if (pstatus != coro::poll_status::event)
408+
{
409+
REQUIRE_THREAD_SAFE(pstatus == coro::poll_status::closed);
410+
// the socket has been closed
411+
break;
412+
}
413+
407414
REQUIRE_THREAD_SAFE(pstatus == coro::poll_status::event);
408415

409416
auto [rstatus, rspan] = client.recv(in);
@@ -597,6 +604,13 @@ TEST_CASE("benchmark tcp::server echo server inline", "[benchmark]")
597604
while (true)
598605
{
599606
auto pstatus = co_await client.poll(coro::poll_op::read);
607+
if (pstatus != coro::poll_status::event)
608+
{
609+
REQUIRE_THREAD_SAFE(pstatus == coro::poll_status::closed);
610+
// the socket has been closed
611+
break;
612+
}
613+
600614
REQUIRE_THREAD_SAFE(pstatus == coro::poll_status::event);
601615

602616
auto [rstatus, rspan] = client.recv(in);

test/net/test_tcp_server.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66

77
#include <iostream>
88

9+
TEST_CASE("tcp_server", "[tcp_server]")
10+
{
11+
std::cerr << "[tcp_server]\n\n";
12+
}
13+
914
TEST_CASE("tcp_server ping server", "[tcp_server]")
1015
{
16+
std::cerr << "BEGIN tcp_server ping server\n";
1117
const std::string client_msg{"Hello from client"};
1218
const std::string server_msg{"Reply from server!"};
1319

@@ -89,10 +95,12 @@ TEST_CASE("tcp_server ping server", "[tcp_server]")
8995

9096
coro::sync_wait(coro::when_all(
9197
make_server_task(scheduler, client_msg, server_msg), make_client_task(scheduler, client_msg, server_msg)));
98+
std::cerr << "END tcp_server ping server\n";
9299
}
93100

94101
TEST_CASE("tcp_server concurrent polling on the same socket", "[tcp_server]")
95102
{
103+
std::cerr << "BEGIN tcp_server concurrent polling on the same socket\n";
96104
// Issue 224: This test duplicates a client and issues two different poll operations per coroutine.
97105

98106
using namespace std::chrono_literals;
@@ -173,6 +181,51 @@ TEST_CASE("tcp_server concurrent polling on the same socket", "[tcp_server]")
173181
auto response = std::move(std::get<1>(result).return_value());
174182

175183
REQUIRE(request == response);
184+
std::cerr << "END tcp_server concurrent polling on the same socket\n";
176185
}
177186

187+
#ifndef __APPLE__
188+
// This test is known to not work on kqueue style systems (e.g. apple) because the socket shutdown()
189+
// call does not properly trigger an EV_EOF flag on the accept socket.
190+
191+
TEST_CASE("tcp_server graceful shutdown via socket", "[tcp_server]")
192+
{
193+
std::cerr << "BEGIN tcp_server graceful shutdown via socket\n";
194+
auto scheduler = coro::io_scheduler::make_shared(coro::io_scheduler::options{
195+
.execution_strategy = coro::io_scheduler::execution_strategy_t::process_tasks_inline});
196+
coro::net::tcp::server server{scheduler};
197+
coro::event started{};
198+
199+
auto make_accept_task = [](coro::net::tcp::server& server, coro::event& started) -> coro::task<void>
200+
{
201+
std::cerr << "make accept task start\n";
202+
started.set();
203+
auto poll_result = co_await server.poll();
204+
REQUIRE(poll_result == coro::poll_status::closed);
205+
auto client = server.accept();
206+
REQUIRE_FALSE(client.socket().is_valid());
207+
std::cerr << "make accept task completed\n";
208+
};
209+
210+
scheduler->spawn(make_accept_task(server, started));
211+
212+
coro::sync_wait(started);
213+
// we'll wait a bit to make sure the server.poll() is fully registered.
214+
std::this_thread::sleep_for(std::chrono::milliseconds(500));
215+
216+
server.accept_socket().shutdown(coro::poll_op::read_write);
217+
218+
scheduler->shutdown();
219+
std::cerr << "END tcp_server graceful shutdown via socket\n";
220+
}
221+
222+
#endif
223+
224+
TEST_CASE("~tcp_server", "[tcp_server]")
225+
{
226+
std::cerr << "[~tcp_server]\n\n";
227+
}
228+
229+
178230
#endif // LIBCORO_FEATURE_NETWORKING
231+

0 commit comments

Comments
 (0)