libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

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 }