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 = ▮ 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, ©_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 }