test_concurrent_stop.c (10063B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007, 2009, 2011, 2015, 2016 Christian Grothoff 4 Copyright (C) 2014-2022 Evgeny Grin (Karlson2k) 5 6 libmicrohttpd is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published 8 by the Free Software Foundation; either version 3, or (at your 9 option) any later version. 10 11 libmicrohttpd is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with libmicrohttpd; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @file test_concurrent_stop.c 24 * @brief test stopping server while concurrent GETs are ongoing 25 * @author Christian Grothoff 26 * @author Karlson2k (Evgeny Grin) 27 */ 28 #include "MHD_config.h" 29 #include "platform.h" 30 #include <curl/curl.h> 31 #include <microhttpd.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <time.h> 35 #include <pthread.h> 36 37 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 38 test into a marked, classifiable test error (TESTING.md, P5). */ 39 #include "mhd_panic_tripwire.h" 40 41 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 42 #undef MHD_CPU_COUNT 43 #endif 44 #if ! defined(MHD_CPU_COUNT) 45 #define MHD_CPU_COUNT 2 46 #endif 47 48 /** 49 * How many requests do we do in parallel? 50 */ 51 #if SIZEOF_SIZE_T >= 8 || MHD_CPU_COUNT < 8 52 # define PAR (MHD_CPU_COUNT * 4) 53 #elif MHD_CPU_COUNT < 16 54 /* Limit load */ 55 # define PAR (MHD_CPU_COUNT * 2) 56 #else 57 /* Limit load */ 58 # define PAR (MHD_CPU_COUNT * 1) 59 #endif 60 61 /** 62 * Do we use HTTP 1.1? 63 */ 64 static int oneone; 65 66 /** 67 * Response to return (re-used). 68 */ 69 static struct MHD_Response *response; 70 71 /** 72 * Continue generating new requests? 73 */ 74 static volatile int continue_requesting; 75 76 /** 77 * Continue waiting in watchdog thread? 78 */ 79 static volatile int watchdog_continue; 80 81 static const char *watchdog_obj; 82 83 /** 84 * Indicate that client detected error 85 */ 86 static volatile CURLcode client_error; 87 88 static void * 89 thread_watchdog (void *param) 90 { 91 int seconds_passed; 92 const int timeout_val = (int) (intptr_t) param; 93 94 seconds_passed = 0; 95 while (watchdog_continue) /* Poor threads sync, but works for testing. */ 96 { 97 if (0 == sleep (1)) /* Poor accuracy, but enough for testing. */ 98 seconds_passed++; 99 if (timeout_val < seconds_passed) 100 { 101 fprintf (stderr, "%s timeout expired.\n", watchdog_obj ? watchdog_obj : 102 "Watchdog"); 103 fflush (stderr); 104 _exit (16); 105 } 106 } 107 return NULL; 108 } 109 110 111 static pthread_t watchdog_tid; 112 113 static void 114 start_watchdog (int timeout, const char *obj_name) 115 { 116 watchdog_continue = 1; 117 watchdog_obj = obj_name; 118 if (0 != pthread_create (&watchdog_tid, NULL, &thread_watchdog, 119 (void *) (intptr_t) timeout)) 120 { 121 fprintf (stderr, "Failed to start watchdog.\n"); 122 _exit (99); 123 } 124 } 125 126 127 static void 128 stop_watchdog (void) 129 { 130 watchdog_continue = 0; 131 if (0 != pthread_join (watchdog_tid, NULL)) 132 { 133 fprintf (stderr, "Failed to stop watchdog.\n"); 134 _exit (99); 135 } 136 } 137 138 139 static size_t 140 copyBuffer (void *ptr, 141 size_t size, size_t nmemb, 142 void *ctx) 143 { 144 (void) ptr; (void) ctx; /* Unused. Silent compiler warning. */ 145 return size * nmemb; 146 } 147 148 149 static enum MHD_Result 150 ahc_echo (void *cls, 151 struct MHD_Connection *connection, 152 const char *url, 153 const char *method, 154 const char *version, 155 const char *upload_data, 156 size_t *upload_data_size, 157 void **req_cls) 158 { 159 static int marker; 160 enum MHD_Result ret; 161 (void) cls; 162 (void) url; (void) version; /* Unused. Silent compiler warning. */ 163 (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ 164 165 if (0 != strcmp (MHD_HTTP_METHOD_GET, method)) 166 return MHD_NO; /* unexpected method */ 167 if (&marker != *req_cls) 168 { 169 *req_cls = ▮ 170 return MHD_YES; 171 } 172 *req_cls = NULL; 173 ret = MHD_queue_response (connection, 174 MHD_HTTP_OK, 175 response); 176 if (ret == MHD_NO) 177 abort (); 178 return ret; 179 } 180 181 182 static void * 183 thread_gets (void *param) 184 { 185 CURL *c; 186 CURLcode errornum; 187 char *const url = (char *) param; 188 189 c = NULL; 190 c = curl_easy_init (); 191 if (NULL == c) 192 { 193 fprintf (stderr, "curl_easy_init failed.\n"); 194 _exit (99); 195 } 196 curl_easy_setopt (c, CURLOPT_URL, url); 197 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 198 curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL); 199 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 200 curl_easy_setopt (c, CURLOPT_TIMEOUT, 2L); 201 if (oneone) 202 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 203 else 204 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 205 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 2L); 206 /* NOTE: use of CONNECTTIMEOUT without also 207 setting NOSIGNAL results in really weird 208 crashes on my system! */ 209 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 210 while (continue_requesting) 211 { 212 errornum = curl_easy_perform (c); 213 if (CURLE_OK != errornum) 214 { 215 curl_easy_cleanup (c); 216 client_error = errornum; 217 return NULL; 218 } 219 } 220 curl_easy_cleanup (c); 221 return NULL; 222 } 223 224 225 static void * 226 do_gets (void *param) 227 { 228 int j; 229 pthread_t par[PAR]; 230 char url[64]; 231 uint16_t port = (uint16_t) (intptr_t) param; 232 233 snprintf (url, 234 sizeof (url), 235 "http://127.0.0.1:%u/hello_world", 236 (unsigned int) port); 237 238 for (j = 0; j < PAR; j++) 239 { 240 if (0 != pthread_create (&par[j], NULL, &thread_gets, (void *) url)) 241 { 242 fprintf (stderr, "pthread_create failed.\n"); 243 continue_requesting = 0; 244 for (j--; j >= 0; j--) 245 { 246 pthread_join (par[j], NULL); 247 } 248 _exit (99); 249 } 250 } 251 (void) sleep (1); 252 for (j = 0; j < PAR; j++) 253 { 254 pthread_join (par[j], NULL); 255 } 256 return NULL; 257 } 258 259 260 static pthread_t 261 start_gets (uint16_t port) 262 { 263 pthread_t tid; 264 continue_requesting = 1; 265 if (0 != pthread_create (&tid, NULL, &do_gets, (void *) (intptr_t) port)) 266 { 267 fprintf (stderr, "pthread_create failed.\n"); 268 _exit (99); 269 } 270 return tid; 271 } 272 273 274 static unsigned int 275 testMultithreadedGet (uint16_t port, 276 uint32_t poll_flag) 277 { 278 struct MHD_Daemon *d; 279 pthread_t p; 280 unsigned int result; 281 282 result = 0; 283 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION 284 | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 285 | (enum MHD_FLAG) poll_flag, 286 port, 287 NULL, NULL, 288 &ahc_echo, NULL, 289 MHD_OPTION_END); 290 if (d == NULL) 291 return 16; 292 if (0 == port) 293 { 294 const union MHD_DaemonInfo *dinfo; 295 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 296 if ((NULL == dinfo) || (0 == dinfo->port) ) 297 { 298 MHD_stop_daemon (d); return 32; 299 } 300 port = dinfo->port; 301 } 302 client_error = CURLE_OK; /* clear client error state */ 303 p = start_gets (port); 304 (void) sleep (1); 305 start_watchdog (10, "daemon_stop() in testMultithreadedGet"); 306 if (CURLE_OK != client_error) /* poor sync, but enough for test */ 307 { 308 result = 64; 309 fprintf (stderr, "libcurl reported at least one error: \"%s\"\n", 310 curl_easy_strerror (client_error)); 311 } 312 MHD_stop_daemon (d); 313 stop_watchdog (); 314 continue_requesting = 0; 315 pthread_join (p, NULL); 316 return result; 317 } 318 319 320 static unsigned int 321 testMultithreadedPoolGet (uint16_t port, 322 uint32_t poll_flag) 323 { 324 struct MHD_Daemon *d; 325 pthread_t p; 326 unsigned int result; 327 328 result = 0; 329 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 330 | (enum MHD_FLAG) poll_flag, 331 port, 332 NULL, NULL, 333 &ahc_echo, NULL, 334 MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT, 335 MHD_OPTION_END); 336 if (d == NULL) 337 return 16; 338 if (0 == port) 339 { 340 const union MHD_DaemonInfo *dinfo; 341 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 342 if ((NULL == dinfo) || (0 == dinfo->port) ) 343 { 344 MHD_stop_daemon (d); return 32; 345 } 346 port = dinfo->port; 347 } 348 client_error = CURLE_OK; /* clear client error state */ 349 p = start_gets (port); 350 (void) sleep (1); 351 start_watchdog (10, "daemon_stop() in testMultithreadedPoolGet"); 352 if (CURLE_OK != client_error) /* poor sync, but enough for test */ 353 { 354 result = 64; 355 fprintf (stderr, "libcurl reported at least one error: \"%s\"\n", 356 curl_easy_strerror (client_error)); 357 } 358 MHD_stop_daemon (d); 359 stop_watchdog (); 360 continue_requesting = 0; 361 pthread_join (p, NULL); 362 return result; 363 } 364 365 366 int 367 main (int argc, char *const *argv) 368 { 369 unsigned int errorCount = 0; 370 uint16_t port; 371 (void) argc; /* Unused. Silent compiler warning. */ 372 (void) argv; /* Unused. Silent compiler warning. */ 373 374 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 375 port = 0; 376 else 377 port = 1142; 378 379 /* Do reuse connection, otherwise all available local ports may exhausted. */ 380 oneone = 1; 381 382 if ((0 != port) && oneone) 383 port += 5; 384 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 385 return 2; 386 response = MHD_create_response_from_buffer_copy (strlen ("/hello_world"), 387 "/hello_world"); 388 errorCount += testMultithreadedGet (port, 0); 389 if (0 != port) 390 port++; 391 errorCount += testMultithreadedPoolGet (port, 0); 392 MHD_destroy_response (response); 393 if (errorCount != 0) 394 fprintf (stderr, "Error (code: %u)\n", errorCount); 395 curl_global_cleanup (); 396 return (0 == errorCount) ? 0 : 1; /* 0 == pass */ 397 }