libmicrohttpd

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

test_suspend_resume_thread.c (9719B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2026 Christian Grothoff
      4 
      5      libmicrohttpd is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 2, or (at your
      8      option) any later version.
      9 
     10      libmicrohttpd is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libmicrohttpd; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 */
     20 /**
     21  * @file test_suspend_resume_thread.c
     22  * @brief  Testcase for suspend/resume with a thread per connection
     23  *
     24  * The content reader suspends the connection and returns zero, which is
     25  * the pattern microhttpd.h prescribes for "no body data yet".  The
     26  * resume comes from a separate thread, as it would from an application
     27  * that is waiting on some other I/O.  Several clients run at once and
     28  * every response stalls repeatedly, so a single lost resume anywhere
     29  * hangs the connection it belongs to and the body arrives short.
     30  *
     31  * @author Christian Grothoff
     32  */
     33 #include "mhd_options.h"
     34 #include <stdlib.h>
     35 #include <stdio.h>
     36 #include <string.h>
     37 #include <pthread.h>
     38 #include <curl/curl.h>
     39 #include <microhttpd.h>
     40 
     41 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this
     42    test into a marked, classifiable test error (TESTING.md, P5). */
     43 #include "mhd_panic_tripwire.h"
     44 
     45 #ifndef WINDOWS
     46 #include <unistd.h>
     47 #endif
     48 
     49 /**
     50  * Number of clients to run against the daemon at the same time.
     51  */
     52 #define NUM_CLIENTS 4
     53 
     54 /**
     55  * Total size of the response body.
     56  */
     57 #define BODY_SIZE 64
     58 
     59 /**
     60  * Number of bytes the content reader hands out at a time.
     61  */
     62 #define CHUNK_SIZE 8
     63 
     64 /**
     65  * Number of times the content reader stalls before each chunk.
     66  */
     67 #define NUM_STALLS 3
     68 
     69 /**
     70  * The byte the response body is made of.
     71  */
     72 #define BODY_FILL 'x'
     73 
     74 
     75 struct ReaderData
     76 {
     77   /**
     78    * Connection to suspend and resume.
     79    */
     80   struct MHD_Connection *connection;
     81 
     82   /**
     83    * Number of stalls left before the next chunk is handed out.
     84    */
     85   unsigned int stalls_left;
     86 };
     87 
     88 
     89 static uint16_t port;
     90 
     91 static volatile unsigned int panicked;
     92 
     93 
     94 _MHD_NORETURN static void
     95 test_panic_cb (void *cls,
     96                const char *file,
     97                unsigned int line,
     98                const char *reason)
     99 {
    100   (void) cls;
    101   fprintf (stderr,
    102            "PANIC: %s at %s:%u\n",
    103            (NULL != reason) ? reason : "",
    104            file,
    105            line);
    106   panicked = 1;
    107   exit (99);
    108 }
    109 
    110 
    111 static void *
    112 resume_thread (void *cls)
    113 {
    114   /* Give the connection's thread a chance to actually park itself,
    115      so that the resume has to travel between threads. */
    116   (void) usleep (1000);
    117   MHD_resume_connection (cls);
    118   return NULL;
    119 }
    120 
    121 
    122 static ssize_t
    123 content_reader (void *cls,
    124                 uint64_t pos,
    125                 char *buf,
    126                 size_t max)
    127 {
    128   struct ReaderData *data = cls;
    129   pthread_t tid;
    130 
    131   if (pos >= BODY_SIZE)
    132     return MHD_CONTENT_READER_END_OF_STREAM;
    133   if (0 != data->stalls_left)
    134   {
    135     /* No data yet.  Park the connection and report that, as
    136        documented for #MHD_ContentReaderCallback. */
    137     data->stalls_left--;
    138     MHD_suspend_connection (data->connection);
    139     if (0 != pthread_create (&tid,
    140                              NULL,
    141                              &resume_thread,
    142                              data->connection))
    143       return MHD_CONTENT_READER_END_WITH_ERROR;
    144     (void) pthread_detach (tid);
    145     return 0;
    146   }
    147   data->stalls_left = NUM_STALLS;
    148   if (max > CHUNK_SIZE)
    149     max = CHUNK_SIZE;
    150   if (max > (size_t) (BODY_SIZE - pos))
    151     max = (size_t) (BODY_SIZE - pos);
    152   memset (buf,
    153           BODY_FILL,
    154           max);
    155   return (ssize_t) max;
    156 }
    157 
    158 
    159 static void
    160 free_reader_data (void *cls)
    161 {
    162   free (cls);
    163 }
    164 
    165 
    166 static enum MHD_Result
    167 ahc_echo (void *cls,
    168           struct MHD_Connection *connection,
    169           const char *url,
    170           const char *method,
    171           const char *version,
    172           const char *upload_data,
    173           size_t *upload_data_size,
    174           void **req_cls)
    175 {
    176   static int marker;
    177   struct MHD_Response *response;
    178   struct ReaderData *data;
    179   enum MHD_Result ret;
    180   (void) cls; (void) url; (void) method; (void) version;
    181   (void) upload_data; (void) upload_data_size;
    182 
    183   if (&marker != *req_cls)
    184   {
    185     *req_cls = &marker;
    186     return MHD_YES;
    187   }
    188   data = malloc (sizeof (struct ReaderData));
    189   if (NULL == data)
    190     return MHD_NO;
    191   data->connection = connection;
    192   data->stalls_left = NUM_STALLS;
    193   response = MHD_create_response_from_callback (BODY_SIZE,
    194                                                 4096,
    195                                                 &content_reader,
    196                                                 data,
    197                                                 &free_reader_data);
    198   if (NULL == response)
    199   {
    200     free (data);
    201     return MHD_NO;
    202   }
    203   ret = MHD_queue_response (connection,
    204                             MHD_HTTP_OK,
    205                             response);
    206   MHD_destroy_response (response);
    207   return ret;
    208 }
    209 
    210 
    211 struct Buffer
    212 {
    213   size_t used;
    214   char data[2 * BODY_SIZE];
    215 };
    216 
    217 
    218 static size_t
    219 copy_buffer (void *ptr,
    220              size_t size,
    221              size_t nmemb,
    222              void *cls)
    223 {
    224   struct Buffer *buf = cls;
    225 
    226   if (0 == size * nmemb)
    227     return 0;
    228   if (buf->used + size * nmemb > sizeof (buf->data))
    229     return 0; /* overflow */
    230   memcpy (&buf->data[buf->used],
    231           ptr,
    232           size * nmemb);
    233   buf->used += size * nmemb;
    234   return size * nmemb;
    235 }
    236 
    237 
    238 /**
    239  * Fetch the response once.
    240  *
    241  * @param cls unused
    242  * @return NULL on success, non-NULL on failure
    243  */
    244 static void *
    245 client_thread (void *cls)
    246 {
    247   static int failure = 1;
    248   char url[128];
    249   struct Buffer buf;
    250   CURL *c;
    251   CURLcode errornum;
    252   (void) cls;
    253 
    254   memset (&buf, 0, sizeof (buf));
    255   c = curl_easy_init ();
    256   if (NULL == c)
    257     return &failure;
    258   snprintf (url,
    259             sizeof (url),
    260             "http://127.0.0.1:%u/",
    261             (unsigned int) port);
    262   curl_easy_setopt (c, CURLOPT_URL, url);
    263   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copy_buffer);
    264   curl_easy_setopt (c, CURLOPT_WRITEDATA, &buf);
    265   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
    266   curl_easy_setopt (c, CURLOPT_TIMEOUT, 30L);
    267   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 30L);
    268   curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    269   errornum = curl_easy_perform (c);
    270   curl_easy_cleanup (c);
    271   if (CURLE_OK != errornum)
    272   {
    273     fprintf (stderr,
    274              "curl_easy_perform() failed: `%s'\n",
    275              curl_easy_strerror (errornum));
    276     return &failure;
    277   }
    278   if (BODY_SIZE != buf.used)
    279   {
    280     fprintf (stderr,
    281              "Got %u bytes of body, expected %u.\n",
    282              (unsigned int) buf.used,
    283              (unsigned int) BODY_SIZE);
    284     return &failure;
    285   }
    286   if (BODY_SIZE != strspn (buf.data, "x"))
    287   {
    288     fprintf (stderr,
    289              "Body has unexpected content.\n");
    290     return &failure;
    291   }
    292   return NULL;
    293 }
    294 
    295 
    296 /**
    297  * Run all clients against a daemon started with the given flags.
    298  *
    299  * @param flags the flags to start the daemon with
    300  * @return 0 on success
    301  */
    302 static unsigned int
    303 test_daemon (unsigned int flags)
    304 {
    305   pthread_t clients[NUM_CLIENTS];
    306   struct MHD_Daemon *d;
    307   const union MHD_DaemonInfo *dinfo;
    308   void *res;
    309   unsigned int i;
    310   unsigned int started;
    311   unsigned int failures;
    312 
    313   d = MHD_start_daemon (flags
    314                         | MHD_USE_THREAD_PER_CONNECTION
    315                         | MHD_USE_INTERNAL_POLLING_THREAD
    316                         | MHD_ALLOW_SUSPEND_RESUME
    317                         | MHD_USE_ERROR_LOG,
    318                         0,
    319                         NULL, NULL,
    320                         &ahc_echo, NULL,
    321                         MHD_OPTION_END);
    322   if (NULL == d)
    323   {
    324     fprintf (stderr,
    325              "Failed to start daemon with flags %x.\n",
    326              flags);
    327     return 1;
    328   }
    329   dinfo = MHD_get_daemon_info (d,
    330                                MHD_DAEMON_INFO_BIND_PORT);
    331   if ( (NULL == dinfo) ||
    332        (0 == dinfo->port) )
    333   {
    334     MHD_stop_daemon (d);
    335     fprintf (stderr,
    336              "Failed to get the port number.\n");
    337     return 1;
    338   }
    339   port = dinfo->port;
    340 
    341   failures = 0;
    342   for (started = 0; started < NUM_CLIENTS; started++)
    343   {
    344     if (0 != pthread_create (&clients[started],
    345                              NULL,
    346                              &client_thread,
    347                              NULL))
    348     {
    349       fprintf (stderr,
    350                "Failed to create a client thread.\n");
    351       failures++;
    352       break;
    353     }
    354   }
    355   for (i = 0; i < started; i++)
    356   {
    357     res = NULL;
    358     if (0 != pthread_join (clients[i],
    359                            &res))
    360     {
    361       fprintf (stderr,
    362                "Failed to join a client thread.\n");
    363       failures++;
    364     }
    365     else if (NULL != res)
    366       failures++;
    367   }
    368   MHD_stop_daemon (d);
    369   return failures;
    370 }
    371 
    372 
    373 int
    374 main (int argc,
    375       char *const *argv)
    376 {
    377   unsigned int failures = 0;
    378   (void) argc; (void) argv;
    379 
    380   MHD_set_panic_func (&test_panic_cb,
    381                       NULL);
    382   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    383     return 2;
    384   /* Without an ITC, and with one; both use select() internally. */
    385   failures += test_daemon (0);
    386   failures += test_daemon (MHD_USE_ITC);
    387   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_POLL))
    388     failures += test_daemon (MHD_USE_POLL | MHD_USE_ITC);
    389   curl_global_cleanup ();
    390   if (0 != panicked)
    391     return 99;
    392   return (0 == failures) ? 0 : 1;
    393 }