mhd_opt_matrix.c (15322B)
1 /* 2 This file is part of GNU libmicrohttpd 3 Copyright (C) 2026 Christian Grothoff 4 5 GNU libmicrohttpd 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 GNU libmicrohttpd. 17 If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 /** 21 * @file microhttpd/mhd_opt_matrix.c 22 * @brief The environment-driven daemon option matrix shared by the tests 23 * @author Christian Grothoff 24 */ 25 26 #include "platform.h" 27 #include <microhttpd.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <stdio.h> 31 32 #include "mhd_opt_matrix.h" 33 34 35 /** 36 * The built-in matrix of daemon option profiles. 37 * 38 * Every value of every dimension listed in the header appears at least once: 39 * 40 * * memory limit: 0 (default), 64, 128, 256, 512, 1024, 2048, 3072, 4096; 41 * * client discipline level: -3, -2, -1, 0, 1, 2, 3; 42 * * #MHD_OPTION_STRICT_FOR_CLIENT with a positive and with a negative value; 43 * * threading: external polling, one internal thread, thread-per-connection, 44 * thread pool; 45 * * polling backend: select(), poll(), epoll. 46 * 47 * Profile zero is "neutral": it is exactly the configuration the tests of 48 * this directory used before the matrix was introduced. 49 * 50 * The combination "thread-per-connection + epoll" is deliberately absent: MHD 51 * rejects it (daemon.c:8680). External polling is always paired with the 52 * select()-style interface, as that is the only one the tests can drive. 53 */ 54 static const struct MHD_OptMatrixProfile opt_profiles[] = { 55 /* name mem discp legacy threading 56 poll pool */ 57 { "default", 0, 0, 0, MHD_OPT_MATRIX_THR_INTERNAL, 58 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 59 { "mem-64", 64, 0, 0, MHD_OPT_MATRIX_THR_INTERNAL, 60 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 61 { "mem-128", 128, 0, 0, MHD_OPT_MATRIX_THR_INTERNAL, 62 MHD_OPT_MATRIX_POLL_POLL, 0 }, 63 { "mem-256", 256, 0, 0, MHD_OPT_MATRIX_THR_PER_CONNECTION, 64 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 65 { "mem-512", 512, 0, 0, MHD_OPT_MATRIX_THR_PER_CONNECTION, 66 MHD_OPT_MATRIX_POLL_POLL, 0 }, 67 { "mem-1024", 1024, 0, 0, MHD_OPT_MATRIX_THR_INTERNAL, 68 MHD_OPT_MATRIX_POLL_EPOLL, 0 }, 69 { "mem-2048", 2048, 0, 0, MHD_OPT_MATRIX_THR_POOL, 70 MHD_OPT_MATRIX_POLL_SELECT, 4 }, 71 { "mem-3072", 3072, 0, 0, MHD_OPT_MATRIX_THR_EXTERNAL, 72 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 73 { "mem-4096", 4096, 0, 0, MHD_OPT_MATRIX_THR_POOL, 74 MHD_OPT_MATRIX_POLL_EPOLL, 3 }, 75 { "strict-1", 0, 1, 0, MHD_OPT_MATRIX_THR_INTERNAL, 76 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 77 { "strict-2", 1024, 2, 0, MHD_OPT_MATRIX_THR_INTERNAL, 78 MHD_OPT_MATRIX_POLL_POLL, 0 }, 79 { "strict-3", 512, 3, 0, MHD_OPT_MATRIX_THR_INTERNAL, 80 MHD_OPT_MATRIX_POLL_EPOLL, 0 }, 81 { "lax-1", 512, -1, 0, MHD_OPT_MATRIX_THR_INTERNAL, 82 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 83 { "lax-2", 256, -2, 0, MHD_OPT_MATRIX_THR_PER_CONNECTION, 84 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 85 { "lax-3", 2048, -3, 0, MHD_OPT_MATRIX_THR_POOL, 86 MHD_OPT_MATRIX_POLL_SELECT, 4 }, 87 { "legacy-strict", 768, 1, 1, MHD_OPT_MATRIX_THR_INTERNAL, 88 MHD_OPT_MATRIX_POLL_SELECT, 0 }, 89 { "legacy-lax", 768, -1, 1, MHD_OPT_MATRIX_THR_EXTERNAL, 90 MHD_OPT_MATRIX_POLL_SELECT, 0 } 91 }; 92 93 /** 94 * The number of built-in profiles. 95 */ 96 #define NUM_PROFILES \ 97 ((unsigned int) (sizeof(opt_profiles) / sizeof(opt_profiles[0]))) 98 99 100 /** 101 * Non-zero once the environment has been read into @a env_profile. 102 */ 103 static int env_parsed; 104 105 /** 106 * Non-zero if the environment selects a profile at all. 107 */ 108 static int env_selected; 109 110 /** 111 * The profile derived from the environment. 112 */ 113 static struct MHD_OptMatrixProfile env_profile; 114 115 /** 116 * The name of @a env_profile. 117 */ 118 static char env_profile_name[64]; 119 120 121 unsigned int 122 mhd_opt_matrix_num_profiles (void) 123 { 124 return NUM_PROFILES; 125 } 126 127 128 const struct MHD_OptMatrixProfile * 129 mhd_opt_matrix_profile (unsigned int idx) 130 { 131 return opt_profiles + (idx % NUM_PROFILES); 132 } 133 134 135 const struct MHD_OptMatrixProfile * 136 mhd_opt_matrix_lookup (const char *name_or_idx) 137 { 138 unsigned int i; 139 140 if ((NULL == name_or_idx) || (0 == name_or_idx[0])) 141 return NULL; 142 for (i = 0; i < NUM_PROFILES; ++i) 143 { 144 if (0 == strcmp (name_or_idx, opt_profiles[i].name)) 145 return opt_profiles + i; 146 } 147 if (('0' <= name_or_idx[0]) && ('9' >= name_or_idx[0])) 148 { 149 char *end; 150 unsigned long v; 151 152 v = strtoul (name_or_idx, &end, 10); 153 if ((NULL != end) && (0 == *end) && (NUM_PROFILES > v)) 154 return opt_profiles + v; 155 } 156 return NULL; 157 } 158 159 160 /** 161 * Parse the threading mode name. 162 * 163 * @param s the name 164 * @param[out] out the parsed mode 165 * @return non-zero on success 166 */ 167 static int 168 parse_threading (const char *s, enum MHD_OptMatrixThreading *out) 169 { 170 if (0 == strcmp (s, "external")) 171 *out = MHD_OPT_MATRIX_THR_EXTERNAL; 172 else if (0 == strcmp (s, "internal")) 173 *out = MHD_OPT_MATRIX_THR_INTERNAL; 174 else if ((0 == strcmp (s, "per-connection")) || 175 (0 == strcmp (s, "tpc"))) 176 *out = MHD_OPT_MATRIX_THR_PER_CONNECTION; 177 else if (0 == strcmp (s, "pool")) 178 *out = MHD_OPT_MATRIX_THR_POOL; 179 else 180 return 0; 181 return ! 0; 182 } 183 184 185 /** 186 * Parse the polling backend name. 187 * 188 * @param s the name 189 * @param[out] out the parsed backend 190 * @return non-zero on success 191 */ 192 static int 193 parse_poll (const char *s, enum MHD_OptMatrixPoll *out) 194 { 195 if (0 == strcmp (s, "select")) 196 *out = MHD_OPT_MATRIX_POLL_SELECT; 197 else if (0 == strcmp (s, "poll")) 198 *out = MHD_OPT_MATRIX_POLL_POLL; 199 else if (0 == strcmp (s, "epoll")) 200 *out = MHD_OPT_MATRIX_POLL_EPOLL; 201 else 202 return 0; 203 return ! 0; 204 } 205 206 207 /** 208 * Report a broken environment variable and terminate: a typo in a driver 209 * script must not silently produce a run with the default configuration. 210 * 211 * @param name the name of the variable 212 * @param value the offending value 213 */ 214 _MHD_NORETURN static void 215 env_error (const char *name, const char *value) 216 { 217 fprintf (stderr, 218 "Invalid value '%s' for the environment variable %s.\n", 219 (NULL != value) ? value : "(null)", 220 name); 221 fflush (stderr); 222 exit (99); 223 } 224 225 226 const struct MHD_OptMatrixProfile * 227 mhd_opt_matrix_from_env (void) 228 { 229 const char *prof_env; 230 const char *mem_env; 231 const char *discp_env; 232 const char *strict_env; 233 const char *thr_env; 234 const char *poll_env; 235 236 if (0 != env_parsed) 237 return env_selected ? &env_profile : NULL; 238 env_parsed = ! 0; 239 240 prof_env = getenv (MHD_OPT_MATRIX_PROFILE_ENV); 241 mem_env = getenv (MHD_OPT_MATRIX_MEM_ENV); 242 discp_env = getenv (MHD_OPT_MATRIX_DISCP_ENV); 243 strict_env = getenv (MHD_OPT_MATRIX_STRICT_ENV); 244 thr_env = getenv (MHD_OPT_MATRIX_THREADING_ENV); 245 poll_env = getenv (MHD_OPT_MATRIX_POLL_ENV); 246 if ((NULL == prof_env) && (NULL == mem_env) && (NULL == discp_env) && 247 (NULL == strict_env) && (NULL == thr_env) && (NULL == poll_env)) 248 return NULL; /* Nothing selected: keep the previous behaviour */ 249 250 if (NULL != prof_env) 251 { 252 const struct MHD_OptMatrixProfile *base; 253 254 base = mhd_opt_matrix_lookup (prof_env); 255 if (NULL == base) 256 env_error (MHD_OPT_MATRIX_PROFILE_ENV, prof_env); 257 env_profile = *base; 258 } 259 else 260 env_profile = opt_profiles[0]; 261 strncpy (env_profile_name, env_profile.name, sizeof(env_profile_name) - 1); 262 env_profile_name[sizeof(env_profile_name) - 1] = 0; 263 env_profile.name = env_profile_name; 264 265 /* The explicit per-dimension overrides below are NOT clamped: they are an 266 explicit request by the caller, see the header. */ 267 if (NULL != mem_env) 268 { 269 if (0 == strcmp (mem_env, "default")) 270 env_profile.mem_limit = 0; 271 else 272 { 273 char *end; 274 unsigned long v; 275 276 v = strtoul (mem_env, &end, 10); 277 if ((NULL == end) || (0 != *end)) 278 env_error (MHD_OPT_MATRIX_MEM_ENV, mem_env); 279 env_profile.mem_limit = (size_t) v; 280 } 281 } 282 if (NULL != discp_env) 283 { 284 char *end; 285 long v; 286 287 v = strtol (discp_env, &end, 10); 288 if ((NULL == end) || (0 != *end) || (-3 > v) || (3 < v)) 289 env_error (MHD_OPT_MATRIX_DISCP_ENV, discp_env); 290 env_profile.discipline_lvl = (int) v; 291 env_profile.use_legacy_strict = 0; 292 } 293 if (NULL != strict_env) 294 { 295 char *end; 296 long v; 297 298 v = strtol (strict_env, &end, 10); 299 if ((NULL == end) || (0 != *end)) 300 env_error (MHD_OPT_MATRIX_STRICT_ENV, strict_env); 301 env_profile.discipline_lvl = (int) v; 302 env_profile.use_legacy_strict = ! 0; 303 } 304 if (NULL != thr_env) 305 { 306 if (! parse_threading (thr_env, &env_profile.threading)) 307 env_error (MHD_OPT_MATRIX_THREADING_ENV, thr_env); 308 if ((MHD_OPT_MATRIX_THR_POOL == env_profile.threading) && 309 (2 > env_profile.thread_pool_size)) 310 env_profile.thread_pool_size = 4; 311 } 312 if (NULL != poll_env) 313 { 314 if (! parse_poll (poll_env, &env_profile.poll_backend)) 315 env_error (MHD_OPT_MATRIX_POLL_ENV, poll_env); 316 } 317 env_selected = ! 0; 318 return &env_profile; 319 } 320 321 322 int 323 mhd_opt_matrix_effective_discipline (const struct MHD_OptMatrixProfile *prof) 324 { 325 if (NULL == prof) 326 return 0; 327 if (! prof->use_legacy_strict) 328 return prof->discipline_lvl; 329 /* Mirror the mapping of daemon.c:7102-7106 exactly. */ 330 if (-1 >= prof->discipline_lvl) 331 return -3; 332 if (1 <= prof->discipline_lvl) 333 return 1; 334 return 0; 335 } 336 337 338 int 339 mhd_opt_matrix_profile_supported (const struct MHD_OptMatrixProfile *prof) 340 { 341 if (NULL == prof) 342 return ! 0; 343 if ((MHD_OPT_MATRIX_THR_EXTERNAL != prof->threading) && 344 (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_THREADS))) 345 return 0; 346 if (MHD_OPT_MATRIX_THR_EXTERNAL == prof->threading) 347 { 348 /* Only the select()-style external interface is driven by the tests. */ 349 return (MHD_OPT_MATRIX_POLL_SELECT == prof->poll_backend) ? ! 0 : 0; 350 } 351 if ((MHD_OPT_MATRIX_POLL_POLL == prof->poll_backend) && 352 (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_POLL))) 353 return 0; 354 if (MHD_OPT_MATRIX_POLL_EPOLL == prof->poll_backend) 355 { 356 if (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_EPOLL)) 357 return 0; 358 /* MHD refuses to combine 'epoll' with a thread per connection. */ 359 if (MHD_OPT_MATRIX_THR_PER_CONNECTION == prof->threading) 360 return 0; 361 } 362 return ! 0; 363 } 364 365 366 const char * 367 mhd_opt_matrix_describe (const struct MHD_OptMatrixProfile *prof, 368 char *buf, 369 size_t buf_size) 370 { 371 static const char *const thr_names[] = { 372 "external polling", "internal thread", "thread-per-connection", 373 "thread pool" 374 }; 375 static const char *const poll_names[] = { "select()", "poll()", "epoll" }; 376 char mem[32]; 377 char pool[32]; 378 char discp[64]; 379 380 if (NULL == prof) 381 { 382 snprintf (buf, buf_size, "(built-in defaults of the test)"); 383 return buf; 384 } 385 if (0 == prof->mem_limit) 386 snprintf (mem, sizeof(mem), "default"); 387 else 388 snprintf (mem, sizeof(mem), "%lu", (unsigned long) prof->mem_limit); 389 if (MHD_OPT_MATRIX_THR_POOL == prof->threading) 390 snprintf (pool, sizeof(pool), " x%u", prof->thread_pool_size); 391 else 392 pool[0] = 0; 393 /* Always show the level that is really in effect: the deprecated option 394 silently maps its value, see mhd_opt_matrix_effective_discipline(). */ 395 if (prof->use_legacy_strict) 396 snprintf (discp, sizeof(discp), "strict_for_client=%d -> discipline_lvl=%d", 397 prof->discipline_lvl, 398 mhd_opt_matrix_effective_discipline (prof)); 399 else 400 snprintf (discp, sizeof(discp), "discipline_lvl=%d", prof->discipline_lvl); 401 snprintf (buf, buf_size, 402 "'%s' (mem_limit=%s, %s, %s%s, %s)", 403 prof->name, 404 mem, 405 discp, 406 thr_names[(unsigned int) prof->threading], 407 pool, 408 poll_names[(unsigned int) prof->poll_backend]); 409 return buf; 410 } 411 412 413 unsigned int 414 mhd_opt_matrix_fill_options (const struct MHD_OptMatrixProfile *prof, 415 struct MHD_OptionItem *ops, 416 unsigned int max_ops) 417 { 418 unsigned int n = 0; 419 420 if (4 > max_ops) 421 return 0; 422 if (NULL != prof) 423 { 424 if (0 != prof->mem_limit) 425 { 426 ops[n].option = MHD_OPTION_CONNECTION_MEMORY_LIMIT; 427 ops[n].value = (intptr_t) prof->mem_limit; 428 ops[n].ptr_value = NULL; 429 ++n; 430 } 431 if (0 != prof->discipline_lvl) 432 { 433 ops[n].option = prof->use_legacy_strict ? 434 MHD_OPTION_STRICT_FOR_CLIENT : 435 MHD_OPTION_CLIENT_DISCIPLINE_LVL; 436 ops[n].value = (intptr_t) prof->discipline_lvl; 437 ops[n].ptr_value = NULL; 438 ++n; 439 } 440 if ((MHD_OPT_MATRIX_THR_POOL == prof->threading) && 441 (1 < prof->thread_pool_size)) 442 { 443 ops[n].option = MHD_OPTION_THREAD_POOL_SIZE; 444 ops[n].value = (intptr_t) prof->thread_pool_size; 445 ops[n].ptr_value = NULL; 446 ++n; 447 } 448 } 449 ops[n].option = MHD_OPTION_END; 450 ops[n].value = 0; 451 ops[n].ptr_value = NULL; 452 ++n; 453 return n; 454 } 455 456 457 unsigned int 458 mhd_opt_matrix_daemon_flags (const struct MHD_OptMatrixProfile *prof, 459 unsigned int base_flags, 460 int allow_external) 461 { 462 unsigned int flags = base_flags; 463 464 if (NULL == prof) 465 return flags; 466 switch (prof->threading) 467 { 468 case MHD_OPT_MATRIX_THR_EXTERNAL: 469 if (allow_external) 470 return flags; /* No internal thread, no polling backend flag */ 471 /* The caller cannot drive MHD_run(); fall back to an internal thread. */ 472 return flags | MHD_USE_INTERNAL_POLLING_THREAD; 473 case MHD_OPT_MATRIX_THR_PER_CONNECTION: 474 flags |= MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_THREAD_PER_CONNECTION; 475 break; 476 case MHD_OPT_MATRIX_THR_INTERNAL: 477 case MHD_OPT_MATRIX_THR_POOL: 478 default: 479 flags |= MHD_USE_INTERNAL_POLLING_THREAD; 480 break; 481 } 482 switch (prof->poll_backend) 483 { 484 case MHD_OPT_MATRIX_POLL_POLL: 485 flags |= MHD_USE_POLL; 486 break; 487 case MHD_OPT_MATRIX_POLL_EPOLL: 488 flags |= MHD_USE_EPOLL; 489 break; 490 case MHD_OPT_MATRIX_POLL_SELECT: 491 default: 492 break; 493 } 494 return flags; 495 } 496 497 498 int 499 mhd_opt_matrix_is_external (const struct MHD_OptMatrixProfile *prof) 500 { 501 if (NULL == prof) 502 return 0; 503 return (MHD_OPT_MATRIX_THR_EXTERNAL == prof->threading) ? ! 0 : 0; 504 } 505 506 507 int 508 mhd_opt_matrix_raise_mem_limit (struct MHD_OptMatrixProfile *prof, 509 size_t min_limit) 510 { 511 if ((NULL == prof) || (0 == min_limit) || (0 == prof->mem_limit)) 512 return 0; 513 if (prof->mem_limit >= min_limit) 514 return 0; 515 prof->mem_limit = min_limit; 516 return ! 0; 517 } 518 519 520 void 521 mhd_opt_matrix_print_notice (const char *test_name) 522 { 523 const struct MHD_OptMatrixProfile *prof; 524 const char *name = (NULL != test_name) ? test_name : "test"; 525 char desc[256]; 526 527 prof = mhd_opt_matrix_from_env (); 528 printf ("%s: option matrix profile %s\n", 529 name, 530 mhd_opt_matrix_describe (prof, desc, sizeof(desc))); 531 fflush (stdout); 532 }