summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/test_shutdown.py58
-rw-r--r--tests/t_fdstate.c12
-rw-r--r--tests/t_socket.c57
-rw-r--r--tests/udp_wake_probe.c42
4 files changed, 157 insertions, 12 deletions
diff --git a/tests/integration/test_shutdown.py b/tests/integration/test_shutdown.py
new file mode 100644
index 0000000..063d779
--- /dev/null
+++ b/tests/integration/test_shutdown.py
@@ -0,0 +1,58 @@
1"""That every thread the tracker starts also ends when it is asked to stop.
2
3The rest of the suite stops a tracker too, so a thread left behind already
4shows up in teardown as the complaint that the tracker had to be killed.
5These ask for the arrangements that park a thread in a blocking read in the
6first place, and they time the exit, because a reader woken only by its own
7receive timeout passes a correctness check while taking a second over it.
8"""
9
10import time
11import unittest
12
13from harness import Tracker, TrackerTestCase
14
15# Generous enough for a platform where shutting the socket down does not wake
16# the reader and the receive timeout has to expire, tight enough that a reader
17# which is never woken at all fails instead of hanging the suite
18PATIENCE = 3.0
19
20
21def time_the_exit(extra_config):
22 tracker = Tracker(extra_config)
23 try:
24 # Serve one request of each kind first, so the threads that are about
25 # to be joined have actually run
26 tracker.announce(b"shutdown-probe\0\0\0\0\0\0")
27 sock, connection_id = tracker.udp_connect()
28 tracker.udp_announce(sock, connection_id, b"shutdown-probe\0\0\0\0\0\0")
29 sock.close()
30 started = time.time()
31 complaints = tracker.stop()
32 return time.time() - started, complaints
33 finally:
34 tracker._cleanup()
35
36
37class UdpWorkers(TrackerTestCase):
38 config = "listen.udp.workers 8\n"
39
40 def test_announce_reaches_one_of_eight_workers(self):
41 """Keeps the workers from being merely started and never used."""
42 sock, connection_id = self.tracker.udp_connect()
43 answer = self.tracker.udp_announce(sock, connection_id, self.hash_for("eight-workers"))
44 sock.close()
45 self.assertEqual(answer["seeders"], 1)
46
47
48class ShutdownEndsEveryThread(unittest.TestCase):
49 def test_eight_udp_workers_are_joined(self):
50 took, complaints = time_the_exit("listen.udp.workers 8\n")
51 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
52 self.assertLess(took, PATIENCE, "stopping eight udp workers took %.1f s" % took)
53
54 def test_udp_in_the_event_loop_still_stops(self):
55 """Worker count zero leaves no thread to join, which must also work."""
56 took, complaints = time_the_exit("listen.udp.workers 0\n")
57 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
58 self.assertLess(took, PATIENCE, "stopping without workers took %.1f s" % took)
diff --git a/tests/t_fdstate.c b/tests/t_fdstate.c
index bf8724a..421f076 100644
--- a/tests/t_fdstate.c
+++ b/tests/t_fdstate.c
@@ -101,6 +101,8 @@ static void case_out_of_range(void) {
101#define RECYCLE_AFTER 32 101#define RECYCLE_AFTER 32
102#define STRESS_CLAIMS 200000 102#define STRESS_CLAIMS 200000
103#define STRESS_GIVEUP (STRESS_CLAIMS * 100) 103#define STRESS_GIVEUP (STRESS_CLAIMS * 100)
104/* What the run has to reach for its result to mean anything */
105#define STRESS_CYCLES (STRESS_CLAIMS / (RECYCLE_AFTER * 4))
104 106
105static _Atomic long stress_claims, stress_recycles, stress_violations, stress_attempts; 107static _Atomic long stress_claims, stress_recycles, stress_violations, stress_attempts;
106static _Atomic int stress_lives[STRESS_SLOTS]; 108static _Atomic int stress_lives[STRESS_SLOTS];
@@ -116,8 +118,14 @@ static _Atomic uint64_t stress_next_cookie;
116#define STRESS_CONN_COOKIE(w) ((void *)(uintptr_t)((w) >> 32)) 118#define STRESS_CONN_COOKIE(w) ((void *)(uintptr_t)((w) >> 32))
117#define STRESS_CONN_GEN(w) ((uint32_t)(w)) 119#define STRESS_CONN_GEN(w) ((uint32_t)(w))
118 120
121/* The workers reach their claim target on their own, but the recycles that
122 make the run worth anything are the keeper's, and on a loaded machine the
123 keeper is the thread that loses. So the run goes on until both targets are
124 met rather than until the faster of the two is. */
119static int stress_running(void) { 125static int stress_running(void) {
120 return atomic_load(&stress_claims) < STRESS_CLAIMS && atomic_load(&stress_attempts) < STRESS_GIVEUP; 126 if (atomic_load(&stress_attempts) >= STRESS_GIVEUP)
127 return 0;
128 return atomic_load(&stress_claims) < STRESS_CLAIMS || atomic_load(&stress_recycles) <= STRESS_CYCLES;
121} 129}
122 130
123static void *stress_worker(void *arg) { 131static void *stress_worker(void *arg) {
@@ -203,7 +211,7 @@ static void case_recycling_under_load(void) {
203 211
204 test_case("and the run actually recycled descriptors, or it proved nothing"); 212 test_case("and the run actually recycled descriptors, or it proved nothing");
205 CHECK(atomic_load(&stress_claims) >= STRESS_CLAIMS); 213 CHECK(atomic_load(&stress_claims) >= STRESS_CLAIMS);
206 CHECK(atomic_load(&stress_recycles) > STRESS_CLAIMS / (RECYCLE_AFTER * 4)); 214 CHECK(atomic_load(&stress_recycles) > STRESS_CYCLES);
207 215
208 for (i = 0; i < STRESS_SLOTS; ++i) 216 for (i = 0; i < STRESS_SLOTS; ++i)
209 fdstate_close((int)i); 217 fdstate_close((int)i);
diff --git a/tests/t_socket.c b/tests/t_socket.c
index 8eef7fd..ab4b785 100644
--- a/tests/t_socket.c
+++ b/tests/t_socket.c
@@ -8,13 +8,18 @@
8#include <fcntl.h> 8#include <fcntl.h>
9#include <netinet/in.h> 9#include <netinet/in.h>
10#include <poll.h> 10#include <poll.h>
11#include <pthread.h>
12#include <signal.h>
13#include <stdatomic.h>
11#include <string.h> 14#include <string.h>
12#include <sys/socket.h> 15#include <sys/socket.h>
16#include <sys/time.h>
13#include <unistd.h> 17#include <unistd.h>
14 18
15/* Opentracker */ 19/* Opentracker */
16#include "ot_ip.h" 20#include "ot_ip.h"
17#include "ot_socket.h" 21#include "ot_socket.h"
22#include "trackerlogic.h"
18#include "test.h" 23#include "test.h"
19 24
20/* These cases talk to the loopback interface rather than to a mock, because 25/* These cases talk to the loopback interface rather than to a mock, because
@@ -144,6 +149,57 @@ static void case_udp_roundtrip(void) {
144 close(s); 149 close(s);
145} 150}
146 151
152/* udp_workers_deinit and livesync_deinit end a read another thread is parked
153 in by signalling that thread. Which mechanisms wake a blocked reader
154 differs between kernels -- shutting the socket down does so on Linux and
155 FreeBSD but not on Darwin, closing it does so on Darwin but not on Linux --
156 so the one the tracker relies on is checked rather than assumed. The
157 watchdog is the test's own: where a reader is not woken this fails with a
158 figure to look at instead of hanging the suite. */
159static _Atomic int reader_returned;
160static int reader_socket;
161
162static void reader_wake(int s) { (void)s; }
163
164static void *blocked_reader(void *args) {
165 char buf[32], ip[16];
166 uint16_t port = 0;
167
168 (void)args;
169 socket_recv(reader_socket, buf, sizeof(buf), ip, &port);
170 atomic_store(&reader_returned, 1);
171 return NULL;
172}
173
174static void case_a_signal_ends_a_blocking_read(void) {
175 struct sigaction sa;
176 struct timeval watchdog = {2, 0};
177 pthread_t thread;
178 int waited;
179
180 test_case("a signal ends a read another thread is parked in");
181 memset(&sa, 0, sizeof(sa));
182 sa.sa_handler = reader_wake;
183 sa.sa_flags = 0; /* SA_RESTART would resume the read instead of ending it */
184 CHECK_INT(sigaction(OT_SIGNAL_WAKE, &sa, NULL), 0);
185
186 atomic_store(&reader_returned, 0);
187 reader_socket = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE);
188 CHECK(reader_socket != -1);
189 CHECK_INT(socket_set_blocking(reader_socket, 1), 0);
190 CHECK_INT(setsockopt(reader_socket, SOL_SOCKET, SO_RCVTIMEO, &watchdog, sizeof(watchdog)), 0);
191
192 CHECK_INT(pthread_create(&thread, NULL, blocked_reader, NULL), 0);
193 usleep(100000); /* long enough to be sure it reached the read */
194 CHECK_INT(pthread_kill(thread, OT_SIGNAL_WAKE), 0);
195
196 for (waited = 0; waited < 1000 && !atomic_load(&reader_returned); ++waited)
197 usleep(1000);
198 CHECK(waited < 1000);
199 pthread_join(thread, NULL);
200 close(reader_socket);
201}
202
147/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however 203/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however
148 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here 204 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here
149 and the BSDs fill a plain sockaddr_in, so this is the case that tells the 205 and the BSDs fill a plain sockaddr_in, so this is the case that tells the
@@ -293,6 +349,7 @@ void test_socket(void) {
293 case_sharing_is_opt_in(); 349 case_sharing_is_opt_in();
294#endif 350#endif
295 case_udp_roundtrip(); 351 case_udp_roundtrip();
352 case_a_signal_ends_a_blocking_read();
296 case_udp_v4mapped(); 353 case_udp_v4mapped();
297 case_tcp_roundtrip(); 354 case_tcp_roundtrip();
298 case_connect_refused(); 355 case_connect_refused();
diff --git a/tests/udp_wake_probe.c b/tests/udp_wake_probe.c
index 8a0f7a8..e948c34 100644
--- a/tests/udp_wake_probe.c
+++ b/tests/udp_wake_probe.c
@@ -1,13 +1,17 @@
1/* Three ways to get a thread out of a blocking recvfrom, tested rather than 1/* Four ways to get a thread out of a blocking recvfrom, tested rather than
2 assumed: close the socket, shut it down, or give it a receive timeout. 2 assumed: close the socket, shut it down, give it a receive timeout, or
3 send it a signal.
3 4
4 This decides how udp workers can be stopped. They sit in recvfrom and are 5 This decides how udp workers are stopped. The sockets of a SO_REUSEPORT
5 never joined today, and the sockets of a SO_REUSEPORT group all share one 6 group all share one address, so a worker cannot be singled out by sending
6 address, so a worker cannot be singled out by sending it a datagram. 7 it a datagram, and the two mechanisms differ in what they cost while the
8 tracker is running: a receive timeout wakes every worker on every tick
9 forever, a signal and a shutdown cost nothing until the one moment they
10 are used.
7 11
8 cc -O2 -pthread -o /tmp/wake tests/udp_wake_probe.c && /tmp/wake 12 cc -O2 -pthread -o /tmp/wake tests/udp_wake_probe.c && /tmp/wake
9 13
10 On Linux 6.1: close does not wake it, shutdown does, SO_RCVTIMEO does. */ 14 On Linux 6.1: close does not wake it, the other three do. */
11#include <errno.h> 15#include <errno.h>
12#include <netinet/in.h> 16#include <netinet/in.h>
13#include <pthread.h> 17#include <pthread.h>
@@ -15,9 +19,12 @@
15#include <stdio.h> 19#include <stdio.h>
16#include <string.h> 20#include <string.h>
17#include <sys/socket.h> 21#include <sys/socket.h>
22#include <signal.h>
18#include <sys/time.h> 23#include <sys/time.h>
19#include <unistd.h> 24#include <unistd.h>
20 25
26#define WAKE_SIGNAL SIGUSR1
27
21static _Atomic int returned; 28static _Atomic int returned;
22static _Atomic int saved_errno; 29static _Atomic int saved_errno;
23static int sock; 30static int sock;
@@ -31,6 +38,11 @@ static void *reader(void *args) {
31 return NULL; 38 return NULL;
32} 39}
33 40
41/* A handler that does nothing is all it takes: the point is the return from
42 recvfrom with EINTR, not anything the handler does. SA_RESTART must stay
43 off or the kernel resumes the call instead of reporting it. */
44static void noop(int s) { (void)s; }
45
34static int bind_udp(int timeout_ms) { 46static int bind_udp(int timeout_ms) {
35 struct sockaddr_in address; 47 struct sockaddr_in address;
36 int s = socket(AF_INET, SOCK_DGRAM, 0); 48 int s = socket(AF_INET, SOCK_DGRAM, 0);
@@ -50,7 +62,7 @@ static int bind_udp(int timeout_ms) {
50 return s; 62 return s;
51} 63}
52 64
53static void try_it(const char *name, int timeout_ms, int do_close, int do_shutdown) { 65static void try_it(const char *name, int timeout_ms, int do_close, int do_shutdown, int do_signal) {
54 pthread_t thread; 66 pthread_t thread;
55 int waited; 67 int waited;
56 68
@@ -62,6 +74,8 @@ static void try_it(const char *name, int timeout_ms, int do_close, int do_shutdo
62 74
63 if (do_shutdown) 75 if (do_shutdown)
64 shutdown(sock, SHUT_RD); 76 shutdown(sock, SHUT_RD);
77 if (do_signal)
78 pthread_kill(thread, WAKE_SIGNAL);
65 if (do_close) 79 if (do_close)
66 close(sock); 80 close(sock);
67 81
@@ -92,8 +106,16 @@ static void try_it(const char *name, int timeout_ms, int do_close, int do_shutdo
92} 106}
93 107
94int main(void) { 108int main(void) {
95 try_it("close() the socket", 0, 1, 0); 109 struct sigaction sa;
96 try_it("shutdown(SHUT_RD)", 0, 0, 1); 110
97 try_it("SO_RCVTIMEO 500 ms", 500, 0, 0); 111 memset(&sa, 0, sizeof(sa));
112 sa.sa_handler = noop;
113 sa.sa_flags = 0;
114 sigaction(WAKE_SIGNAL, &sa, NULL);
115
116 try_it("close() the socket", 0, 1, 0, 0);
117 try_it("shutdown(SHUT_RD)", 0, 0, 1, 0);
118 try_it("SO_RCVTIMEO 500 ms", 500, 0, 0, 0);
119 try_it("pthread_kill(SIGUSR1)", 0, 0, 0, 1);
98 return 0; 120 return 0;
99} 121}