test_suspend_resume_epoll.c (17170B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2026 Christian Grothoff (and other contributing authors) 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Lesser General Public 7 License as published by the Free Software Foundation; either 8 version 2.1 of the License, or (at your option) any later version. 9 10 This library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 /** 20 * @file test_suspend_resume_epoll.c 21 * @brief Test for the suspend_resume_epoll example. The example itself 22 * is started as a child process and then driven over HTTP, so 23 * what is tested is exactly the code that ships as the example. 24 * 25 * The example parks every request for one second on a timerfd 26 * that lives in the application's own epoll set, next to the 27 * daemon's epoll FD. Checking that the right bytes come back is 28 * the easy part; the checks that matter are that the process 29 * burns no CPU while a connection is suspended, and that two 30 * requests are parked at the same time rather than one after the 31 * other. Both would still deliver identical bytes if suspending 32 * were broken. 33 * @author Christian Grothoff 34 */ 35 36 #include <sys/types.h> 37 #include <sys/socket.h> 38 #include <sys/wait.h> 39 #include <netinet/in.h> 40 #include <arpa/inet.h> 41 #include <curl/curl.h> 42 #include <errno.h> 43 #include <signal.h> 44 #include <stdint.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <time.h> 49 #include <unistd.h> 50 51 /** 52 * How long the example parks a request, in milliseconds. This is the 53 * one-second timer hard-coded in suspend_resume_epoll.c. 54 */ 55 #define PARK_MS 1000 56 57 /** 58 * Upper bound for any single request, in seconds. 59 */ 60 #define REQUEST_TIMEOUT 30 61 62 /** 63 * How long to wait for the freshly started example to answer, in 64 * milliseconds. 65 */ 66 #define STARTUP_TIMEOUT_MS 5000 67 68 /** 69 * How often to retry with a different port if the one we picked turned 70 * out to be taken after all. 71 */ 72 #define PORT_ATTEMPTS 5 73 74 /** 75 * Largest response body we are prepared to keep. 76 */ 77 #define MAX_BODY 4096 78 79 80 /** 81 * What one HTTP request collected. 82 */ 83 struct Fetch 84 { 85 /** 86 * The body, as far as it was received. 87 */ 88 char body[MAX_BODY]; 89 90 /** 91 * Number of valid bytes in @e body. 92 */ 93 size_t len; 94 95 /** 96 * Result of the transfer. 97 */ 98 CURLcode res; 99 }; 100 101 102 /** 103 * The example's process ID, or -1 once it has been reaped. 104 */ 105 static pid_t server_pid = -1; 106 107 /** 108 * The port the example was told to bind. 109 */ 110 static unsigned int server_port; 111 112 /** 113 * Number of checks that failed. 114 */ 115 static unsigned int failures; 116 117 118 /** 119 * Report the outcome of one check. 120 * 121 * @param ok non-zero if the check passed 122 * @param what what was being checked 123 */ 124 static void 125 check (int ok, 126 const char *what) 127 { 128 if (! ok) 129 failures++; 130 fprintf (stderr, 131 "%s: %s\n", 132 ok ? "PASS" : "FAIL", 133 what); 134 } 135 136 137 /** 138 * @return the current value of the monotonic clock, in milliseconds 139 */ 140 static long 141 now_ms (void) 142 { 143 struct timespec ts; 144 145 if (0 != clock_gettime (CLOCK_MONOTONIC, 146 &ts)) 147 return 0; 148 return (long) ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 149 } 150 151 152 /** 153 * Read the CPU time consumed by process @a pid so far. Only 154 * implemented for Linux; everywhere else the test simply skips the 155 * check that uses it. 156 * 157 * @param pid the process to look at 158 * @param[out] ms where to store the sum of user and system time in 159 * milliseconds 160 * @return non-zero on success 161 */ 162 static int 163 cpu_time_ms (pid_t pid, 164 long *ms) 165 { 166 #ifdef __linux__ 167 char path[64]; 168 char buf[1024]; 169 char *p; 170 FILE *f; 171 size_t got; 172 unsigned long utime; 173 unsigned long stime; 174 long ticks; 175 176 snprintf (path, 177 sizeof (path), 178 "/proc/%ld/stat", 179 (long) pid); 180 f = fopen (path, 181 "r"); 182 if (NULL == f) 183 return 0; 184 got = fread (buf, 185 1, 186 sizeof (buf) - 1, 187 f); 188 fclose (f); 189 if (0 == got) 190 return 0; 191 buf[got] = '\0'; 192 /* The second field is the executable name and may contain spaces and 193 parentheses, so start parsing behind its closing one. */ 194 p = strrchr (buf, 195 ')'); 196 if (NULL == p) 197 return 0; 198 if (2 != sscanf (p + 1, 199 " %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", 200 &utime, 201 &stime)) 202 return 0; 203 ticks = sysconf (_SC_CLK_TCK); 204 if (0 >= ticks) 205 return 0; 206 *ms = (long) ((utime + stime) * 1000 / (unsigned long) ticks); 207 return 1; 208 #else 209 (void) pid; 210 (void) ms; 211 return 0; 212 #endif 213 } 214 215 216 static size_t 217 write_cb (void *ptr, 218 size_t size, 219 size_t nmemb, 220 void *cls) 221 { 222 struct Fetch *f = cls; 223 size_t total = size * nmemb; 224 225 if (total > sizeof (f->body) - f->len - 1) 226 total = sizeof (f->body) - f->len - 1; 227 memcpy (&f->body[f->len], 228 ptr, 229 total); 230 f->len += total; 231 f->body[f->len] = '\0'; 232 return size * nmemb; 233 } 234 235 236 /** 237 * Create an easy handle that will GET @a path from the example. 238 * 239 * @param path the path to request 240 * @param[out] f where to store what is received; cleared here 241 * @return the handle, or NULL on error 242 */ 243 static CURL * 244 make_handle (const char *path, 245 struct Fetch *f) 246 { 247 CURL *curl; 248 char url[128]; 249 250 memset (f, 251 0, 252 sizeof (struct Fetch)); 253 f->res = CURLE_FAILED_INIT; 254 snprintf (url, 255 sizeof (url), 256 "http://127.0.0.1:%u%s", 257 server_port, 258 path); 259 curl = curl_easy_init (); 260 if (NULL == curl) 261 return NULL; 262 curl_easy_setopt (curl, CURLOPT_URL, url); 263 curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &write_cb); 264 curl_easy_setopt (curl, CURLOPT_WRITEDATA, f); 265 curl_easy_setopt (curl, CURLOPT_TIMEOUT, (long) REQUEST_TIMEOUT); 266 curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1L); 267 return curl; 268 } 269 270 271 /** 272 * Perform one GET against the example. 273 * 274 * @param path the path to request 275 * @param[out] f where to store what was received 276 * @return milliseconds the request took 277 */ 278 static long 279 fetch (const char *path, 280 struct Fetch *f) 281 { 282 CURL *curl; 283 long start; 284 285 curl = make_handle (path, 286 f); 287 if (NULL == curl) 288 return 0; 289 start = now_ms (); 290 f->res = curl_easy_perform (curl); 291 curl_easy_cleanup (curl); 292 return now_ms () - start; 293 } 294 295 296 /** 297 * Perform two GETs against the example at the same time. 298 * 299 * @param path1 the path for the first request 300 * @param path2 the path for the second request 301 * @param[out] f1 where to store what the first request received 302 * @param[out] f2 where to store what the second request received 303 * @return milliseconds until both were done 304 */ 305 static long 306 fetch_both (const char *path1, 307 const char *path2, 308 struct Fetch *f1, 309 struct Fetch *f2) 310 { 311 CURLM *multi; 312 CURL *c1; 313 CURL *c2; 314 CURLMsg *msg; 315 int running; 316 int left; 317 long start; 318 319 c1 = make_handle (path1, 320 f1); 321 c2 = make_handle (path2, 322 f2); 323 multi = curl_multi_init (); 324 if ( (NULL == c1) || 325 (NULL == c2) || 326 (NULL == multi) ) 327 { 328 if (NULL != c1) 329 curl_easy_cleanup (c1); 330 if (NULL != c2) 331 curl_easy_cleanup (c2); 332 if (NULL != multi) 333 curl_multi_cleanup (multi); 334 return 0; 335 } 336 curl_multi_add_handle (multi, c1); 337 curl_multi_add_handle (multi, c2); 338 start = now_ms (); 339 running = 1; 340 while (0 != running) 341 { 342 if (CURLM_OK != curl_multi_perform (multi, 343 &running)) 344 break; 345 if (0 == running) 346 break; 347 if (CURLM_OK != curl_multi_wait (multi, 348 NULL, 349 0, 350 100, 351 NULL)) 352 break; 353 if (now_ms () - start > REQUEST_TIMEOUT * 1000) 354 break; 355 } 356 while (NULL != (msg = curl_multi_info_read (multi, 357 &left))) 358 { 359 if (CURLMSG_DONE != msg->msg) 360 continue; 361 if (msg->easy_handle == c1) 362 f1->res = msg->data.result; 363 else if (msg->easy_handle == c2) 364 f2->res = msg->data.result; 365 } 366 curl_multi_remove_handle (multi, c1); 367 curl_multi_remove_handle (multi, c2); 368 curl_easy_cleanup (c1); 369 curl_easy_cleanup (c2); 370 curl_multi_cleanup (multi); 371 return now_ms () - start; 372 } 373 374 375 /** 376 * Ask the operating system for a port that is currently free. The 377 * example insists on being given a port number, so the test has to pick 378 * one; if the guess goes stale between here and the child's bind() the 379 * child exits at once and the caller simply tries again. 380 * 381 * @param[out] port where to store the port number 382 * @return non-zero on success 383 */ 384 static int 385 pick_free_port (unsigned int *port) 386 { 387 struct sockaddr_in addr; 388 socklen_t addrlen = sizeof (addr); 389 int sock; 390 391 sock = socket (AF_INET, 392 SOCK_STREAM, 393 0); 394 if (-1 == sock) 395 return 0; 396 memset (&addr, 397 0, 398 sizeof (addr)); 399 addr.sin_family = AF_INET; 400 addr.sin_addr.s_addr = htonl (INADDR_ANY); 401 addr.sin_port = 0; 402 if ( (0 != bind (sock, 403 (struct sockaddr *) &addr, 404 sizeof (addr))) || 405 (0 != getsockname (sock, 406 (struct sockaddr *) &addr, 407 &addrlen)) ) 408 { 409 close (sock); 410 return 0; 411 } 412 close (sock); 413 *port = ntohs (addr.sin_port); 414 return (0 != *port); 415 } 416 417 418 /** 419 * Start the example and wait until it answers. 420 * 421 * @return 1 on success, 0 if it could not be started, -1 if the binary 422 * could not be executed at all (in which case the test skips) 423 */ 424 static int 425 start_server (void) 426 { 427 unsigned int attempt; 428 429 for (attempt = 0; attempt < PORT_ATTEMPTS; attempt++) 430 { 431 char port_arg[16]; 432 long deadline; 433 434 if (! pick_free_port (&server_port)) 435 return 0; 436 snprintf (port_arg, 437 sizeof (port_arg), 438 "%u", 439 server_port); 440 server_pid = fork (); 441 if (-1 == server_pid) 442 return 0; 443 if (0 == server_pid) 444 { 445 execl ("./suspend_resume_epoll", 446 "suspend_resume_epoll", 447 port_arg, 448 (char *) NULL); 449 fprintf (stderr, 450 "Failed to exec ./suspend_resume_epoll: %s\n", 451 strerror (errno)); 452 _exit (77); 453 } 454 deadline = now_ms () + STARTUP_TIMEOUT_MS; 455 while (now_ms () < deadline) 456 { 457 struct Fetch f; 458 int status; 459 460 if (server_pid == waitpid (server_pid, 461 &status, 462 WNOHANG)) 463 { 464 /* The example gave up, most likely because the port was taken 465 after all. Unless it could not even be started, try again. */ 466 if (WIFEXITED (status) && (77 == WEXITSTATUS (status))) 467 { 468 server_pid = -1; 469 return -1; 470 } 471 server_pid = -1; 472 break; 473 } 474 (void) fetch ("/startup", 475 &f); 476 if (CURLE_OK == f.res) 477 return 1; 478 usleep (100 * 1000); 479 } 480 if (-1 != server_pid) 481 { 482 kill (server_pid, 483 SIGKILL); 484 waitpid (server_pid, 485 NULL, 486 0); 487 server_pid = -1; 488 return 0; /* it is running but not answering; retrying will not help */ 489 } 490 } 491 return 0; 492 } 493 494 495 /** 496 * The example echoes the requested URL back, but only after the timer 497 * it armed has fired. 498 */ 499 static void 500 test_echo (void) 501 { 502 struct Fetch f; 503 long ms; 504 505 ms = fetch ("/hello/world", 506 &f); 507 check (CURLE_OK == f.res, 508 "request completed"); 509 check (0 == strcmp (f.body, 510 "/hello/world"), 511 "example echoed the requested URL"); 512 /* Only a lower bound is checked: a loaded machine may take longer, 513 but nothing can make the one-second timer fire early. */ 514 fprintf (stderr, 515 "request was parked for %ld ms\n", 516 ms); 517 check (ms >= PARK_MS / 2, 518 "answer waited for the timer to fire"); 519 } 520 521 522 /** 523 * While the connection is parked the process must be asleep in 524 * epoll_wait(). This is the check that tells a suspended connection 525 * apart from an access handler that is polled in a loop --- both return 526 * the same bytes after the same delay. 527 */ 528 static void 529 test_idle_while_suspended (void) 530 { 531 struct Fetch f; 532 long cpu_before; 533 long cpu_after; 534 long ms; 535 536 if (! cpu_time_ms (server_pid, 537 &cpu_before)) 538 { 539 fprintf (stderr, 540 "SKIP: no way to read the server's CPU time on this " 541 "platform\n"); 542 return; 543 } 544 ms = fetch ("/idle", 545 &f); 546 check (CURLE_OK == f.res, 547 "request completed"); 548 if (! cpu_time_ms (server_pid, 549 &cpu_after)) 550 { 551 fprintf (stderr, 552 "SKIP: could not read the server's CPU time\n"); 553 return; 554 } 555 fprintf (stderr, 556 "server used %ld ms of CPU during %ld ms of waiting\n", 557 cpu_after - cpu_before, 558 ms); 559 /* "<=" rather than "<" so that a degenerate run in which nothing was 560 waited for at all is left for test_echo() to report, instead of 561 being blamed on CPU usage here. */ 562 check ((cpu_after - cpu_before) * 4 <= ms, 563 "server stayed idle while the connection was suspended"); 564 } 565 566 567 /** 568 * Suspending must not serialise requests: two connections parked at the 569 * same time have to come back after one timer period, not two. With a 570 * single-threaded daemon this only works because the connection is 571 * taken out of the event loop rather than blocking it. 572 */ 573 static void 574 test_two_at_once (void) 575 { 576 struct Fetch f1; 577 struct Fetch f2; 578 long ms; 579 580 ms = fetch_both ("/first", 581 "/second", 582 &f1, 583 &f2); 584 check ( (CURLE_OK == f1.res) && 585 (CURLE_OK == f2.res), 586 "both concurrent requests completed"); 587 check ( (0 == strcmp (f1.body, 588 "/first")) && 589 (0 == strcmp (f2.body, 590 "/second")), 591 "each concurrent request got its own answer"); 592 fprintf (stderr, 593 "two concurrent requests took %ld ms\n", 594 ms); 595 check (ms < 2 * PARK_MS - PARK_MS / 4, 596 "the two requests were parked at the same time"); 597 } 598 599 600 /** 601 * A client that hangs up while its connection is parked must not take 602 * the server down, and must not leak the timer either. 603 */ 604 static void 605 test_client_hangs_up (void) 606 { 607 struct sockaddr_in addr; 608 struct Fetch f; 609 static const char *req = "GET /gone HTTP/1.1\r\nHost: localhost\r\n\r\n"; 610 int sock; 611 612 sock = socket (AF_INET, 613 SOCK_STREAM, 614 0); 615 if (-1 == sock) 616 { 617 check (0, 618 "could not create a socket"); 619 return; 620 } 621 memset (&addr, 622 0, 623 sizeof (addr)); 624 addr.sin_family = AF_INET; 625 addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); 626 addr.sin_port = htons ((uint16_t) server_port); 627 if (0 != connect (sock, 628 (struct sockaddr *) &addr, 629 sizeof (addr))) 630 { 631 close (sock); 632 check (0, 633 "could not connect to the example"); 634 return; 635 } 636 if (-1 == send (sock, 637 req, 638 strlen (req), 639 0)) 640 { 641 close (sock); 642 check (0, 643 "could not send a request"); 644 return; 645 } 646 /* Hang up well before the timer fires, so that the connection is 647 still suspended when the FIN arrives. */ 648 usleep ((useconds_t) (PARK_MS / 4) * 1000); 649 close (sock); 650 651 (void) fetch ("/still/here", 652 &f); 653 check ( (CURLE_OK == f.res) && 654 (0 == strcmp (f.body, 655 "/still/here")), 656 "server survived a client that hung up while suspended"); 657 } 658 659 660 int 661 main (void) 662 { 663 int started; 664 665 if (0 != curl_global_init (CURL_GLOBAL_ALL)) 666 return 77; 667 started = start_server (); 668 if (1 != started) 669 { 670 fprintf (stderr, 671 "Could not start ./suspend_resume_epoll\n"); 672 curl_global_cleanup (); 673 return (-1 == started) ? 77 : 1; 674 } 675 fprintf (stderr, 676 "example listening on port %u\n", 677 server_port); 678 679 test_echo (); 680 test_idle_while_suspended (); 681 test_two_at_once (); 682 test_client_hangs_up (); 683 684 /* The example runs an endless loop by design, so there is nothing to 685 ask it to do but die. */ 686 kill (server_pid, 687 SIGKILL); 688 waitpid (server_pid, 689 NULL, 690 0); 691 server_pid = -1; 692 curl_global_cleanup (); 693 if (0 != failures) 694 fprintf (stderr, 695 "%u check(s) failed\n", 696 failures); 697 return (0 == failures) ? 0 : 1; 698 }