Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
listen_tests.cpp
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include "common.h"
6#include "unixlistener.h"
7#include <mp/test/foo.capnp.h>
8#include <mp/test/foo.capnp.proxy.h>
9
10#include <chrono>
11#include <compare>
12#include <condition_variable>
13#include <cstring>
14#include <future>
15#include <functional>
16#include <kj/async.h>
17#include <kj/common.h>
18#include <kj/debug.h>
19#include <kj/function.h>
20#include <kj/memory.h>
21#include <kj/test.h>
22#include <memory>
23#include <mp/proxy.h>
24#include <mp/proxy-io.h>
25#include <mp/util.h>
26#include <ratio> // IWYU pragma: keep
27#include <optional>
28#include <string>
29#include <thread>
30#include <unistd.h>
31
32namespace mp {
33namespace test {
34namespace {
35
36constexpr auto FAILURE_TIMEOUT = std::chrono::seconds{30};
37
41class ClientSetup
42{
43public:
44 explicit ClientSetup(int fd)
45 : thread([this, fd] {
46 EventLoop loop("mptest-client", DefaultLogHandler);
48 loop.loop();
49 })
50 {
51 client = client_promise.get_future().get();
52 }
53
55 {
56 client.reset();
57 thread.join();
58 }
59
60 std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> client_promise;
61 std::unique_ptr<ProxyClient<messages::FooInterface>> client;
62
65 std::thread thread;
66};
67
71class ListenSetup
72{
73public:
74 explicit ListenSetup(std::optional<size_t> max_connections = std::nullopt,
76 : thread([this, max_connections, log_handler] {
77 EventLoop loop("mptest-server", log_handler);
78 loop.testing_hook_disconnected = [&] {
79 Lock lock(counter_mutex);
81 counter_cv.notify_all();
82 };
83 loop.testing_hook_connected = [&] {
84 Lock lock(counter_mutex);
86 counter_cv.notify_all();
87 };
88 m_loop_ref.emplace(loop);
90 ListenConnections<messages::FooInterface>(loop, listener.release(), foo, max_connections);
91 ready_promise.set_value();
92 loop.loop();
93 })
94 {
95 ready_promise.get_future().get();
96 }
97
99 {
100 m_loop_ref.reset();
101 thread.join();
102 }
103
104 size_t ConnectedCount()
105 {
106 Lock lock(counter_mutex);
107 return connected_count;
108 }
109
110 size_t DisconnectedCount()
111 {
112 Lock lock(counter_mutex);
113 return disconnected_count;
114 }
115
117 {
118 Lock lock(counter_mutex);
119 const auto deadline = std::chrono::steady_clock::now() + FAILURE_TIMEOUT;
120 const bool matched = counter_cv.wait_until(lock.m_lock, deadline, [&]() MP_REQUIRES(counter_mutex) {
122 });
124 }
125
127 {
128 Lock lock(counter_mutex);
129 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
130 const bool matched = counter_cv.wait_until(lock.m_lock, deadline, [&]() MP_REQUIRES(counter_mutex) {
132 });
134 }
135
136 UnixListener listener;
137 std::promise<void> ready_promise;
138 std::optional<EventLoopRef> m_loop_ref;
140 std::condition_variable counter_cv;
141 size_t connected_count MP_GUARDED_BY(counter_mutex) {0};
142 size_t disconnected_count MP_GUARDED_BY(counter_mutex) {0};
145 std::thread thread;
146};
147
148KJ_TEST("ListenConnections accepts incoming connections")
149{
150 ListenSetup server;
151 KJ_EXPECT(server.ConnectedCount() == 0)
152 auto client = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
153
154 server.WaitForConnectedCount(1);
155 KJ_EXPECT(client->client->add(1, 2) == 3);
156}
157
159{
160 // With max-connections=1, the second socket can connect to the kernel
161 // backlog, but ListenConnections should not accept or serve it until the
162 // first accepted client disconnects.
163
164 ListenSetup server(/*max_connections=*/1);
165
166 KJ_EXPECT(server.ConnectedCount() == 0)
167 auto client1 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
168 server.WaitForConnectedCount(1);
169
170 KJ_EXPECT(client1->client->add(1, 2) == 3);
171
172 auto client2 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
173 // Without this sync, ConnectedCount() == 1 might pass even if
174 // max_connections was not enforced because the event loop has not accepted
175 // client2 yet.
176 (**server.m_loop_ref).sync([] {});
177
178 KJ_EXPECT(server.ConnectedCount() == 1);
179 KJ_EXPECT(server.DisconnectedCount() == 0);
180 client1.reset();
181 server.WaitForDisconnectedCount(1);
182 server.WaitForConnectedCount(2);
183
184 KJ_EXPECT(client2->client->add(2, 3) == 5);
185
186 KJ_EXPECT(server.DisconnectedCount() == 1);
187 client2.reset();
188 server.WaitForDisconnectedCount(2);
189
190 KJ_EXPECT(server.DisconnectedCount() == 2);
191 auto client3 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
192 server.WaitForConnectedCount(3);
193 KJ_EXPECT(client3->client->add(3, 4) == 7);
194}
195
196KJ_TEST("ListenConnections accepts multiple connections")
197{
198 // With max-connections=2, two clients should be accepted and usable at the
199 // same time, while a third waits until one active client disconnects.
200
201 ListenSetup server(/*max_connections=*/2);
202
203 KJ_EXPECT(server.ConnectedCount() == 0);
204 auto client1 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
205 auto client2 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
206 server.WaitForConnectedCount(2);
207
208 KJ_EXPECT(client1->client->add(1, 2) == 3);
209 KJ_EXPECT(client2->client->add(2, 3) == 5);
210
211 auto client3 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
212 // Without this sync, ConnectedCount() == 2 might pass even if
213 // max_connections was not enforced because the event loop has not accepted
214 // client3 yet.
215 (**server.m_loop_ref).sync([] {});
216
217 KJ_EXPECT(server.ConnectedCount() == 2);
218 KJ_EXPECT(server.DisconnectedCount() == 0);
219 client1.reset();
220 server.WaitForDisconnectedCount(1);
221 server.WaitForConnectedCount(3);
222
223 KJ_EXPECT(client3->client->add(3, 4) == 7);
224}
225
226KJ_TEST("ListenConnections handles a client that disconnects before being accepted")
227{
228 Mutex mutex;
229 bool accept_error = false;
230 ListenSetup server(std::nullopt, [&](mp::LogMessage log) {
231 // On macOS, accept() can fail if the peer closes the connection before it is accepted.
232 // The event loop then reports this as an uncaught task exception. We catch and ignore
233 // this specific error here so that the corresponding CI job does not fail.
234 //
235 // This is a Cap'n Proto bug, fixed by https://github.com/capnproto/capnproto/pull/2748
236 if (log.level == mp::Log::Error && log.message.find("Uncaught exception in daemonized task.") != std::string::npos) {
237 Lock lock(mutex);
238 accept_error = true;
239 }
241 });
242
243 // This is racy, if the close does not happen before accept(),
244 // the connection is accepted normally.
245 int fd = server.listener.MakeConnectedSocket();
246 KJ_SYSCALL(close(fd));
247
248 // Wait for the connection to either be accepted and disconnected, or fail
249 // to be accepted and log the error above.
250 const auto deadline = std::chrono::steady_clock::now() + FAILURE_TIMEOUT;
251 auto accept_error_seen = [&] { Lock lock(mutex); return accept_error; };
252 while (!accept_error_seen() && server.DisconnectedCount() < 1 && std::chrono::steady_clock::now() < deadline) {
253 std::this_thread::sleep_for(std::chrono::milliseconds(1));
254 }
255
256#ifndef __APPLE__
257 // No error is expected on platforms other than macOS.
259#endif
260 KJ_EXPECT(server.DisconnectedCount() == (accept_error_seen() ? 0 : 1));
261}
262
263} // namespace
264} // namespace test
265} // namespace mp
std::optional< mp::EventLoopRef > m_loop_ref
Reference to the same EventLoop.
Definition protocol.cpp:154
#define MP_GUARDED_BY(x)
Definition util.h:197
#define MP_REQUIRES(x)
Definition util.h:193
std::unique_ptr< ProxyClient< messages::FooInterface > > client
Mutex counter_mutex
std::promise< std::unique_ptr< ProxyClient< messages::FooInterface > > > client_promise
UnixListener listener
std::condition_variable counter_cv
std::thread thread
Thread variable should be after other struct members so the thread does not start until the other mem...
std::promise< void > ready_promise
void DefaultLogHandler(LogMessage log)
Default event loop log handler used by tests.
Definition common.h:18
KJ_TEST("SpawnProcess does not run callback in child")
Functions to serialize / deserialize common bitcoin types.
Stream MakeStream(EventLoop &loop, SocketId socket)
Wrap a socket file descriptor as an async stream, taking ownership of the fd.
Definition proxy.cpp:50
std::function< void(LogMessage)> LogFn
Definition proxy-io.h:153
void ListenConnections(EventLoop &loop, SocketId fd, InitImpl &init, std::optional< size_t > max_connections=std::nullopt)
Given listening socket identifier and an init object, handle incoming connections and requests by cal...
Definition proxy-io.h:985
Log level
The severity level of this message.
Definition proxy-io.h:150
std::string message
Message to be logged.
Definition proxy-io.h:147
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82