Bitcoin Core 31.99.0
P2P Digital Currency
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 <cassert>
6#include <filesystem>
7#include <mp/test/foo.capnp.h>
8#include <mp/test/foo.capnp.proxy.h>
9
10#include <chrono>
11#include <condition_variable>
12#include <cstdlib>
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/memory.h>
20#include <kj/test.h>
21#include <memory>
22#include <mp/proxy.h>
23#include <mp/proxy-io.h>
24#include <mp/util.h>
25#include <ratio> // IWYU pragma: keep
26#include <optional>
27#include <stdexcept>
28#include <string>
29#include <sys/socket.h>
30#include <sys/un.h>
31#include <thread>
32#include <unistd.h>
33
34namespace mp {
35namespace test {
36namespace {
37
38constexpr auto FAILURE_TIMEOUT = std::chrono::seconds{30};
39
43class UnixListener
44{
45public:
46 UnixListener()
47 {
48 std::string dir_template = (std::filesystem::temp_directory_path() / "mptest-listener-XXXXXX").string();
49 char* dir = mkdtemp(dir_template.data());
50 KJ_REQUIRE(dir != nullptr);
51 m_dir = dir;
52 m_path = m_dir + "/socket";
53
54 m_fd = socket(AF_UNIX, SOCK_STREAM, 0);
55 KJ_REQUIRE(m_fd >= 0);
56
57 sockaddr_un addr{};
58 addr.sun_family = AF_UNIX;
59 KJ_REQUIRE(m_path.size() < sizeof(addr.sun_path));
60 std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1);
61 KJ_REQUIRE(bind(m_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0);
62 KJ_REQUIRE(listen(m_fd, SOMAXCONN) == 0);
63 }
64
65 ~UnixListener()
66 {
67 if (m_fd >= 0) close(m_fd);
68 if (!m_path.empty()) unlink(m_path.c_str());
69 if (!m_dir.empty()) rmdir(m_dir.c_str());
70 }
71
72 int release()
73 {
74 assert(m_fd >= 0);
75 int fd = m_fd;
76 m_fd = -1;
77 return fd;
78 }
79
80 int MakeConnectedSocket() const
81 {
82 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
83 KJ_REQUIRE(fd >= 0);
84
85 sockaddr_un addr{};
86 addr.sun_family = AF_UNIX;
87 KJ_REQUIRE(m_path.size() < sizeof(addr.sun_path));
88 std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1);
89 KJ_REQUIRE(connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0);
90 return fd;
91 }
92
93private:
94 int m_fd{-1};
95 std::string m_dir;
96 std::string m_path;
97};
98
102class ClientSetup
103{
104public:
105 explicit ClientSetup(int fd)
106 : thread([this, fd] {
107 EventLoop loop("mptest-client", [](mp::LogMessage log) {
108 KJ_LOG(INFO, log.level, log.message);
109 if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
110 });
111 client_promise.set_value(ConnectStream<messages::FooInterface>(loop, fd));
112 loop.loop();
113 })
114 {
115 client = client_promise.get_future().get();
116 }
117
118 ~ClientSetup()
119 {
120 client.reset();
121 thread.join();
122 }
123
124 std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> client_promise;
125 std::unique_ptr<ProxyClient<messages::FooInterface>> client;
126
130};
131
135class ListenSetup
136{
137public:
138 explicit ListenSetup(std::optional<size_t> max_connections = std::nullopt)
139 : thread([this, max_connections] {
140 EventLoop loop("mptest-server", [](mp::LogMessage log) {
141 KJ_LOG(INFO, log.level, log.message);
142 if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
143 });
144 loop.testing_hook_disconnected = [&] {
145 Lock lock(counter_mutex);
146 ++disconnected_count;
147 counter_cv.notify_all();
148 };
149 loop.testing_hook_connected = [&] {
150 Lock lock(counter_mutex);
151 ++connected_count;
152 counter_cv.notify_all();
153 };
154 m_loop_ref.emplace(loop);
156 ListenConnections<messages::FooInterface>(loop, listener.release(), foo, max_connections);
157 ready_promise.set_value();
158 loop.loop();
159 })
160 {
161 ready_promise.get_future().get();
162 }
163
164 ~ListenSetup()
165 {
166 m_loop_ref.reset();
167 thread.join();
168 }
169
170 size_t ConnectedCount()
171 {
172 Lock lock(counter_mutex);
173 return connected_count;
174 }
175
176 size_t DisconnectedCount()
177 {
178 Lock lock(counter_mutex);
179 return disconnected_count;
180 }
181
182 void WaitForConnectedCount(size_t expected_count)
183 {
184 Lock lock(counter_mutex);
185 const auto deadline = std::chrono::steady_clock::now() + FAILURE_TIMEOUT;
186 const bool matched = counter_cv.wait_until(lock.m_lock, deadline, [&]() MP_REQUIRES(counter_mutex) {
187 return connected_count >= expected_count;
188 });
189 KJ_REQUIRE(matched);
190 }
191
192 void WaitForDisconnectedCount(size_t expected_count)
193 {
194 Lock lock(counter_mutex);
195 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
196 const bool matched = counter_cv.wait_until(lock.m_lock, deadline, [&]() MP_REQUIRES(counter_mutex) {
197 return disconnected_count >= expected_count;
198 });
199 KJ_REQUIRE(matched);
200 }
201
202 UnixListener listener;
203 std::promise<void> ready_promise;
204 std::optional<EventLoopRef> m_loop_ref;
206 std::condition_variable counter_cv;
207 size_t connected_count MP_GUARDED_BY(counter_mutex) {0};
208 size_t disconnected_count MP_GUARDED_BY(counter_mutex) {0};
212};
213
214KJ_TEST("ListenConnections accepts incoming connections")
215{
216 ListenSetup server;
217 KJ_EXPECT(server.ConnectedCount() == 0)
218 auto client = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
219
220 server.WaitForConnectedCount(1);
221 KJ_EXPECT(client->client->add(1, 2) == 3);
222}
223
224KJ_TEST("ListenConnections enforces a local connection limit")
225{
226 // With max-connections=1, the second socket can connect to the kernel
227 // backlog, but ListenConnections should not accept or serve it until the
228 // first accepts clients disconnects.
229
230 ListenSetup server(/*max_connections=*/1);
231
232 KJ_EXPECT(server.ConnectedCount() == 0)
233 auto client1 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
234 server.WaitForConnectedCount(1);
235
236 KJ_EXPECT(client1->client->add(1, 2) == 3);
237
238 auto client2 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
239 // Without this sync, ConnectedCount() == 1 might pass even if
240 // max_connections was not enforced because the event loop has not accepted
241 // client2 yet.
242 (**server.m_loop_ref).sync([] {});
243
244 KJ_EXPECT(server.ConnectedCount() == 1);
245 KJ_EXPECT(server.DisconnectedCount() == 0);
246 client1.reset();
247 server.WaitForDisconnectedCount(1);
248 server.WaitForConnectedCount(2);
249
250 KJ_EXPECT(client2->client->add(2, 3) == 5);
251
252 KJ_EXPECT(server.DisconnectedCount() == 1);
253 client2.reset();
254 server.WaitForDisconnectedCount(2);
255
256 KJ_EXPECT(server.DisconnectedCount() == 2);
257 auto client3 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
258 server.WaitForConnectedCount(3);
259 KJ_EXPECT(client3->client->add(3, 4) == 7);
260}
261
262KJ_TEST("ListenConnections accepts multiple connections")
263{
264 // With max-connections=2, two clients should be accepted and usable at the
265 // same time, while a third waits until one active client disconnects.
266
267 ListenSetup server(/*max_connections=*/2);
268
269 KJ_EXPECT(server.ConnectedCount() == 0);
270 auto client1 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
271 auto client2 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
272 server.WaitForConnectedCount(2);
273
274 KJ_EXPECT(client1->client->add(1, 2) == 3);
275 KJ_EXPECT(client2->client->add(2, 3) == 5);
276
277 auto client3 = std::make_unique<ClientSetup>(server.listener.MakeConnectedSocket());
278 // Without this sync, ConnectedCount() == 2 might pass even if
279 // max_connections was not enforced because the event loop has not accepted
280 // client3 yet.
281 (**server.m_loop_ref).sync([] {});
282
283 KJ_EXPECT(server.ConnectedCount() == 2);
284 KJ_EXPECT(server.DisconnectedCount() == 0);
285 client1.reset();
286 server.WaitForDisconnectedCount(1);
287 server.WaitForConnectedCount(3);
288
289 KJ_EXPECT(client3->client->add(3, 4) == 7);
290}
291
292} // namespace
293} // namespace test
294} // namespace mp
#define MP_GUARDED_BY(x)
Definition: util.h:166
#define MP_REQUIRES(x)
Definition: util.h:162
std::unique_ptr< ProxyClient< messages::FooInterface > > client
Mutex counter_mutex
std::optional< EventLoopRef > m_loop_ref
std::promise< std::unique_ptr< ProxyClient< messages::FooInterface > > > client_promise
UnixListener listener
std::string m_dir
std::string m_path
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...
int m_fd
std::promise< void > ready_promise
KJ_TEST("Call FooInterface methods")
Definition: test.cpp:142
Functions to serialize / deserialize common bitcoin types.
Definition: common-types.h:57
void ListenConnections(EventLoop &loop, int fd, InitImpl &init, std::optional< size_t > max_connections=std::nullopt)
Given listening socket file descriptor and an init object, handle incoming connections and requests b...
Definition: proxy-io.h:923
Log level
The severity level of this message.
Definition: proxy-io.h:147
std::string message
Message to be logged.
Definition: proxy-io.h:144
assert(!tx.IsCoinBase())