[Previous] [Next] [TOC] [Index]

Appendix C
Writing Multithreaded Clients

his appendix explains how to write multi-threaded LDAP clients.

Specifying Thread Functions

If you are writing a multi-threaded client where different threads access the same LDAP structure, you need to set up the session so that the threads do not interfere with each other.

For example, in a single-threaded LDAP client, the LDAP structure contains the error code of the last LDAP operation. In a multi-threaded client, you need to set up the session so that each thread can have its own error code.

The LDAP_OPT_THREAD_FN_PTRS session option lets you set up an ldap_thread_fns structure identifying the functions that are called in multi-threaded environments (for example, functions to lock and unlock critical sections of code and to get and set errors).

Because this structure lets you specify these functions, you can use the LDAP API library in different types of threading environments.

Setting Up the ldap_thread_fns Structure

If you are writing a multi-threaded client in which different threads use the same LDAP connection, you need to set up the ldap_thread_fns structure and identify the functions that you want to use.

The ldap_thread_fns structure has the following fields:

struct ldap_thread_fns {
   void *(*ltf_mutex_alloc)( void );
   void (*ltf_mutex_free)( void * );
   int (*ltf_mutex_lock)( void * );
   int (*ltf_mutex_unlock)( void * );
   int (*ltf_get_errno)( void );
   void (*ltf_set_errno)( int );
   int (*ltf_get_lderrno)( char **, char **, void * );
   void (*ltf_set_lderrno)( int, char *, char *, void * );
   void *ltf_lderrno_arg;
} ;
These fields are describe in more detail below:
Field Description
(*ltf_mutex_alloc)
Function pointer for allocating a mutex. This function is called by the client when needed if the function pointer is not NULL.
(*ltf_mutex_free)
Function pointer for freeing a mutex. This function is called by the client when needed if the function pointer is not NULL.
(*ltf_mutex_lock)
Function pointer for locking critical sections of code. This function is called by the client when needed if the function pointer is not NULL.
(*ltf_mutex_unlock)
Function pointer for unlocking critical sections of code. This function is called by the client when needed if the function pointer is not NULL.
(*ltf_get_errno)
Function pointer for getting the value of the errno variable. This function is called by the client when needed if the function pointer is not NULL. In a threaded environment, errno is typically redefined so that it has a value for each thread, rather than a global value for the entire process. This redefinition is done at compile time. Because the libldap library does not know what method your code and threading environment will use to get the value of errno for each thread, it calls this function to return the value of errno.
(*ltf_set_errno)
Function pointer for setting the value of the errno variable. This function is called by the client when needed if the function pointer is not NULL. In a threaded environment, errno is typically redefined so that it has a value for each thread, rather than a global value for the entire process. This redefinition is done at compile time. Because the libldap library does not know what method your code and threading environment will use to get the value of errno for each thread, it calls this function to set the value of errno.
(*ltf_get_lderrno)
Function pointer for getting error values from calls to functions in the libldap library. This function is called by the client when needed if the function pointer is not NULL. If this function pointer is not set, the libldap library records these errors in fields in the LDAP structure.
(*ltf_set_lderrno)
Function pointer for setting error values from calls to functions in the libldap library. This function is called by the client when needed if the function pointer is not NULL. If this function pointer is not set, the libldap library records these errors in fields in the LDAP structure.
*ltf_lderrno_arg
Additional parameter passed to the functions for getting and setting error values from calls to functions in the libldap library. (*ltf_get_lderrno) and (*ltf_set_lderrno) identify these functions.

Setting the Session Options

After you set up the ldap_thread_fns structure, you need to associate the structure with the current session. Call the ldap_set_option() function and pass LDAP_OPT_THREAD_FN_PTRS as the value of the option parameter. Pass a pointer to the ldap_thread_fns structure as the value of the optdata parameter.

For example, the following section of code sets up an ldap_thread_fns structure for an LDAP session.

#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <pthread.h>
#include <ldap.h>
struct ldap_thread_fns tfns;
...
   /* Set up the ldap_thread_fns structure with pointers 
      to the functions that you want called */
   memset( &tfns, '\0', sizeof(struct ldap_thread_fns) );
   /* Specify the functions that you want called */
   /* Call the my_mutex_alloc() function whenever mutexes 
      need to be allocated */
   tfns.ltf_mutex_alloc = (void *(*)(void)) my_mutex_alloc;
   /* Call the my_mutex_free() function whenever mutexes 
      need to be destroyed */
   tfns.ltf_mutex_free = (void (*)(void *)) my_mutex_free;
   /* Call the pthread_mutex_lock() function whenever a 
      thread needs to lock a mutex. */
   tfns.ltf_mutex_lock = (int (*)(void *)) pthread_mutex_lock;
   /* Call the pthread_mutex_unlock() function whenever a 
      thread needs to unlock a mutex. */
   tfns.ltf_mutex_unlock = (int (*)(void *)) pthread_mutex_unlock;
   /* Call the get_errno() function to get the value of errno */
   tfns.ltf_get_errno = get_errno;
   /* Call the set_errno() function to set the value of errno */
   tfns.ltf_set_errno = set_errno;
   /* Call the get_ld_error() function to get error values from 
      calls to functions in the libldap library */
   tfns.ltf_get_lderrno = get_ld_error;
   /* Call the set_ld_error() function to set error values for 
      calls to functions in the libldap library */
   tfns.ltf_set_lderrno = set_ld_error;
   /* Don't pass any extra parameter to the functions for 
      getting and setting libldap function call errors */
   tfns.ltf_lderrno_arg = NULL;
...
/* Set the session option that specifies the functions to call for multi-threaded clients */
if (ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &tfns)!= 0) {
   ldap_perror( ld, "ldap_set_option: thread pointers" );
}
...

Example of a Pthreads Client Application

The following example, which uses pthreads (POSIX threads) under Solaris, is the source code for a multi-threaded client. The client connects to a specified LDAP server and creates several threads to perform multiple search and update operations simultaneously on the directory.

#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <pthread.h>
#include <ldap.h>
#define NAME NULL
#define PASSWORD NULL
#define BASE "o=Ace Industry, c=US"
static void *search_thread();
static void *modify_thread();
static void *add_thread();
static void *delete_thread();
static void *my_mutex_alloc();
static void my_mutex_free();
static void set_ld_error();
static int  get_ld_error();
static void set_errno();
static int  get_errno();
static void tsd_setup();
LDAP *ld;
pthread_key_t key;
main( int argc, char **argv )
{
   pthread_attr_t attr;
   pthread_t search_tid, search_tid2, search_tid3, search_tid4;
   pthread_t modify_tid, add_tid, delete_tid;
   void *status;
   struct ldap_thread_fns tfns;
   /* To run the client, enter the name and port of the LDAP server */
   if ( argc != 3 ) {
      fprintf( stderr, "usage: %s host port\n", argv[0] );
      exit( 1 );
   }
   /* Allocate a key for thread-specific data */
   if ( pthread_key_create( &key, free ) != 0 ) {
      perror( "pthread_key_create" );
   }
   /* Set up thread-specific data for the error codes */
   tsd_setup();
   /* Initialize a session with the server */
   if ( (ld = ldap_init( argv[1], atoi( argv[2] ) )) == NULL ) {
      perror( "ldap_init" );
      exit( 1 );
   }
   /* Specify the functions that you want to be called 
      in the ldap_thread_fns structure. */
   memset( &tfns, '\0', sizeof(struct ldap_thread_fns) );
   /* Function for allocating memory for a mutex */
   tfns.ltf_mutex_alloc = (void *(*)(void)) my_mutex_alloc;
   /* Function for destroying a mutex */
   tfns.ltf_mutex_free = (void (*)(void *)) my_mutex_free;
   /* Function for locking for a mutex */
   tfns.ltf_mutex_lock = (int (*)(void *)) pthread_mutex_lock;
   /* Function for unlocking for a mutex */
   tfns.ltf_mutex_unlock = (int (*)(void *)) pthread_mutex_unlock;
   /* Function for getting the value of the errno variable */
   tfns.ltf_get_errno = get_errno;
   /* Function for setting the value of the errno variable */
   tfns.ltf_set_errno = set_errno;
   /* Function for getting the error values for LDAP API calls */
   tfns.ltf_get_lderrno = get_ld_error;
   /* Function for setting the error values for LDAP API calls */
   tfns.ltf_set_lderrno = set_ld_error;
   /* Don't pass any extra parameters to the functions for 
      setting LDAP API error values */
   tfns.ltf_lderrno_arg = NULL;
   /* Set up the session to use these functions */
   if ( ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &tfns )
    != 0 ) {
      ldap_perror( ld, "ldap_set_option: thread pointers" );
   }
   /* Connect and authenticate to the LDAP server */
   if ( ldap_simple_bind_s( ld, NAME, PASSWORD ) != LDAP_SUCCESS ) {
      ldap_perror( ld, "ldap_simple_bind_s" );
      exit( 1 );
   }
   /* Initialize the threads attributes structure */
   if ( pthread_attr_init( &attr ) != 0 ) {
      perror( "pthread_attr_init" );
      exit( 1 );
   }
   /* Set up the threads so they are not detachable */
   pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
   /* Create a thread to search the directory */
   if (pthread_create( &search_tid, &attr, search_thread, "1") != 0) {
      perror( "pthread_create search_thread" );
      exit( 1 );
   }
   /* Create a thread to modify an entry in the directory */
   if (pthread_create( &modify_tid, &attr, modify_thread, "2") != 0) {
      perror( "pthread_create modify_thread" );
      exit( 1 );
   }
   /* Create another thread to search the directory */
   if (pthread_create( &search_tid2, &attr, search_thread, "3")!= 0) {
      perror( "pthread_create search_thread2" );
      exit( 1 );
   }
   /* Create a thread to add an entry to the directory */
   if ( pthread_create( &add_tid, &attr, add_thread, "4" ) != 0 ) {
      perror( "pthread_create add_thread" );
      exit( 1 );
   }
   /* Create a 3rd search thread */
   if (pthread_create( &search_tid3, &attr, search_thread, "5")!= 0) {
      perror( "pthread_create search_thread3" );
      exit( 1 );
   }
   /* Create a thread to delete an entry from the directory */
   if (pthread_create( &delete_tid, &attr, delete_thread, "6") != 0) {
      perror( "pthread_create delete_thread" );
      exit( 1 );
   }
   /* Create a 4th search thread */
   if (pthread_create( &search_tid4, &attr, search_thread, "7")!= 0) {
      perror( "pthread_create search_thread4" );
      exit( 1 );
   }
   /* Wait for the threads to complete, and return the status 
      of each thread */
   pthread_join( search_tid2, &status );
   pthread_join( search_tid3, &status );
   pthread_join( search_tid4, &status );
   pthread_join( modify_tid, &status );
   pthread_join( add_tid, &status );
   pthread_join( delete_tid, &status );
   pthread_join( search_tid, &status );
}
/* Thread for searching the directory */
static void *
search_thread( char *id )
{
   LDAPMessage *res;
   LDAPMessage *e;
   char *a;
   char **v;
   char *dn;
   BerElement *ber;
   int i, rc, msgid;
   void *tsd;
   printf( "search_thread\n" );
   /* Set up thread-specific data for LDAP error values */
   tsd_setup();
   for ( ;; ) {
      printf( "%sSearching...\n", id );
      /* Perform an asynchronous search */
      if ( (msgid = ldap_search( ld, BASE, LDAP_SCOPE_SUBTREE,
       "(objectclass=*)", NULL, 0 )) == -1 ) {
         ldap_perror( ld, "ldap_search" );
         continue;
      }
      /* Check the results of the search operation */
      while ( (rc = ldap_result( ld, msgid, 0, NULL, &res ))
       == LDAP_RES_SEARCH_ENTRY ) {
         /* Iterate through each entry in the search results */
         for ( e = ldap_first_entry( ld, res ); e != NULL;
          e = ldap_next_entry( ld, e ) ) {
            /* Get and print the DN of the entry */
            dn = ldap_get_dn( ld, e );
            printf( "%sdn: %s\n", id, dn ); 
            free( dn );
            /* Iterate through each attribute in an entry */
            for ( a = ldap_first_attribute( ld, e, &ber );
             a != NULL; a = ldap_next_attribute( ld, e, ber ) ) {
               /* Get and print the values of the attr */
               v = ldap_get_values( ld, e, a );
               for ( i = 0; v && v[i] != 0; i++ ) {
                  printf( "%s%s: %s\n", id, a, v[i] );
               }
               /* Free the attribute values when done */
               ldap_value_free( v );
               /* Free the attribute name */
               free( a );
            }
         }
         /* Free the search results */
         ldap_msgfree( res );
         /* Print the thread ID */
         printf( "%s\n", id );
      }
      /* If the search was unsuccessful, print the error message */ 
      if ( rc == -1 || ldap_result2error( ld, res, 0 ) !=
       LDAP_SUCCESS ) {
         ldap_perror( ld, "ldap_search" );
      } else {
         /* Keep track of which threads are done */
         printf( "%sDone with one round\n", id );
      }
   }
}
/* Thread for modifying an entry in the directory */
static void *
modify_thread( char *id )
{
   LDAPMessage *res, *list;
   LDAPMessage *e;
   int i, modentry, entries, msgid, rc;
   LDAPMod mod;
   LDAPMod *mods[2];
   char *vals[2];
   char *dn;
   printf( "modify_thread\n" );
   /* Set up thread-specific data for LDAP error values */
   tsd_setup();
   /* First search the directory for entries */
   if ( (msgid = ldap_search( ld, BASE, LDAP_SCOPE_SUBTREE,
    "(objectclass=*)", NULL, 0 )) == -1 ) {
      ldap_perror( ld, "ldap_search" );
      exit( 1 );
   }
   /* Keep a list of the matching entries */
   entries = 0;
   list = NULL;
   while ( (rc = ldap_result( ld, msgid, 0, NULL, &res ))
    == LDAP_RES_SEARCH_ENTRY ) {
      entries++;
      ldap_add_result_entry( &list, res );
   }
   if ( rc == -1 || ldap_result2error( ld, res, 0 ) != LDAP_SUCCESS) {
      ldap_perror( ld, "modify_thread: ldap_search" );
      exit( 1 );
   } else {
      entries++;
      printf( "%sModify got %d entries\n", id, entries );
   }
   mods[0] = &mod;
   mods[1] = NULL;
   vals[0] = "bar";
   vals[1] = NULL;
   for ( ;; ) {
      /* Select a random entry to modify */
      modentry = rand() % entries;
      for ( i = 0, e = ldap_first_entry( ld, list ); e != NULL &&
       i < modentry; i++, e = ldap_next_entry( ld, e ) ) {
         /* NULL */
      }
      /* Make sure the entry is valid */
      if ( e == NULL ) {
         fprintf( stderr, "%sModify could not find entry %d of %d\n",
          id, modentry, entries );
         continue;
      }
      printf( "%sPicked entry %d of %d\n", id, i, entries );
      /* Update the "foo" attribute of the entry */
      dn = ldap_get_dn( ld, e );
      mod.mod_op = LDAP_MOD_REPLACE;
      mod.mod_type = "foo";
      mod.mod_values = vals;
      printf( "%sModifying (%s)\n", id, dn );
      if ( ldap_modify_s( ld, dn, mods ) != LDAP_SUCCESS ) {
         ldap_perror( ld, "ldap_modify_s" );
      }
      free( dn );
   }
}
/* Thread for adding an entry to the directory */
static void *
add_thread( char *id )
{
   LDAPMod mod[5];
   LDAPMod *mods[6];
   char dn[BUFSIZ], name[40];
   char *cnvals[2], *snvals[2], *ocvals[3];
   int i;
   printf( "add_thread\n" );
   /* Set up thread-specific data for LDAP error values */
   tsd_setup();
   /* Initialize the LDAPMod array */
   for ( i = 0; i < 5; i++ ) {
      mods[i] = &mod[i];
   }
   /* Set up the values in the entry's attributes */
   mods[5] = NULL;
   mod[0].mod_op = 0;
   mod[0].mod_type = "cn";
   mod[0].mod_values = cnvals;
   cnvals[1] = NULL;
   mod[1].mod_op = 0;
   mod[1].mod_type = "sn";
   mod[1].mod_values = snvals;
   snvals[1] = NULL;
   mod[2].mod_op = 0;
   mod[2].mod_type = "objectclass";
   mod[2].mod_values = ocvals;
   ocvals[0] = "top";
   ocvals[1] = "person";
   ocvals[2] = NULL;
   mods[3] = NULL;
   for ( ;; ) {
      /* Generate a random name for the new entry */
      sprintf( name, "%d", rand() );
      sprintf( dn, "cn=%s, o=Ace Industry, c=US", name );
      cnvals[0] = name;
      snvals[0] = name;
      /* Add the new entry to the directory */
      printf( "%sAdding entry (%s)\n", id, dn );
      if ( ldap_add_s( ld, dn, mods ) != LDAP_SUCCESS ) {
         ldap_perror( ld, "ldap_add_s" );
      }
   }
}
/* Thread for deleting an entry from the directory */
static void *
delete_thread( char *id )
{
   LDAPMessage *res;
   char dn[BUFSIZ], name[40];
   int entries, msgid, rc;
   printf( "delete_thread\n" );
   /* Set up thread-specific data for LDAP error values */
   tsd_setup();
   /* First search the directory */
   if ( (msgid = ldap_search( ld, BASE, LDAP_SCOPE_SUBTREE,
    "(objectclass=*)", NULL, 0 )) == -1 ) {
      ldap_perror( ld, "delete_thread: ldap_search_s" );
      exit( 1 );
   }
   /* Keep a list of the matching entries */
   entries = 0;
   while ( (rc = ldap_result( ld, msgid, 0, NULL, &res ))
    == LDAP_RES_SEARCH_ENTRY ) {
      entries++;
      ldap_msgfree( res );
   }
   entries++;
   if ( rc == -1 || ldap_result2error( ld, res, 0 ) != LDAP_SUCCESS) {
      ldap_perror( ld, "delete_thread: ldap_search" );
   } else {
      printf( "%sDelete got %d entries\n", id, entries );
   }
   for ( ;; ) {
      /* Generate a random name */
      sprintf( name, "%d", rand() );
      sprintf( dn, "cn=%s, o=Ace Industry, c=US", name );
      /* Delete that entry */
      printf( "%sDeleting entry (%s)\n", id, dn );
      if ( ldap_delete_s( ld, dn ) != LDAP_SUCCESS ) {
         ldap_perror( ld, "ldap_delete_s" );
      }
   }
}
/* Function for allocating memory for mutexes */
static void *
my_mutex_alloc( void )
{
   pthread_mutex_t *mutexp;
   /* Allocate memory for the mutex and initialize it */
   if ( (mutexp = malloc( sizeof(pthread_mutex_t) )) != NULL ) {
      pthread_mutex_init( mutexp, NULL );
   }
   return( mutexp );
}
/* Function for freeing mutexes */
static void
my_mutex_free( void *mutexp )
{
   pthread_mutex_destroy( (pthread_mutex_t *) mutexp );
}
/* Structure for LDAP error values */
struct ldap_error {
   int le_errno; /* Corresponds to the LDAP error code */
   char *le_matched; /* Matching components of the DN, 
                  if an NO_SUCH_OBJECT error occurred */
   char *le_errmsg; /* Error message */
};
/* Function for setting up thread-specific data */
static void
tsd_setup()
{
   void *tsd;
   /* Check if thread-specific data already exists */
   tsd = pthread_getspecific( key );
   if ( tsd != NULL ) {
      fprintf( stderr, "tsd non-null!\n" );
      pthread_exit( NULL );
   }
   /* Allocate memory for the LDAP error values */
   tsd = (void *) calloc( 1, sizeof(struct ldap_error) );
   /* Make the data specific to the calling thread */
   pthread_setspecific( key, tsd );
}
/* Function for setting thread-specific LDAP error values */
static void
set_ld_error( int err, char *matched, char *errmsg, void *dummy )
{
   struct ldap_error *le;
   /* Get the data structure specific to the calling thread */
   le = pthread_getspecific( key );
   /* Set the error code returned by the LDAP operation */
   le->le_errno = err;
   /* Specify the components of the DN that matched (if 
      an "NO_SUCH_OBJECT" error occurred */
   if ( le->le_matched != NULL ) {
      ldap_memfree( le->le_matched );
   }
   le->le_matched = matched;
   /* Specify the error message corresponding to the error code */
   if ( le->le_errmsg != NULL ) {
      ldap_memfree( le->le_errmsg );
   }
   le->le_errmsg = errmsg;
}
/* Function for getting the thread-specific LDAP error values */
static int
get_ld_error( char **matched, char **errmsg, void *dummy )
{
   struct ldap_error *le;
   /* Get the data values specific to the calling thread */
   le = pthread_getspecific( key );
   /* Retrieve the error values */
   if ( matched != NULL ) {
      *matched = le->le_matched;
   }
   if ( errmsg != NULL ) {
      *errmsg = le->le_errmsg;
   }
   return( le->le_errno );
}
/* Function for setting the value of the errno variable */
static void
set_errno( int err )
{
   errno = err;
}
/* Function for getting the value of the errno variable */
static int
get_errno( void )
{
   return( errno );
}


[Previous] [Next] [TOC] [Index]

Last modified: March 31, 1997
Copyright © 1997 Netscape Communications Corporation