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