test_concurrent_stop.c (9896B)
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 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 38 #undef MHD_CPU_COUNT 39 #endif 40 #if ! defined(MHD_CPU_COUNT) 41 #define MHD_CPU_COUNT 2 42 #endif 43 44 /** 45 * How many requests do we do in parallel? 46 */ 47 #if SIZEOF_SIZE_T >= 8 || MHD_CPU_COUNT < 8 48 # define PAR (MHD_CPU_COUNT * 4) 49 #elif MHD_CPU_COUNT < 16 50 /* Limit load */ 51 # define PAR (MHD_CPU_COUNT * 2) 52 #else 53 /* Limit load */ 54 # define PAR (MHD_CPU_COUNT * 1) 55 #endif 56 57 /** 58 * Do we use HTTP 1.1? 59 */ 60 static int oneone; 61 62 /** 63 * Response to return (re-used). 64 */ 65 static struct MHD_Response *response; 66 67 /** 68 * Continue generating new requests? 69 */ 70 static volatile int continue_requesting; 71 72 /** 73 * Continue waiting in watchdog thread? 74 */ 75 static volatile int watchdog_continue; 76 77 static const char *watchdog_obj; 78 79 /** 80 * Indicate that client detected error 81 */ 82 static volatile CURLcode client_error; 83 84 static void * 85 thread_watchdog (void *param) 86 { 87 int seconds_passed; 88 const int timeout_val = (int) (intptr_t) param; 89 90 seconds_passed = 0; 91 while (watchdog_continue) /* Poor threads sync, but works for testing. */ 92 { 93 if (0 == sleep (1)) /* Poor accuracy, but enough for testing. */ 94 seconds_passed++; 95 if (timeout_val < seconds_passed) 96 { 97 fprintf (stderr, "%s timeout expired.\n", watchdog_obj ? watchdog_obj : 98 "Watchdog"); 99 fflush (stderr); 100 _exit (16); 101 } 102 } 103 return NULL; 104 } 105 106 107 static pthread_t watchdog_tid; 108 109 static void 110 start_watchdog (int timeout, const char *obj_name) 111 { 112 watchdog_continue = 1; 113 watchdog_obj = obj_name; 114 if (0 != pthread_create (&watchdog_tid, NULL, &thread_watchdog, 115 (void *) (intptr_t) timeout)) 116 { 117 fprintf (stderr, "Failed to start watchdog.\n"); 118 _exit (99); 119 } 120 } 121 122 123 static void 124 stop_watchdog (void) 125 { 126 watchdog_continue = 0; 127 if (0 != pthread_join (watchdog_tid, NULL)) 128 { 129 fprintf (stderr, "Failed to stop watchdog.\n"); 130 _exit (99); 131 } 132 } 133 134 135 static size_t 136 copyBuffer (void *ptr, 137 size_t size, size_t nmemb, 138 void *ctx) 139 { 140 (void) ptr; (void) ctx; /* Unused. Silent compiler warning. */ 141 return size * nmemb; 142 } 143 144 145 static enum MHD_Result 146 ahc_echo (void *cls, 147 struct MHD_Connection *connection, 148 const char *url, 149 const char *method, 150 const char *version, 151 const char *upload_data, 152 size_t *upload_data_size, 153 void **req_cls) 154 { 155 static int marker; 156 enum MHD_Result ret; 157 (void) cls; 158 (void) url; (void) version; /* Unused. Silent compiler warning. */ 159 (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ 160 161 if (0 != strcmp (MHD_HTTP_METHOD_GET, method)) 162 return MHD_NO; /* unexpected method */ 163 if (&marker != *req_cls) 164 { 165 *req_cls = ▮ 166 return MHD_YES; 167 } 168 *req_cls = NULL; 169 ret = MHD_queue_response (connection, 170 MHD_HTTP_OK, 171 response); 172 if (ret == MHD_NO) 173 abort (); 174 return ret; 175 } 176 177 178 static void * 179 thread_gets (void *param) 180 { 181 CURL *c; 182 CURLcode errornum; 183 char *const url = (char *) param; 184 185 c = NULL; 186 c = curl_easy_init (); 187 if (NULL == c) 188 { 189 fprintf (stderr, "curl_easy_init failed.\n"); 190 _exit (99); 191 } 192 curl_easy_setopt (c, CURLOPT_URL, url); 193 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 194 curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL); 195 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 196 curl_easy_setopt (c, CURLOPT_TIMEOUT, 2L); 197 if (oneone) 198 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 199 else 200 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 201 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 2L); 202 /* NOTE: use of CONNECTTIMEOUT without also 203 setting NOSIGNAL results in really weird 204 crashes on my system! */ 205 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 206 while (continue_requesting) 207 { 208 errornum = curl_easy_perform (c); 209 if (CURLE_OK != errornum) 210 { 211 curl_easy_cleanup (c); 212 client_error = errornum; 213 return NULL; 214 } 215 } 216 curl_easy_cleanup (c); 217 return NULL; 218 } 219 220 221 static void * 222 do_gets (void *param) 223 { 224 int j; 225 pthread_t par[PAR]; 226 char url[64]; 227 uint16_t port = (uint16_t) (intptr_t) param; 228 229 snprintf (url, 230 sizeof (url), 231 "http://127.0.0.1:%u/hello_world", 232 (unsigned int) port); 233 234 for (j = 0; j < PAR; j++) 235 { 236 if (0 != pthread_create (&par[j], NULL, &thread_gets, (void *) url)) 237 { 238 fprintf (stderr, "pthread_create failed.\n"); 239 continue_requesting = 0; 240 for (j--; j >= 0; j--) 241 { 242 pthread_join (par[j], NULL); 243 } 244 _exit (99); 245 } 246 } 247 (void) sleep (1); 248 for (j = 0; j < PAR; j++) 249 { 250 pthread_join (par[j], NULL); 251 } 252 return NULL; 253 } 254 255 256 static pthread_t 257 start_gets (uint16_t port) 258 { 259 pthread_t tid; 260 continue_requesting = 1; 261 if (0 != pthread_create (&tid, NULL, &do_gets, (void *) (intptr_t) port)) 262 { 263 fprintf (stderr, "pthread_create failed.\n"); 264 _exit (99); 265 } 266 return tid; 267 } 268 269 270 static unsigned int 271 testMultithreadedGet (uint16_t port, 272 uint32_t poll_flag) 273 { 274 struct MHD_Daemon *d; 275 pthread_t p; 276 unsigned int result; 277 278 result = 0; 279 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION 280 | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 281 | (enum MHD_FLAG) poll_flag, 282 port, 283 NULL, NULL, 284 &ahc_echo, NULL, 285 MHD_OPTION_END); 286 if (d == NULL) 287 return 16; 288 if (0 == port) 289 { 290 const union MHD_DaemonInfo *dinfo; 291 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 292 if ((NULL == dinfo) || (0 == dinfo->port) ) 293 { 294 MHD_stop_daemon (d); return 32; 295 } 296 port = dinfo->port; 297 } 298 client_error = CURLE_OK; /* clear client error state */ 299 p = start_gets (port); 300 (void) sleep (1); 301 start_watchdog (10, "daemon_stop() in testMultithreadedGet"); 302 if (CURLE_OK != client_error) /* poor sync, but enough for test */ 303 { 304 result = 64; 305 fprintf (stderr, "libcurl reported at least one error: \"%s\"\n", 306 curl_easy_strerror (client_error)); 307 } 308 MHD_stop_daemon (d); 309 stop_watchdog (); 310 continue_requesting = 0; 311 pthread_join (p, NULL); 312 return result; 313 } 314 315 316 static unsigned int 317 testMultithreadedPoolGet (uint16_t port, 318 uint32_t poll_flag) 319 { 320 struct MHD_Daemon *d; 321 pthread_t p; 322 unsigned int result; 323 324 result = 0; 325 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 326 | (enum MHD_FLAG) poll_flag, 327 port, 328 NULL, NULL, 329 &ahc_echo, NULL, 330 MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT, 331 MHD_OPTION_END); 332 if (d == NULL) 333 return 16; 334 if (0 == port) 335 { 336 const union MHD_DaemonInfo *dinfo; 337 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 338 if ((NULL == dinfo) || (0 == dinfo->port) ) 339 { 340 MHD_stop_daemon (d); return 32; 341 } 342 port = dinfo->port; 343 } 344 client_error = CURLE_OK; /* clear client error state */ 345 p = start_gets (port); 346 (void) sleep (1); 347 start_watchdog (10, "daemon_stop() in testMultithreadedPoolGet"); 348 if (CURLE_OK != client_error) /* poor sync, but enough for test */ 349 { 350 result = 64; 351 fprintf (stderr, "libcurl reported at least one error: \"%s\"\n", 352 curl_easy_strerror (client_error)); 353 } 354 MHD_stop_daemon (d); 355 stop_watchdog (); 356 continue_requesting = 0; 357 pthread_join (p, NULL); 358 return result; 359 } 360 361 362 int 363 main (int argc, char *const *argv) 364 { 365 unsigned int errorCount = 0; 366 uint16_t port; 367 (void) argc; /* Unused. Silent compiler warning. */ 368 (void) argv; /* Unused. Silent compiler warning. */ 369 370 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 371 port = 0; 372 else 373 port = 1142; 374 375 /* Do reuse connection, otherwise all available local ports may exhausted. */ 376 oneone = 1; 377 378 if ((0 != port) && oneone) 379 port += 5; 380 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 381 return 2; 382 response = MHD_create_response_from_buffer_copy (strlen ("/hello_world"), 383 "/hello_world"); 384 errorCount += testMultithreadedGet (port, 0); 385 if (0 != port) 386 port++; 387 errorCount += testMultithreadedPoolGet (port, 0); 388 MHD_destroy_response (response); 389 if (errorCount != 0) 390 fprintf (stderr, "Error (code: %u)\n", errorCount); 391 curl_global_cleanup (); 392 return (0 == errorCount) ? 0 : 1; /* 0 == pass */ 393 }