diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-28 17:11:33 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-28 17:11:33 +0100 |
| commit | 9ec918187ab65488ebcce427f5a14a6b38e87b78 (patch) | |
| tree | 4110ae8b03a67628cd020675159b64c765fb4f07 /tests/udp_wake_probe.c | |
| parent | f3f640b6bcf10ba463fc81cec97cfa212c520cfe (diff) | |
Add load tests to sanitize check the trackerlogic, based on udp requests
Diffstat (limited to 'tests/udp_wake_probe.c')
| -rw-r--r-- | tests/udp_wake_probe.c | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/tests/udp_wake_probe.c b/tests/udp_wake_probe.c new file mode 100644 index 0000000..8a0f7a8 --- /dev/null +++ b/tests/udp_wake_probe.c | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | /* Three 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. | ||
| 3 | |||
| 4 | This decides how udp workers can be stopped. They sit in recvfrom and are | ||
| 5 | never joined today, and the sockets of a SO_REUSEPORT group all share one | ||
| 6 | address, so a worker cannot be singled out by sending it a datagram. | ||
| 7 | |||
| 8 | cc -O2 -pthread -o /tmp/wake tests/udp_wake_probe.c && /tmp/wake | ||
| 9 | |||
| 10 | On Linux 6.1: close does not wake it, shutdown does, SO_RCVTIMEO does. */ | ||
| 11 | #include <errno.h> | ||
| 12 | #include <netinet/in.h> | ||
| 13 | #include <pthread.h> | ||
| 14 | #include <stdatomic.h> | ||
| 15 | #include <stdio.h> | ||
| 16 | #include <string.h> | ||
| 17 | #include <sys/socket.h> | ||
| 18 | #include <sys/time.h> | ||
| 19 | #include <unistd.h> | ||
| 20 | |||
| 21 | static _Atomic int returned; | ||
| 22 | static _Atomic int saved_errno; | ||
| 23 | static int sock; | ||
| 24 | |||
| 25 | static void *reader(void *args) { | ||
| 26 | char buffer[64]; | ||
| 27 | (void)args; | ||
| 28 | if (recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL) < 0) | ||
| 29 | atomic_store(&saved_errno, errno); | ||
| 30 | atomic_store(&returned, 1); | ||
| 31 | return NULL; | ||
| 32 | } | ||
| 33 | |||
| 34 | static int bind_udp(int timeout_ms) { | ||
| 35 | struct sockaddr_in address; | ||
| 36 | int s = socket(AF_INET, SOCK_DGRAM, 0); | ||
| 37 | |||
| 38 | memset(&address, 0, sizeof(address)); | ||
| 39 | address.sin_family = AF_INET; | ||
| 40 | address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
| 41 | address.sin_port = 0; | ||
| 42 | bind(s, (struct sockaddr *)&address, sizeof(address)); | ||
| 43 | |||
| 44 | if (timeout_ms) { | ||
| 45 | struct timeval how_long; | ||
| 46 | how_long.tv_sec = timeout_ms / 1000; | ||
| 47 | how_long.tv_usec = (timeout_ms % 1000) * 1000; | ||
| 48 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &how_long, sizeof(how_long)); | ||
| 49 | } | ||
| 50 | return s; | ||
| 51 | } | ||
| 52 | |||
| 53 | static void try_it(const char *name, int timeout_ms, int do_close, int do_shutdown) { | ||
| 54 | pthread_t thread; | ||
| 55 | int waited; | ||
| 56 | |||
| 57 | atomic_store(&returned, 0); | ||
| 58 | atomic_store(&saved_errno, 0); | ||
| 59 | sock = bind_udp(timeout_ms); | ||
| 60 | pthread_create(&thread, NULL, reader, NULL); | ||
| 61 | usleep(300000); /* let it reach recvfrom */ | ||
| 62 | |||
| 63 | if (do_shutdown) | ||
| 64 | shutdown(sock, SHUT_RD); | ||
| 65 | if (do_close) | ||
| 66 | close(sock); | ||
| 67 | |||
| 68 | for (waited = 0; waited < 2000 && !atomic_load(&returned); ++waited) | ||
| 69 | usleep(1000); | ||
| 70 | |||
| 71 | if (atomic_load(&returned)) { | ||
| 72 | printf(" %-24s returned after %4d ms, errno %s\n", name, waited, | ||
| 73 | atomic_load(&saved_errno) ? strerror(atomic_load(&saved_errno)) : "none"); | ||
| 74 | pthread_join(thread, NULL); | ||
| 75 | } else { | ||
| 76 | printf(" %-24s STILL BLOCKED after 2000 ms\n", name); | ||
| 77 | /* unblock it so the probe can finish */ | ||
| 78 | struct sockaddr_in to; | ||
| 79 | socklen_t length = sizeof(to); | ||
| 80 | int poke = socket(AF_INET, SOCK_DGRAM, 0); | ||
| 81 | if (!getsockname(sock, (struct sockaddr *)&to, &length)) | ||
| 82 | sendto(poke, "x", 1, 0, (struct sockaddr *)&to, length); | ||
| 83 | close(poke); | ||
| 84 | usleep(200000); | ||
| 85 | if (atomic_load(&returned)) | ||
| 86 | pthread_join(thread, NULL); | ||
| 87 | else | ||
| 88 | pthread_detach(thread); | ||
| 89 | } | ||
| 90 | if (!do_close) | ||
| 91 | close(sock); | ||
| 92 | } | ||
| 93 | |||
| 94 | int main(void) { | ||
| 95 | try_it("close() the socket", 0, 1, 0); | ||
| 96 | try_it("shutdown(SHUT_RD)", 0, 0, 1); | ||
| 97 | try_it("SO_RCVTIMEO 500 ms", 500, 0, 0); | ||
| 98 | return 0; | ||
| 99 | } | ||
