1 /* $NetBSD: slapd-mtread.c,v 1.4 2025/09/05 21:16:33 christos Exp $ */ 2 3 /* $OpenLDAP$ */ 4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 5 * 6 * Copyright 1999-2024 The OpenLDAP Foundation. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted only as authorized by the OpenLDAP 11 * Public License. 12 * 13 * A copy of this license is available in file LICENSE in the 14 * top-level directory of the distribution or, alternatively, at 15 * <http://www.OpenLDAP.org/license.html>. 16 */ 17 /* ACKNOWLEDGEMENTS: 18 * This work was initially developed by Kurt Spanier for inclusion 19 * in OpenLDAP Software. 20 */ 21 22 /* 23 * This tool is a MT reader. It behaves like slapd-read however 24 * with one or more threads simultaneously using the same connection. 25 * If -M is enabled, then M threads will also perform write operations. 26 */ 27 28 #include <sys/cdefs.h> 29 __RCSID("$NetBSD: slapd-mtread.c,v 1.4 2025/09/05 21:16:33 christos Exp $"); 30 31 #include "portable.h" 32 33 /* Requires libldap with threads */ 34 #ifndef NO_THREADS 35 36 #include <stdio.h> 37 #include "ldap_pvt_thread.h" 38 39 #include "ac/stdlib.h" 40 41 #include "ac/ctype.h" 42 #include "ac/param.h" 43 #include "ac/socket.h" 44 #include "ac/string.h" 45 #include "ac/unistd.h" 46 #include "ac/wait.h" 47 48 #include "ldap.h" 49 #include "lutil.h" 50 51 #include "ldap_pvt.h" 52 53 #include "slapd-common.h" 54 55 #define MAXCONN 512 56 #define LOOPS 100 57 #define RETRIES 0 58 #define DEFAULT_BASE "ou=people,dc=example,dc=com" 59 60 static void 61 do_read( LDAP *ld, char *entry, 62 char **attrs, int noattrs, int nobind, int maxloop, 63 int force, int idx ); 64 65 static void 66 do_random( LDAP *ld, 67 char *sbase, char *filter, char **attrs, int noattrs, int nobind, 68 int force, int idx ); 69 70 static void 71 do_random2( LDAP *ld, 72 char *sbase, char *filter, char **attrs, int noattrs, int nobind, 73 int force, int idx ); 74 75 static void * 76 do_onethread( void *arg ); 77 78 static void * 79 do_onerwthread( void *arg ); 80 81 #define MAX_THREAD 1024 82 /* Use same array for readers and writers, offset writers by MAX_THREAD */ 83 int rt_pass[MAX_THREAD*2]; 84 int rt_fail[MAX_THREAD*2]; 85 int *rwt_pass = rt_pass + MAX_THREAD; 86 int *rwt_fail = rt_fail + MAX_THREAD; 87 ldap_pvt_thread_t rtid[MAX_THREAD*2], *rwtid = rtid + MAX_THREAD; 88 89 /* 90 * Shared globals (command line args) 91 */ 92 LDAP *ld = NULL; 93 struct tester_conn_args *config; 94 char *entry = NULL; 95 char *filter = NULL; 96 int force = 0; 97 char *srchattrs[] = { "1.1", NULL }; 98 char **attrs = srchattrs; 99 int noattrs = 0; 100 int nobind = 0; 101 int threads = 1; 102 int rwthreads = 0; 103 int verbose = 0; 104 105 int noconns = 1; 106 LDAP **lds = NULL; 107 108 static void 109 thread_error(int idx, char *string) 110 { 111 char thrstr[BUFSIZ]; 112 113 snprintf(thrstr, BUFSIZ, "error on tidx: %d: %s", idx, string); 114 tester_error( thrstr ); 115 } 116 117 static void 118 thread_output(int idx, char *string) 119 { 120 char thrstr[BUFSIZ]; 121 122 snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string); 123 tester_error( thrstr ); 124 } 125 126 static void 127 thread_verbose(int idx, char *string) 128 { 129 char thrstr[BUFSIZ]; 130 131 if (!verbose) 132 return; 133 snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string); 134 tester_error( thrstr ); 135 } 136 137 static void 138 usage( char *name, char opt ) 139 { 140 if ( opt ) { 141 fprintf( stderr, "%s: unable to handle option \'%c\'\n\n", 142 name, opt ); 143 } 144 145 fprintf( stderr, "usage: %s " TESTER_COMMON_HELP 146 "-e <entry> " 147 "[-A] " 148 "[-F] " 149 "[-N] " 150 "[-v] " 151 "[-c connections] " 152 "[-f filter] " 153 "[-m threads] " 154 "[-M threads] " 155 "[-T <attrs>] " 156 "[<attrs>] " 157 "\n", 158 name ); 159 exit( EXIT_FAILURE ); 160 } 161 162 int 163 main( int argc, char **argv ) 164 { 165 int i; 166 char *uri = NULL; 167 char *manager = NULL; 168 struct berval passwd = { 0, NULL }; 169 char outstr[BUFSIZ]; 170 int ptpass; 171 int testfail = 0; 172 173 config = tester_init( "slapd-mtread", TESTER_READ ); 174 175 /* by default, tolerate referrals and no such object */ 176 tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" ); 177 178 while ( (i = getopt( argc, argv, TESTER_COMMON_OPTS "Ac:e:Ff:M:m:NT:v" )) != EOF ) { 179 switch ( i ) { 180 case 'A': 181 noattrs++; 182 break; 183 184 case 'N': 185 nobind = TESTER_INIT_ONLY; 186 break; 187 188 case 'v': 189 verbose++; 190 break; 191 192 case 'c': /* the number of connections */ 193 if ( lutil_atoi( &noconns, optarg ) != 0 ) { 194 usage( argv[0], i ); 195 } 196 break; 197 198 case 'e': /* DN to search for */ 199 entry = optarg; 200 break; 201 202 case 'f': /* the search request */ 203 filter = optarg; 204 break; 205 206 case 'F': 207 force++; 208 break; 209 210 case 'M': /* the number of R/W threads */ 211 if ( lutil_atoi( &rwthreads, optarg ) != 0 ) { 212 usage( argv[0], i ); 213 } 214 if (rwthreads > MAX_THREAD) 215 rwthreads = MAX_THREAD; 216 break; 217 218 case 'm': /* the number of threads */ 219 if ( lutil_atoi( &threads, optarg ) != 0 ) { 220 usage( argv[0], i ); 221 } 222 if (threads > MAX_THREAD) 223 threads = MAX_THREAD; 224 break; 225 226 case 'T': 227 attrs = ldap_str2charray( optarg, "," ); 228 if ( attrs == NULL ) { 229 usage( argv[0], i ); 230 } 231 break; 232 233 default: 234 if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) { 235 break; 236 } 237 usage( argv[0], i ); 238 break; 239 } 240 } 241 242 if ( entry == NULL ) 243 usage( argv[0], 0 ); 244 245 if ( *entry == '\0' ) { 246 fprintf( stderr, "%s: invalid EMPTY entry DN.\n", 247 argv[0] ); 248 exit( EXIT_FAILURE ); 249 } 250 251 if ( argv[optind] != NULL ) { 252 attrs = &argv[optind]; 253 } 254 255 if (noconns < 1) 256 noconns = 1; 257 if (noconns > MAXCONN) 258 noconns = MAXCONN; 259 lds = (LDAP **) calloc( sizeof(LDAP *), noconns); 260 if (lds == NULL) { 261 fprintf( stderr, "%s: Memory error: calloc noconns.\n", 262 argv[0] ); 263 exit( EXIT_FAILURE ); 264 } 265 266 tester_config_finish( config ); 267 ldap_pvt_thread_initialize(); 268 269 for (i = 0; i < noconns; i++) { 270 tester_init_ld( &lds[i], config, nobind ); 271 } 272 273 snprintf(outstr, BUFSIZ, "MT Test Start: conns: %d (%s)", noconns, uri); 274 tester_error(outstr); 275 snprintf(outstr, BUFSIZ, "Threads: RO: %d RW: %d", threads, rwthreads); 276 tester_error(outstr); 277 278 /* Set up read only threads */ 279 for ( i = 0; i < threads; i++ ) { 280 ldap_pvt_thread_create( &rtid[i], 0, do_onethread, &rtid[i]); 281 snprintf(outstr, BUFSIZ, "Created RO thread %d", i); 282 thread_verbose(-1, outstr); 283 } 284 /* Set up read/write threads */ 285 for ( i = 0; i < rwthreads; i++ ) { 286 ldap_pvt_thread_create( &rwtid[i], 0, do_onerwthread, &rwtid[i]); 287 snprintf(outstr, BUFSIZ, "Created RW thread %d", i + MAX_THREAD); 288 thread_verbose(-1, outstr); 289 } 290 291 ptpass = config->outerloops * config->loops; 292 293 /* wait for read only threads to complete */ 294 for ( i = 0; i < threads; i++ ) 295 ldap_pvt_thread_join(rtid[i], NULL); 296 /* wait for read/write threads to complete */ 297 for ( i = 0; i < rwthreads; i++ ) 298 ldap_pvt_thread_join(rwtid[i], NULL); 299 300 for(i = 0; i < noconns; i++) { 301 if ( lds[i] != NULL ) { 302 ldap_unbind_ext( lds[i], NULL, NULL ); 303 } 304 } 305 free( lds ); 306 307 for ( i = 0; i < threads; i++ ) { 308 snprintf(outstr, BUFSIZ, "RO thread %d pass=%d fail=%d", i, 309 rt_pass[i], rt_fail[i]); 310 tester_error(outstr); 311 if (rt_fail[i] != 0 || rt_pass[i] != ptpass) { 312 snprintf(outstr, BUFSIZ, "FAIL RO thread %d", i); 313 tester_error(outstr); 314 testfail++; 315 } 316 } 317 for ( i = 0; i < rwthreads; i++ ) { 318 snprintf(outstr, BUFSIZ, "RW thread %d pass=%d fail=%d", i + MAX_THREAD, 319 rwt_pass[i], rwt_fail[i]); 320 tester_error(outstr); 321 if (rwt_fail[i] != 0 || rwt_pass[i] != ptpass) { 322 snprintf(outstr, BUFSIZ, "FAIL RW thread %d", i); 323 tester_error(outstr); 324 testfail++; 325 } 326 } 327 snprintf(outstr, BUFSIZ, "MT Test complete" ); 328 tester_error(outstr); 329 330 if (testfail) 331 exit( EXIT_FAILURE ); 332 exit( EXIT_SUCCESS ); 333 } 334 335 static void * 336 do_onethread( void *arg ) 337 { 338 int i, j, thisconn; 339 LDAP **mlds; 340 char thrstr[BUFSIZ]; 341 int rc, refcnt = 0; 342 int idx = (ldap_pvt_thread_t *)arg - rtid; 343 344 mlds = (LDAP **) calloc( sizeof(LDAP *), noconns); 345 if (mlds == NULL) { 346 thread_error( idx, "Memory error: thread calloc for noconns" ); 347 exit( EXIT_FAILURE ); 348 } 349 350 for ( j = 0; j < config->outerloops; j++ ) { 351 for(i = 0; i < noconns; i++) { 352 mlds[i] = ldap_dup(lds[i]); 353 if (mlds[i] == NULL) { 354 thread_error( idx, "ldap_dup error" ); 355 } 356 } 357 rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt); 358 snprintf(thrstr, BUFSIZ, 359 "RO Thread conns: %d refcnt: %d (rc = %d)", 360 noconns, refcnt, rc); 361 thread_verbose(idx, thrstr); 362 363 thisconn = (idx + j) % noconns; 364 if (thisconn < 0 || thisconn >= noconns) 365 thisconn = 0; 366 if (mlds[thisconn] == NULL) { 367 thread_error( idx, "(failed to dup)"); 368 tester_perror( "ldap_dup", "(failed to dup)" ); 369 exit( EXIT_FAILURE ); 370 } 371 snprintf(thrstr, BUFSIZ, "Using conn %d", thisconn); 372 thread_verbose(idx, thrstr); 373 if ( filter != NULL ) { 374 if (strchr(filter, '[')) 375 do_random2( mlds[thisconn], entry, filter, attrs, 376 noattrs, nobind, force, idx ); 377 else 378 do_random( mlds[thisconn], entry, filter, attrs, 379 noattrs, nobind, force, idx ); 380 381 } else { 382 do_read( mlds[thisconn], entry, attrs, noattrs, 383 nobind, config->loops, force, idx ); 384 } 385 for(i = 0; i < noconns; i++) { 386 (void) ldap_destroy(mlds[i]); 387 mlds[i] = NULL; 388 } 389 } 390 free( mlds ); 391 return( NULL ); 392 } 393 394 static void * 395 do_onerwthread( void *arg ) 396 { 397 int i, j, thisconn; 398 LDAP **mlds, *ld; 399 char thrstr[BUFSIZ]; 400 char dn[256], uids[32], cns[32], *base; 401 LDAPMod *attrp[5], attrs[4]; 402 char *oc_vals[] = { "top", "OpenLDAPperson", NULL }; 403 char *cn_vals[] = { NULL, NULL }; 404 char *sn_vals[] = { NULL, NULL }; 405 char *uid_vals[] = { NULL, NULL }; 406 int ret; 407 int adds = 0; 408 int dels = 0; 409 int rc, refcnt = 0; 410 int idx = (ldap_pvt_thread_t *)arg - rtid; 411 412 mlds = (LDAP **) calloc( sizeof(LDAP *), noconns); 413 if (mlds == NULL) { 414 thread_error( idx, "Memory error: thread calloc for noconns" ); 415 exit( EXIT_FAILURE ); 416 } 417 418 snprintf(uids, sizeof(uids), "rwtest%04d", idx); 419 snprintf(cns, sizeof(cns), "rwtest%04d", idx); 420 /* add setup */ 421 for (i = 0; i < 4; i++) { 422 attrp[i] = &attrs[i]; 423 attrs[i].mod_op = 0; 424 } 425 attrp[4] = NULL; 426 attrs[0].mod_type = "objectClass"; 427 attrs[0].mod_values = oc_vals; 428 attrs[1].mod_type = "cn"; 429 attrs[1].mod_values = cn_vals; 430 cn_vals[0] = &cns[0]; 431 attrs[2].mod_type = "sn"; 432 attrs[2].mod_values = sn_vals; 433 sn_vals[0] = &cns[0]; 434 attrs[3].mod_type = "uid"; 435 attrs[3].mod_values = uid_vals; 436 uid_vals[0] = &uids[0]; 437 438 for ( j = 0; j < config->outerloops; j++ ) { 439 for(i = 0; i < noconns; i++) { 440 mlds[i] = ldap_dup(lds[i]); 441 if (mlds[i] == NULL) { 442 thread_error( idx, "ldap_dup error" ); 443 } 444 } 445 rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt); 446 snprintf(thrstr, BUFSIZ, 447 "RW Thread conns: %d refcnt: %d (rc = %d)", 448 noconns, refcnt, rc); 449 thread_verbose(idx, thrstr); 450 451 thisconn = (idx + j) % noconns; 452 if (thisconn < 0 || thisconn >= noconns) 453 thisconn = 0; 454 if (mlds[thisconn] == NULL) { 455 thread_error( idx, "(failed to dup)"); 456 tester_perror( "ldap_dup", "(failed to dup)" ); 457 exit( EXIT_FAILURE ); 458 } 459 snprintf(thrstr, BUFSIZ, "START RW Thread using conn %d", thisconn); 460 thread_verbose(idx, thrstr); 461 462 ld = mlds[thisconn]; 463 if (entry != NULL) 464 base = entry; 465 else 466 base = DEFAULT_BASE; 467 snprintf(dn, 256, "cn=%s,%s", cns, base); 468 469 adds = 0; 470 dels = 0; 471 for (i = 0; i < config->loops; i++) { 472 ret = ldap_add_ext_s(ld, dn, &attrp[0], NULL, NULL); 473 if (ret == LDAP_SUCCESS) { 474 adds++; 475 ret = ldap_delete_ext_s(ld, dn, NULL, NULL); 476 if (ret == LDAP_SUCCESS) { 477 dels++; 478 rt_pass[idx]++; 479 } else { 480 thread_output(idx, ldap_err2string(ret)); 481 rt_fail[idx]++; 482 } 483 } else { 484 thread_output(idx, ldap_err2string(ret)); 485 rt_fail[idx]++; 486 } 487 } 488 489 snprintf(thrstr, BUFSIZ, 490 "INNER STOP RW Thread using conn %d (%d/%d)", 491 thisconn, adds, dels); 492 thread_verbose(idx, thrstr); 493 494 for(i = 0; i < noconns; i++) { 495 (void) ldap_destroy(mlds[i]); 496 mlds[i] = NULL; 497 } 498 } 499 500 free( mlds ); 501 return( NULL ); 502 } 503 504 static void 505 do_random( LDAP *ld, 506 char *sbase, char *filter, char **srchattrs, int noattrs, int nobind, 507 int force, int idx ) 508 { 509 int i = 0, do_retry = config->retries; 510 char *attrs[ 2 ]; 511 int rc = LDAP_SUCCESS; 512 int nvalues = 0; 513 char **values = NULL; 514 LDAPMessage *res = NULL, *e = NULL; 515 char thrstr[BUFSIZ]; 516 517 attrs[ 0 ] = LDAP_NO_ATTRS; 518 attrs[ 1 ] = NULL; 519 520 snprintf( thrstr, BUFSIZ, 521 "Read(%d): base=\"%s\", filter=\"%s\".\n", 522 config->loops, sbase, filter ); 523 thread_verbose( idx, thrstr ); 524 525 rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE, 526 filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res ); 527 switch ( rc ) { 528 case LDAP_SIZELIMIT_EXCEEDED: 529 case LDAP_TIMELIMIT_EXCEEDED: 530 case LDAP_SUCCESS: 531 nvalues = ldap_count_entries( ld, res ); 532 if ( nvalues == 0 ) { 533 if ( rc ) { 534 tester_ldap_error( ld, "ldap_search_ext_s", NULL ); 535 } 536 ldap_msgfree( res ); 537 break; 538 } 539 540 values = malloc( ( nvalues + 1 ) * sizeof( char * ) ); 541 if (values == NULL) { 542 thread_error( idx, "(failed to malloc)"); 543 exit( EXIT_FAILURE ); 544 } 545 for ( i = 0, e = ldap_first_entry( ld, res ); e != NULL; i++, e = ldap_next_entry( ld, e ) ) 546 { 547 values[ i ] = ldap_get_dn( ld, e ); 548 } 549 values[ i ] = NULL; 550 551 ldap_msgfree( res ); 552 553 if ( do_retry == config->retries ) { 554 snprintf( thrstr, BUFSIZ, 555 "Read base=\"%s\" filter=\"%s\" got %d values.\n", 556 sbase, filter, nvalues ); 557 thread_verbose( idx, thrstr ); 558 } 559 560 for ( i = 0; i < config->loops; i++ ) { 561 int r = ((double)nvalues)*rand()/(RAND_MAX + 1.0); 562 563 do_read( ld, values[ r ], 564 srchattrs, noattrs, nobind, 1, force, idx ); 565 } 566 for( i = 0; i < nvalues; i++) { 567 if (values[i] != NULL) 568 ldap_memfree( values[i] ); 569 } 570 free( values ); 571 break; 572 573 default: 574 tester_ldap_error( ld, "ldap_search_ext_s", NULL ); 575 ldap_msgfree( res ); 576 break; 577 } 578 579 snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc ); 580 thread_verbose( idx, thrstr ); 581 } 582 583 /* substitute a generated int into the filter */ 584 static void 585 do_random2( LDAP *ld, 586 char *sbase, char *filter, char **srchattrs, int noattrs, int nobind, 587 int force, int idx ) 588 { 589 int i = 0, do_retry = config->retries; 590 int rc = LDAP_SUCCESS; 591 int lo, hi, range; 592 int flen; 593 LDAPMessage *res = NULL; 594 char *ptr, *ftail; 595 char thrstr[BUFSIZ]; 596 char fbuf[BUFSIZ]; 597 598 snprintf( thrstr, BUFSIZ, 599 "Read(%d): base=\"%s\", filter=\"%s\".\n", 600 config->loops, sbase, filter ); 601 thread_verbose( idx, thrstr ); 602 603 ptr = strchr(filter, '['); 604 if (!ptr) 605 return; 606 ftail = strchr(filter, ']'); 607 if (!ftail || ftail < ptr) 608 return; 609 610 sscanf(ptr, "[%d-%d]", &lo, &hi); 611 range = hi - lo + 1; 612 613 flen = ptr - filter; 614 ftail++; 615 616 for ( i = 0; i < config->loops; i++ ) { 617 int r = ((double)range)*rand()/(RAND_MAX + 1.0); 618 sprintf(fbuf, "%.*s%d%s", flen, filter, r, ftail); 619 620 rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE, 621 fbuf, srchattrs, noattrs, NULL, NULL, NULL, 622 LDAP_NO_LIMIT, &res ); 623 if ( res != NULL ) { 624 ldap_msgfree( res ); 625 } 626 if ( rc == 0 ) { 627 rt_pass[idx]++; 628 } else { 629 int first = tester_ignore_err( rc ); 630 char buf[ BUFSIZ ]; 631 632 rt_fail[idx]++; 633 snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry ); 634 635 /* if ignore.. */ 636 if ( first ) { 637 /* only log if first occurrence */ 638 if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) { 639 tester_ldap_error( ld, buf, NULL ); 640 } 641 continue; 642 } 643 644 /* busy needs special handling */ 645 tester_ldap_error( ld, buf, NULL ); 646 if ( rc == LDAP_BUSY && do_retry > 0 ) { 647 do_retry--; 648 continue; 649 } 650 break; 651 } 652 } 653 654 snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc ); 655 thread_verbose( idx, thrstr ); 656 } 657 658 static void 659 do_read( LDAP *ld, char *entry, 660 char **attrs, int noattrs, int nobind, int maxloop, 661 int force, int idx ) 662 { 663 int i = 0, do_retry = config->retries; 664 int rc = LDAP_SUCCESS; 665 char thrstr[BUFSIZ]; 666 667 retry:; 668 if ( do_retry == config->retries ) { 669 snprintf( thrstr, BUFSIZ, "Read(%d): entry=\"%s\".\n", 670 maxloop, entry ); 671 thread_verbose( idx, thrstr ); 672 } 673 674 snprintf(thrstr, BUFSIZ, "LD %p cnt: %d (retried %d) (%s)", \ 675 (void *) ld, maxloop, (do_retry - config->retries), entry); 676 thread_verbose( idx, thrstr ); 677 678 for ( ; i < maxloop; i++ ) { 679 LDAPMessage *res = NULL; 680 681 rc = ldap_search_ext_s( ld, entry, LDAP_SCOPE_BASE, 682 NULL, attrs, noattrs, NULL, NULL, NULL, 683 LDAP_NO_LIMIT, &res ); 684 if ( res != NULL ) { 685 ldap_msgfree( res ); 686 } 687 688 if ( rc == 0 ) { 689 rt_pass[idx]++; 690 } else { 691 int first = tester_ignore_err( rc ); 692 char buf[ BUFSIZ ]; 693 694 rt_fail[idx]++; 695 snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry ); 696 697 /* if ignore.. */ 698 if ( first ) { 699 /* only log if first occurrence */ 700 if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) { 701 tester_ldap_error( ld, buf, NULL ); 702 } 703 continue; 704 } 705 706 /* busy needs special handling */ 707 tester_ldap_error( ld, buf, NULL ); 708 if ( rc == LDAP_BUSY && do_retry > 0 ) { 709 do_retry--; 710 goto retry; 711 } 712 break; 713 } 714 } 715 } 716 717 #else /* NO_THREADS */ 718 719 #include <stdio.h> 720 #include <stdlib.h> 721 722 int 723 main( int argc, char **argv ) 724 { 725 fprintf( stderr, "%s: not available when configured --without-threads\n", argv[0] ); 726 exit( EXIT_FAILURE ); 727 } 728 729 #endif /* NO_THREADS */ 730