diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-29 01:16:53 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-29 01:16:53 +0100 |
| commit | d3a628a8ec1d4b1a2ec080328a41c21e48f04d06 (patch) | |
| tree | 0ad651dbb9a0a9763a30c995c8a652e3862cda63 /tests/udp_wake_probe.c | |
| parent | 0f95c7d1056cab5bd8c6334cf601349ac4631457 (diff) | |
add wake-up tester that helped figuring out how to tickle hanging udp readers
Diffstat (limited to 'tests/udp_wake_probe.c')
| -rw-r--r-- | tests/udp_wake_probe.c | 42 |
1 files changed, 32 insertions, 10 deletions
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 | |||
| 21 | static _Atomic int returned; | 28 | static _Atomic int returned; |
| 22 | static _Atomic int saved_errno; | 29 | static _Atomic int saved_errno; |
| 23 | static int sock; | 30 | static 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. */ | ||
| 44 | static void noop(int s) { (void)s; } | ||
| 45 | |||
| 34 | static int bind_udp(int timeout_ms) { | 46 | static 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 | ||
| 53 | static void try_it(const char *name, int timeout_ms, int do_close, int do_shutdown) { | 65 | static 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 | ||
| 94 | int main(void) { | 108 | int 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 | } |
