diff options
| -rw-r--r-- | opentracker.c | 9 | ||||
| -rw-r--r-- | ot_socket.c | 7 | ||||
| -rw-r--r-- | ot_socket.h | 4 | ||||
| -rw-r--r-- | ot_udp.c | 42 | ||||
| -rw-r--r-- | ot_udp.h | 3 | ||||
| -rw-r--r-- | tests/integration/test_shutdown.py | 58 | ||||
| -rw-r--r-- | tests/t_socket.c | 40 |
7 files changed, 156 insertions, 7 deletions
diff --git a/opentracker.c b/opentracker.c index f579b92..ab4a934 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -397,7 +397,6 @@ static void listen_start_udp_workers(void) { | |||
| 397 | if (entry->proto != FLAG_UDP || entry->workers <= 0) | 397 | if (entry->proto != FLAG_UDP || entry->workers <= 0) |
| 398 | continue; | 398 | continue; |
| 399 | 399 | ||
| 400 | socket_set_blocking((int)entry->sock, 1); | ||
| 401 | event_del((int)entry->sock); | 400 | event_del((int)entry->sock); |
| 402 | udp_worker_install(entry->sock); | 401 | udp_worker_install(entry->sock); |
| 403 | 402 | ||
| @@ -409,8 +408,7 @@ static void listen_start_udp_workers(void) { | |||
| 409 | if (sock == -1) { | 408 | if (sock == -1) { |
| 410 | fprintf(stderr, "Warning: could not bind another socket for udp worker %d: %s. Sharing the first instead.\n", worker, strerror(errno)); | 409 | fprintf(stderr, "Warning: could not bind another socket for udp worker %d: %s. Sharing the first instead.\n", worker, strerror(errno)); |
| 411 | sock = entry->sock; | 410 | sock = entry->sock; |
| 412 | } else | 411 | } |
| 413 | socket_set_blocking((int)sock, 1); | ||
| 414 | } | 412 | } |
| 415 | udp_worker_install(sock); | 413 | udp_worker_install(sock); |
| 416 | } | 414 | } |
| @@ -548,9 +546,10 @@ int main(int argc, char **argv) { | |||
| 548 | listen_start_udp_workers(); | 546 | listen_start_udp_workers(); |
| 549 | 547 | ||
| 550 | server_mainloop(0); | 548 | server_mainloop(0); |
| 549 | udp_workers_deinit(); | ||
| 551 | 550 | ||
| 552 | /* The loop has left, so no thread is serving any more and the teardown | 551 | /* No thread is serving any more, so the teardown can run where a debugger |
| 553 | can run where a debugger and the sanitizers can follow it */ | 552 | and the sanitizers can follow it */ |
| 554 | trackerlogic_deinit(); | 553 | trackerlogic_deinit(); |
| 555 | #ifdef WANT_SYSLOGS | 554 | #ifdef WANT_SYSLOGS |
| 556 | closelog(); | 555 | closelog(); |
diff --git a/ot_socket.c b/ot_socket.c index 158459f..c73738c 100644 --- a/ot_socket.c +++ b/ot_socket.c | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include <netinet/in.h> | 9 | #include <netinet/in.h> |
| 10 | #include <string.h> | 10 | #include <string.h> |
| 11 | #include <sys/socket.h> | 11 | #include <sys/socket.h> |
| 12 | #include <sys/time.h> | ||
| 12 | #include <unistd.h> | 13 | #include <unistd.h> |
| 13 | 14 | ||
| 14 | /* Opentracker */ | 15 | /* Opentracker */ |
| @@ -48,6 +49,12 @@ int socket_set_blocking(int s, int blocking) { | |||
| 48 | return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK)); | 49 | return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK)); |
| 49 | } | 50 | } |
| 50 | 51 | ||
| 52 | int socket_set_timeout(int s, int seconds) { | ||
| 53 | struct timeval patience = {seconds, 0}; | ||
| 54 | |||
| 55 | return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &patience, sizeof(patience)); | ||
| 56 | } | ||
| 57 | |||
| 51 | static int set_nonblocking(int s) { return socket_set_blocking(s, 0); } | 58 | static int set_nonblocking(int s) { return socket_set_blocking(s, 0); } |
| 52 | 59 | ||
| 53 | /* Close without letting the failure we are about to report be overwritten */ | 60 | /* Close without letting the failure we are about to report be overwritten */ |
diff --git a/ot_socket.h b/ot_socket.h index f73a5c9..9340d0d 100644 --- a/ot_socket.h +++ b/ot_socket.h | |||
| @@ -39,6 +39,10 @@ int socket_is_connected(int s); | |||
| 39 | int socket_accept(int s, ot_ip6 ip, uint16_t *port); | 39 | int socket_accept(int s, ot_ip6 ip, uint16_t *port); |
| 40 | int socket_set_blocking(int s, int blocking); | 40 | int socket_set_blocking(int s, int blocking); |
| 41 | 41 | ||
| 42 | /* Bound how long a blocking read waits, so the thread sitting in one gets to | ||
| 43 | look at the running flag now and then */ | ||
| 44 | int socket_set_timeout(int s, int seconds); | ||
| 45 | |||
| 42 | ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); | 46 | ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); |
| 43 | ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port); | 47 | ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port); |
| 44 | 48 | ||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include <stdio.h> | 9 | #include <stdio.h> |
| 10 | #include <stdlib.h> | 10 | #include <stdlib.h> |
| 11 | #include <string.h> | 11 | #include <string.h> |
| 12 | #include <sys/socket.h> | ||
| 12 | 13 | ||
| 13 | /* Opentracker */ | 14 | /* Opentracker */ |
| 14 | #include "ot_ip.h" | 15 | #include "ot_ip.h" |
| @@ -19,6 +20,17 @@ | |||
| 19 | #include "ot_udp.h" | 20 | #include "ot_udp.h" |
| 20 | #include "trackerlogic.h" | 21 | #include "trackerlogic.h" |
| 21 | 22 | ||
| 23 | /* How long a worker may sit in a read before it looks at the running flag */ | ||
| 24 | #define OT_UDP_WORKER_PATIENCE 1 | ||
| 25 | |||
| 26 | struct udp_worker { | ||
| 27 | pthread_t thread; | ||
| 28 | int64_t sock; | ||
| 29 | }; | ||
| 30 | |||
| 31 | static struct udp_worker *g_workers; | ||
| 32 | static size_t g_worker_count; | ||
| 33 | |||
| 22 | static uint32_t g_rijndael_round_key[44] = {0}; | 34 | static uint32_t g_rijndael_round_key[44] = {0}; |
| 23 | static volatile uint32_t g_key_of_the_hour[2] = {0}; | 35 | static volatile uint32_t g_key_of_the_hour[2] = {0}; |
| 24 | static volatile ot_time g_hour_of_the_key; | 36 | static volatile ot_time g_hour_of_the_key; |
| @@ -236,11 +248,37 @@ void udp_init(void) { | |||
| 236 | } | 248 | } |
| 237 | 249 | ||
| 238 | void udp_worker_install(int64_t sock) { | 250 | void udp_worker_install(int64_t sock) { |
| 239 | pthread_t thread_id; | 251 | struct udp_worker *grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers)); |
| 252 | |||
| 253 | if (!grown) { | ||
| 254 | fprintf(stderr, "Warning: out of memory installing a udp worker, this socket stays with the workers already running.\n"); | ||
| 255 | return; | ||
| 256 | } | ||
| 257 | g_workers = grown; | ||
| 240 | 258 | ||
| 241 | #ifdef _DEBUG | 259 | #ifdef _DEBUG |
| 242 | fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock); | 260 | fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock); |
| 243 | #endif | 261 | #endif |
| 244 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); | 262 | socket_set_blocking((int)sock, 1); |
| 263 | socket_set_timeout((int)sock, OT_UDP_WORKER_PATIENCE); | ||
| 264 | |||
| 265 | g_workers[g_worker_count].sock = sock; | ||
| 266 | if (!pthread_create(&g_workers[g_worker_count].thread, NULL, udp_worker, (void *)sock)) | ||
| 267 | ++g_worker_count; | ||
| 268 | } | ||
| 269 | |||
| 270 | void udp_workers_deinit(void) { | ||
| 271 | size_t i; | ||
| 272 | |||
| 273 | /* The receive timeout bounds this on its own, shutting the socket down | ||
| 274 | cuts it short wherever the platform wakes a blocked reader */ | ||
| 275 | for (i = 0; i < g_worker_count; ++i) | ||
| 276 | shutdown((int)g_workers[i].sock, SHUT_RD); | ||
| 277 | for (i = 0; i < g_worker_count; ++i) | ||
| 278 | pthread_join(g_workers[i].thread, NULL); | ||
| 279 | |||
| 280 | free(g_workers); | ||
| 281 | g_workers = NULL; | ||
| 282 | g_worker_count = 0; | ||
| 245 | } | 283 | } |
| 246 | 284 | ||
| @@ -13,6 +13,9 @@ | |||
| 13 | void udp_init(void); | 13 | void udp_init(void); |
| 14 | /* One thread reading one socket. Called once per worker. */ | 14 | /* One thread reading one socket. Called once per worker. */ |
| 15 | void udp_worker_install(int64_t sock); | 15 | void udp_worker_install(int64_t sock); |
| 16 | |||
| 17 | /* Stop every worker installed above and wait for it to be gone */ | ||
| 18 | void udp_workers_deinit(void); | ||
| 16 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); | 19 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); |
| 17 | 20 | ||
| 18 | #endif | 21 | #endif |
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 | |||
| 3 | The rest of the suite stops a tracker too, so a thread left behind already | ||
| 4 | shows up in teardown as the complaint that the tracker had to be killed. | ||
| 5 | These ask for the arrangements that park a thread in a blocking read in the | ||
| 6 | first place, and they time the exit, because a reader woken only by its own | ||
| 7 | receive timeout passes a correctness check while taking a second over it. | ||
| 8 | """ | ||
| 9 | |||
| 10 | import time | ||
| 11 | import unittest | ||
| 12 | |||
| 13 | from 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 | ||
| 18 | PATIENCE = 3.0 | ||
| 19 | |||
| 20 | |||
| 21 | def 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 | |||
| 37 | class 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 | |||
| 48 | class 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_socket.c b/tests/t_socket.c index 8eef7fd..ede8ae0 100644 --- a/tests/t_socket.c +++ b/tests/t_socket.c | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include <poll.h> | 10 | #include <poll.h> |
| 11 | #include <string.h> | 11 | #include <string.h> |
| 12 | #include <sys/socket.h> | 12 | #include <sys/socket.h> |
| 13 | #include <time.h> | ||
| 13 | #include <unistd.h> | 14 | #include <unistd.h> |
| 14 | 15 | ||
| 15 | /* Opentracker */ | 16 | /* Opentracker */ |
| @@ -144,6 +145,44 @@ static void case_udp_roundtrip(void) { | |||
| 144 | close(s); | 145 | close(s); |
| 145 | } | 146 | } |
| 146 | 147 | ||
| 148 | /* udp_workers_deinit relies on a worker parked in a read coming back. The | ||
| 149 | receive timeout is what guarantees that; shutting the socket down is what | ||
| 150 | makes it prompt where the platform allows it. Neither is uniform across the | ||
| 151 | systems opentracker runs on, so both are checked rather than assumed. */ | ||
| 152 | static void case_blocking_read_comes_back(void) { | ||
| 153 | char buf[32], ip[16], own_ip[16]; | ||
| 154 | uint16_t port = 0, own_port = 0; | ||
| 155 | time_t started; | ||
| 156 | int s; | ||
| 157 | |||
| 158 | test_case("a receive timeout does not delay a datagram that is already there"); | ||
| 159 | s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); | ||
| 160 | CHECK(s != -1); | ||
| 161 | CHECK_INT(socket_set_blocking(s, 1), 0); | ||
| 162 | CHECK_INT(socket_set_timeout(s, 1), 0); | ||
| 163 | bound_address(s, own_ip, &own_port); | ||
| 164 | CHECK_INT(socket_send(s, "opentracker", 11, own_ip, own_port), 11); | ||
| 165 | CHECK_INT(socket_recv(s, buf, sizeof(buf), ip, &port), 11); | ||
| 166 | |||
| 167 | test_case("and ends a read that would otherwise never return"); | ||
| 168 | started = time(NULL); | ||
| 169 | CHECK_INT(socket_recv(s, buf, sizeof(buf), ip, &port), -1); | ||
| 170 | CHECK(errno == EAGAIN || errno == EWOULDBLOCK); | ||
| 171 | CHECK(time(NULL) - started <= 3); | ||
| 172 | close(s); | ||
| 173 | |||
| 174 | test_case("shutting the socket down ends one too"); | ||
| 175 | s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); | ||
| 176 | CHECK(s != -1); | ||
| 177 | CHECK_INT(socket_set_blocking(s, 1), 0); | ||
| 178 | CHECK_INT(socket_set_timeout(s, 1), 0); | ||
| 179 | shutdown(s, SHUT_RD); | ||
| 180 | started = time(NULL); | ||
| 181 | CHECK(socket_recv(s, buf, sizeof(buf), ip, &port) <= 0); | ||
| 182 | CHECK(time(NULL) - started <= 3); | ||
| 183 | close(s); | ||
| 184 | } | ||
| 185 | |||
| 147 | /* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however | 186 | /* 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 | 187 | 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 | 188 | and the BSDs fill a plain sockaddr_in, so this is the case that tells the |
| @@ -293,6 +332,7 @@ void test_socket(void) { | |||
| 293 | case_sharing_is_opt_in(); | 332 | case_sharing_is_opt_in(); |
| 294 | #endif | 333 | #endif |
| 295 | case_udp_roundtrip(); | 334 | case_udp_roundtrip(); |
| 335 | case_blocking_read_comes_back(); | ||
| 296 | case_udp_v4mapped(); | 336 | case_udp_v4mapped(); |
| 297 | case_tcp_roundtrip(); | 337 | case_tcp_roundtrip(); |
| 298 | case_connect_refused(); | 338 | case_connect_refused(); |
