diff options
Diffstat (limited to '')
-rw-r--r-- | libraries/libldap/thr_debug.c | 1338 |
1 files changed, 1338 insertions, 0 deletions
diff --git a/libraries/libldap/thr_debug.c b/libraries/libldap/thr_debug.c new file mode 100644 index 0000000..9e028b3 --- /dev/null +++ b/libraries/libldap/thr_debug.c @@ -0,0 +1,1338 @@ +/* thr_debug.c - wrapper around the chosen thread wrapper, for debugging. */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +/* + * This package provides several types of thread operation debugging: + * + * - Check the results of operations on threads, mutexes, condition + * variables and read/write locks. Also check some thread pool + * operations, but not those for which failure can happen in normal + * slapd operation. + * + * - Wrap those types except threads and pools in structs with state + * information, and check that on all operations: + * + * + Check that the resources are initialized and are only used at + * their original address (i.e. not realloced or copied). + * + * + Check the owner (thread ID) on mutex operations. + * + * + Optionally allocate a reference to a byte of dummy memory. + * This lets malloc debuggers see some incorrect use as memory + * leaks, access to freed memory, etc. + * + * - Print an error message and by default abort() upon errors. + * + * - Print a count of leaked thread resources after cleanup. + * + * Compile-time (./configure) setup: Macros defined in CPPFLAGS. + * + * LDAP_THREAD_DEBUG or LDAP_THREAD_DEBUG=2 + * Enables debugging, but value & 2 turns off type wrapping. + * + * LDAP_UINTPTR_T=integer type to hold pointers, preferably unsigned. + * Used by dummy memory option "scramble". Default = unsigned long. + * + * LDAP_DEBUG_THREAD_NONE = initializer for a "no thread" thread ID. + * + * In addition, you may need to set up an implementation-specific way + * to enable whatever error checking your thread library provides. + * Currently only implemented for Posix threads (pthreads), where + * you may need to define LDAP_INT_THREAD_MUTEXATTR. The default + * is PTHREAD_MUTEX_ERRORCHECK, or PTHREAD_MUTEX_ERRORCHECK_NP for + * Linux threads. See pthread_mutexattr_settype(3). + * + * Run-time configuration: + * + * Memory debugging tools: + * Tools that report uninitialized memory accesses should disable + * such warnings about the function debug_already_initialized(). + * Alternatively, include "noreinit" (below) in $LDAP_THREAD_DEBUG. + * + * Environment variable $LDAP_THREAD_DEBUG: + * The variable may contain a comma- or space-separated option list. + * Options: + * off - Disable this package. (It still slows things down). + * tracethreads - Report create/join/exit/kill of threads. + * noabort - Do not abort() on errors. + * noerror - Do not report errors. Implies noabort. + * nocount - Do not report counts of unreleased resources. + * nosync - Disable tests that use synchronization and thus + * clearly affect thread scheduling: + * Implies nocount, and cancels threadID if that is set. + * Note that if you turn on tracethreads or malloc + * debugging, these also use library calls which may + * affect thread scheduling (fprintf and malloc). + * The following options do not apply if type wrapping is disabled: + * nomem - Do not check memory operations. + * Implies noreinit,noalloc. + * noreinit - Do not catch reinitialization of existing resources. + * (That test accesses uninitialized memory). + * threadID - Trace thread IDs. Currently mostly useless. + * Malloc debugging -- allocate dummy memory for initialized + * resources, so malloc debuggers will report them as memory leaks: + * noalloc - Default. Do not allocate dummy memory. + * alloc - Store a pointer to dummy memory. However, leak + * detectors might not catch unreleased resources in + * global variables. + * scramble - Store bitwise complement of dummy memory pointer. + * That never escapes memory leak detectors - + * but detection while the program is running will + * report active resources as leaks. Do not + * use this if a garbage collector is in use:-) + * adjptr - Point to end of dummy memory. + * Purify reports these as "potential leaks" (PLK). + * I have not checked other malloc debuggers. + */ + +#include "portable.h" + +#if defined( LDAP_THREAD_DEBUG ) + +#include <stdio.h> +#include <ac/errno.h> +#include <ac/stdlib.h> +#include <ac/string.h> + +#include "ldap_pvt_thread.h" /* Get the thread interface */ +#define LDAP_THREAD_IMPLEMENTATION +#define LDAP_THREAD_DEBUG_IMPLEMENTATION +#define LDAP_THREAD_RDWR_IMPLEMENTATION +#define LDAP_THREAD_POOL_IMPLEMENTATION +#include "ldap_thr_debug.h" /* Get the underlying implementation */ + +#ifndef LDAP_THREAD_DEBUG_WRAP +#undef LDAP_THREAD_DEBUG_THREAD_ID +#elif !defined LDAP_THREAD_DEBUG_THREAD_ID +#define LDAP_THREAD_DEBUG_THREAD_ID 1 +#endif + +/* Use native malloc - the OpenLDAP wrappers may defeat malloc debuggers */ +#undef malloc +#undef calloc +#undef realloc +#undef free + + +/* Options from environment variable $LDAP_THREAD_DEBUG */ +enum { Count_no = 0, Count_yes, Count_reported, Count_reported_more }; +static int count = Count_yes; +#ifdef LDAP_THREAD_DEBUG_WRAP +enum { Wrap_noalloc, Wrap_alloc, Wrap_scramble, Wrap_adjptr }; +static int wraptype = Wrap_noalloc, wrap_offset, unwrap_offset; +static int nomem, noreinit; +#endif +#if LDAP_THREAD_DEBUG_THREAD_ID +0 +static int threadID; +#else +enum { threadID = 0 }; +#endif +static int nodebug, noabort, noerror, nosync, tracethreads; +static int wrap_threads; +static int options_done; + + +/* ldap_pvt_thread_initialize() called, ldap_pvt_thread_destroy() not called */ +static int threading_enabled; + + +/* Resource counts */ +enum { + Idx_unexited_thread, Idx_unjoined_thread, Idx_locked_mutex, + Idx_mutex, Idx_cond, Idx_rdwr, Idx_tpool, Idx_max +}; +static int resource_counts[Idx_max]; +static const char *const resource_names[] = { + "unexited threads", "unjoined threads", "locked mutexes", + "mutexes", "conds", "rdwrs", "thread pools" +}; +static ldap_int_thread_mutex_t resource_mutexes[Idx_max]; + + +/* Hide pointers from malloc debuggers. */ +#define SCRAMBLE(ptr) (~(LDAP_UINTPTR_T) (ptr)) +#define UNSCRAMBLE_usagep(num) ((ldap_debug_usage_info_t *) ~(num)) +#define UNSCRAMBLE_dummyp(num) ((unsigned char *) ~(num)) + + +#define WARN(var, msg) (warn (__FILE__, __LINE__, (msg), #var, (var))) +#define WARN_IF(rc, msg) {if (rc) warn (__FILE__, __LINE__, (msg), #rc, (rc));} + +#define ERROR(var, msg) { \ + if (!noerror) { \ + errmsg(__FILE__, __LINE__, (msg), #var, (var)); \ + if( !noabort ) abort(); \ + } \ +} + +#define ERROR_IF(rc, msg) { \ + if (!noerror) { \ + int rc_ = (rc); \ + if (rc_) { \ + errmsg(__FILE__, __LINE__, (msg), #rc, rc_); \ + if( !noabort ) abort(); \ + } \ + } \ +} + +#ifdef LDAP_THREAD_DEBUG_WRAP +#define MEMERROR_IF(rc, msg, mem_act) { \ + if (!noerror) { \ + int rc_ = (rc); \ + if (rc_) { \ + errmsg(__FILE__, __LINE__, (msg), #rc, rc_); \ + if( wraptype != Wrap_noalloc ) { mem_act; } \ + if( !noabort ) abort(); \ + } \ + } \ +} +#endif /* LDAP_THREAD_DEBUG_WRAP */ + +#if 0 +static void +warn( const char *file, int line, const char *msg, const char *var, int val ) +{ + fprintf( stderr, + (strpbrk( var, "!=" ) + ? "%s:%d: %s warning: %s\n" + : "%s:%d: %s warning: %s is %d\n"), + file, line, msg, var, val ); +} +#endif + +static void +errmsg( const char *file, int line, const char *msg, const char *var, int val ) +{ + fprintf( stderr, + (strpbrk( var, "!=" ) + ? "%s:%d: %s error: %s\n" + : "%s:%d: %s error: %s is %d\n"), + file, line, msg, var, val ); +} + +static void +count_resource_leaks( void ) +{ + int i, j; + char errbuf[200]; + if( count == Count_yes ) { + count = Count_reported; +#if 0 /* Could break if there are still threads after atexit */ + for( i = j = 0; i < Idx_max; i++ ) + j |= ldap_int_thread_mutex_destroy( &resource_mutexes[i] ); + WARN_IF( j, "ldap_debug_thread_destroy:mutexes" ); +#endif + for( i = j = 0; i < Idx_max; i++ ) + if( resource_counts[i] ) + j += sprintf( errbuf + j, ", %d %s", + resource_counts[i], resource_names[i] ); + if( j ) + fprintf( stderr, "== thr_debug: Leaked%s. ==\n", errbuf + 1 ); + } +} + +static void +get_options( void ) +{ + static const struct option_info_s { + const char *name; + int *var, val; + } option_info[] = { + { "off", &nodebug, 1 }, + { "noabort", &noabort, 1 }, + { "noerror", &noerror, 1 }, + { "nocount", &count, Count_no }, + { "nosync", &nosync, 1 }, +#if LDAP_THREAD_DEBUG_THREAD_ID +0 + { "threadID", &threadID, 1 }, +#endif +#ifdef LDAP_THREAD_DEBUG_WRAP + { "nomem", &nomem, 1 }, + { "noreinit", &noreinit, 1 }, + { "noalloc", &wraptype, Wrap_noalloc }, + { "alloc", &wraptype, Wrap_alloc }, + { "adjptr", &wraptype, Wrap_adjptr }, + { "scramble", &wraptype, Wrap_scramble }, +#endif + { "tracethreads", &tracethreads, 1 }, + { NULL, NULL, 0 } + }; + const char *s = getenv( "LDAP_THREAD_DEBUG" ); + if( s != NULL ) { + while( *(s += strspn( s, ", \t\r\n" )) != '\0' ) { + size_t optlen = strcspn( s, ", \t\r\n" ); + const struct option_info_s *oi = option_info; + while( oi->name && + (strncasecmp( oi->name, s, optlen ) || oi->name[optlen]) ) + oi++; + if( oi->name ) + *oi->var = oi->val; + else + fprintf( stderr, + "== thr_debug: Unknown $%s option '%.*s' ==\n", + "LDAP_THREAD_DEBUG", (int) optlen, s ); + s += optlen; + } + } + if( nodebug ) { + tracethreads = 0; + nosync = noerror = 1; + } + if( nosync ) + count = Count_no; + if( noerror ) + noabort = 1; +#if LDAP_THREAD_DEBUG_THREAD_ID +0 + if( nosync ) + threadID = 0; +#endif +#ifdef LDAP_THREAD_DEBUG_WRAP + if( noerror ) + nomem = 1; + if( !nomem ) { + static const ldap_debug_usage_info_t usage; + if( sizeof(LDAP_UINTPTR_T) < sizeof(unsigned char *) + || sizeof(LDAP_UINTPTR_T) < sizeof(ldap_debug_usage_info_t *) + || UNSCRAMBLE_usagep( SCRAMBLE( &usage ) ) != &usage + || UNSCRAMBLE_dummyp( SCRAMBLE( (unsigned char *) 0 ) ) ) + { + fputs( "== thr_debug: Memory checks unsupported, " + "adding nomem to $LDAP_THREAD_DEBUG ==\n", stderr ); + nomem = 1; + } + } + if( nomem ) { + noreinit = 1; + wraptype = Wrap_noalloc; + } + unwrap_offset = -(wrap_offset = (wraptype == Wrap_adjptr)); +#endif + wrap_threads = (tracethreads || threadID || count); + options_done = 1; +} + + +#ifndef LDAP_THREAD_DEBUG_WRAP + +#define WRAPPED(ptr) (ptr) +#define GET_OWNER(ptr) 0 +#define SET_OWNER(ptr, thread) ((void) 0) +#define RESET_OWNER(ptr) ((void) 0) +#define ASSERT_OWNER(ptr, msg) ((void) 0) +#define ASSERT_NO_OWNER(ptr, msg) ((void) 0) + +#define init_usage(ptr, msg) ((void) 0) +#define check_usage(ptr, msg) ((void) 0) +#define destroy_usage(ptr) ((void) 0) + +#else /* LDAP_THREAD_DEBUG_WRAP */ + +/* Specialize this if the initializer is not appropriate. */ +/* The ASSERT_NO_OWNER() definition may also need an override. */ +#ifndef LDAP_DEBUG_THREAD_NONE +#define LDAP_DEBUG_THREAD_NONE { -1 } /* "no thread" ldap_int_thread_t value */ +#endif + +static const ldap_int_thread_t ldap_debug_thread_none = LDAP_DEBUG_THREAD_NONE; + +#define THREAD_MUTEX_OWNER(mutex) \ + ldap_int_thread_equal( (mutex)->owner, ldap_int_thread_self() ) + +void +ldap_debug_thread_assert_mutex_owner( + const char *file, + int line, + const char *msg, + ldap_pvt_thread_mutex_t *mutex ) +{ + if( !(noerror || THREAD_MUTEX_OWNER( mutex )) ) { + errmsg( file, line, msg, "ASSERT_MUTEX_OWNER", 0 ); + if( !noabort ) abort(); + } +} + +#define WRAPPED(ptr) (&(ptr)->wrapped) +#define GET_OWNER(ptr) ((ptr)->owner) +#define SET_OWNER(ptr, thread) ((ptr)->owner = (thread)) +#define RESET_OWNER(ptr) ((ptr)->owner = ldap_debug_thread_none) +#define ASSERT_OWNER(ptr, msg) ERROR_IF( !THREAD_MUTEX_OWNER( ptr ), msg ) +#ifndef ASSERT_NO_OWNER +#define ASSERT_NO_OWNER(ptr, msg) ERROR_IF( \ + !ldap_int_thread_equal( (ptr)->owner, ldap_debug_thread_none ), msg ) +#endif + +/* Try to provoke memory access error (for malloc debuggers) */ +#define PEEK(mem) {if (-*(volatile const unsigned char *)(mem)) debug_noop();} + +static void debug_noop( void ); +static int debug_already_initialized( const ldap_debug_usage_info_t *usage ); + +/* Name used for clearer error message */ +#define IS_COPY_OR_MOVED(usage) ((usage)->self != SCRAMBLE( usage )) + +#define DUMMY_ADDR(usage) \ + (wraptype == Wrap_scramble \ + ? UNSCRAMBLE_dummyp( (usage)->mem.num ) \ + : (usage)->mem.ptr + unwrap_offset) + +/* Mark resource as initialized */ +static void +init_usage( ldap_debug_usage_info_t *usage, const char *msg ) +{ + if( !options_done ) + get_options(); + if( !nomem ) { + if( !noreinit ) { + MEMERROR_IF( debug_already_initialized( usage ), msg, { + /* Provoke malloc debuggers */ + unsigned char *dummy = DUMMY_ADDR( usage ); + PEEK( dummy ); + free( dummy ); + free( dummy ); + } ); + } + if( wraptype != Wrap_noalloc ) { + unsigned char *dummy = malloc( 1 ); + assert( dummy != NULL ); + if( wraptype == Wrap_scramble ) { + usage->mem.num = SCRAMBLE( dummy ); + /* Verify that ptr<->integer casts work on this host */ + assert( UNSCRAMBLE_dummyp( usage->mem.num ) == dummy ); + } else { + usage->mem.ptr = dummy + wrap_offset; + } + } + } else { + /* Unused, but set for readability in debugger */ + usage->mem.ptr = NULL; + } + usage->self = SCRAMBLE( usage ); /* If nomem, only for debugger */ + usage->magic = ldap_debug_magic; + usage->state = ldap_debug_state_inited; +} + +/* Check that resource is initialized and not copied/realloced */ +static void +check_usage( const ldap_debug_usage_info_t *usage, const char *msg ) +{ + enum { Is_destroyed = 1 }; /* Name used for clearer error message */ + + if( usage->magic != ldap_debug_magic ) { + ERROR( usage->magic, msg ); + return; + } + switch( usage->state ) { + case ldap_debug_state_destroyed: + MEMERROR_IF( Is_destroyed, msg, { + PEEK( DUMMY_ADDR( usage ) ); + } ); + break; + default: + ERROR( usage->state, msg ); + break; + case ldap_debug_state_inited: + if( !nomem ) { + MEMERROR_IF( IS_COPY_OR_MOVED( usage ), msg, { + PEEK( DUMMY_ADDR( usage ) ); + PEEK( UNSCRAMBLE_usagep( usage->self ) ); + } ); + } + break; + } +} + +/* Mark resource as destroyed. */ +/* Does not check for errors, call check_usage()/init_usage() first. */ +static void +destroy_usage( ldap_debug_usage_info_t *usage ) +{ + if( usage->state == ldap_debug_state_inited ) { + if( wraptype != Wrap_noalloc ) { + free( DUMMY_ADDR( usage ) ); + /* Do not reset the DUMMY_ADDR, leave it for malloc debuggers + * in case the resource is used after it is freed. */ + } + usage->state = ldap_debug_state_destroyed; + } +} + +/* Define these after they are used, so they are hopefully not inlined */ + +static void +debug_noop( void ) +{ +} + +/* + * Valid programs access uninitialized memory here unless "noreinit". + * + * Returns true if the resource is initialized and not copied/realloced. + */ +LDAP_GCCATTR((noinline)) +static int +debug_already_initialized( const ldap_debug_usage_info_t *usage ) +{ + /* + * 'ret' keeps the Valgrind warning "Conditional jump or move + * depends on uninitialised value(s)" _inside_ this function. + */ + volatile int ret = 0; + if( usage->state == ldap_debug_state_inited ) + if( !IS_COPY_OR_MOVED( usage ) ) + if( usage->magic == ldap_debug_magic ) + ret = 1; + return ret; +} + +#endif /* LDAP_THREAD_DEBUG_WRAP */ + + +#if !(LDAP_THREAD_DEBUG_THREAD_ID +0) + +typedef void ldap_debug_thread_t; +#define init_thread_info() {} +#define with_thread_info_lock(statements) { statements; } +#define thread_info_detached(t) 0 +#define add_thread_info(msg, thr, det) ((void) 0) +#define remove_thread_info(tinfo, msg) ((void) 0) +#define get_thread_info(thread, msg) NULL + +#else /* LDAP_THREAD_DEBUG_THREAD_ID */ + +/* + * Thread ID tracking. Currently achieves little. + * Should be either expanded or deleted. + */ + +/* + * Array of threads. Used instead of making ldap_pvt_thread_t a wrapper + * around ldap_int_thread_t, which would slow down ldap_pvt_thread_self(). + */ +typedef struct { + ldap_pvt_thread_t wrapped; + ldap_debug_usage_info_t usage; + int detached; + int idx; +} ldap_debug_thread_t; + +static ldap_debug_thread_t **thread_info; +static unsigned int thread_info_size, thread_info_used; +static ldap_int_thread_mutex_t thread_info_mutex; + +#define init_thread_info() { \ + if( threadID ) { \ + int mutex_init_rc = ldap_int_thread_mutex_init( &thread_info_mutex ); \ + assert( mutex_init_rc == 0 ); \ + } \ +} + +#define with_thread_info_lock(statements) { \ + int rc_wtl_ = ldap_int_thread_mutex_lock( &thread_info_mutex ); \ + assert( rc_wtl_ == 0 ); \ + { statements; } \ + rc_wtl_ = ldap_int_thread_mutex_unlock( &thread_info_mutex ); \ + assert( rc_wtl_ == 0 ); \ +} + +#define thread_info_detached(t) ((t)->detached) + +static void +add_thread_info( + const char *msg, + const ldap_pvt_thread_t *thread, + int detached ) +{ + ldap_debug_thread_t *t; + + if( thread_info_used >= thread_info_size ) { + unsigned int more = thread_info_size + 8; + unsigned int new_size = thread_info_size + more; + + t = calloc( more, sizeof(ldap_debug_thread_t) ); + assert( t != NULL ); + thread_info = realloc( thread_info, new_size * sizeof(*thread_info) ); + assert( thread_info != NULL ); + do { + t->idx = thread_info_size; + thread_info[thread_info_size++] = t++; + } while( thread_info_size < new_size ); + } + + t = thread_info[thread_info_used]; + init_usage( &t->usage, msg ); + t->wrapped = *thread; + t->detached = detached; + thread_info_used++; +} + +static void +remove_thread_info( ldap_debug_thread_t *t, const char *msg ) +{ + ldap_debug_thread_t *last; + int idx; + check_usage( &t->usage, msg ); + destroy_usage( &t->usage ); + idx = t->idx; + assert( thread_info[idx] == t ); + last = thread_info[--thread_info_used]; + assert( last->idx == thread_info_used ); + (thread_info[idx] = last)->idx = idx; + (thread_info[thread_info_used] = t )->idx = thread_info_used; +} + +static ldap_debug_thread_t * +get_thread_info( ldap_pvt_thread_t thread, const char *msg ) +{ + unsigned int i; + ldap_debug_thread_t *t; + for( i = 0; i < thread_info_used; i++ ) { + if( ldap_pvt_thread_equal( thread, thread_info[i]->wrapped ) ) + break; + } + ERROR_IF( i == thread_info_used, msg ); + t = thread_info[i]; + check_usage( &t->usage, msg ); + return t; +} + +#endif /* LDAP_THREAD_DEBUG_THREAD_ID */ + + +static char * +thread_name( char *buf, int bufsize, ldap_pvt_thread_t thread ) +{ + int i; + --bufsize; + if( bufsize > 2*sizeof(thread) ) + bufsize = 2*sizeof(thread); + for( i = 0; i < bufsize; i += 2 ) + snprintf( buf+i, 3, "%02x", ((unsigned char *)&thread)[i/2] ); + return buf; +} + + +/* Add <adjust> (+/-1) to resource count <which> unless "nocount". */ +static void +adjust_count( int which, int adjust ) +{ + int rc; + switch( count ) { + case Count_no: + break; + case Count_yes: + rc = ldap_int_thread_mutex_lock( &resource_mutexes[which] ); + assert( rc == 0 ); + resource_counts[which] += adjust; + rc = ldap_int_thread_mutex_unlock( &resource_mutexes[which] ); + assert( rc == 0 ); + break; + case Count_reported: + fputs( "== thr_debug: More thread activity after exit ==\n", stderr ); + count = Count_reported_more; + /* FALL THROUGH */ + case Count_reported_more: + /* Not used, but result might be inspected with debugger */ + /* (Hopefully threading is disabled by now...) */ + resource_counts[which] += adjust; + break; + } +} + + +/* Wrappers for LDAP_THREAD_IMPLEMENTATION: */ + +/* Used instead of ldap_int_thread_initialize by ldap_pvt_thread_initialize */ +int +ldap_debug_thread_initialize( void ) +{ + int i, rc, rc2; + if( !options_done ) + get_options(); + ERROR_IF( threading_enabled, "ldap_debug_thread_initialize" ); + threading_enabled = 1; + rc = ldap_int_thread_initialize(); + if( rc ) { + ERROR( rc, "ldap_debug_thread_initialize:threads" ); + threading_enabled = 0; + } else { + init_thread_info(); + if( count != Count_no ) { + for( i = rc2 = 0; i < Idx_max; i++ ) + rc2 |= ldap_int_thread_mutex_init( &resource_mutexes[i] ); + assert( rc2 == 0 ); + /* FIXME: Only for static libldap as in init.c? If so, why? */ + atexit( count_resource_leaks ); + } + } + return rc; +} + +/* Used instead of ldap_int_thread_destroy by ldap_pvt_thread_destroy */ +int +ldap_debug_thread_destroy( void ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_debug_thread_destroy" ); + /* sleep(1) -- need to wait for thread pool to finish? */ + rc = ldap_int_thread_destroy(); + if( rc ) { + ERROR( rc, "ldap_debug_thread_destroy:threads" ); + } else { + threading_enabled = 0; + } + return rc; +} + +int +ldap_pvt_thread_set_concurrency( int n ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_set_concurrency" ); + rc = ldap_int_thread_set_concurrency( n ); + ERROR_IF( rc, "ldap_pvt_thread_set_concurrency" ); + return rc; +} + +int +ldap_pvt_thread_get_concurrency( void ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_get_concurrency" ); + rc = ldap_int_thread_get_concurrency(); + ERROR_IF( rc, "ldap_pvt_thread_get_concurrency" ); + return rc; +} + +unsigned int +ldap_pvt_thread_sleep( unsigned int interval ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_sleep" ); + rc = ldap_int_thread_sleep( interval ); + ERROR_IF( rc, "ldap_pvt_thread_sleep" ); + return 0; +} + +static void +thread_exiting( const char *how, const char *msg ) +{ + ldap_pvt_thread_t thread; +#if 0 /* Detached threads may exit after ldap_debug_thread_destroy(). */ + ERROR_IF( !threading_enabled, msg ); +#endif + thread = ldap_pvt_thread_self(); + if( tracethreads ) { + char buf[40]; + fprintf( stderr, "== thr_debug: %s thread %s ==\n", + how, thread_name( buf, sizeof(buf), thread ) ); + } + if( threadID ) { + with_thread_info_lock({ + ldap_debug_thread_t *t = get_thread_info( thread, msg ); + if( thread_info_detached( t ) ) + remove_thread_info( t, msg ); + }); + } + adjust_count( Idx_unexited_thread, -1 ); +} + +void +ldap_pvt_thread_exit( void *retval ) +{ + thread_exiting( "Exiting", "ldap_pvt_thread_exit" ); + ldap_int_thread_exit( retval ); +} + +typedef struct { + void *(*start_routine)( void * ); + void *arg; +} ldap_debug_thread_call_t; + +static void * +ldap_debug_thread_wrapper( void *arg ) +{ + void *ret; + ldap_debug_thread_call_t call = *(ldap_debug_thread_call_t *)arg; + free( arg ); + ret = call.start_routine( call.arg ); + thread_exiting( "Returning from", "ldap_debug_thread_wrapper" ); + return ret; +} + +int +ldap_pvt_thread_create( + ldap_pvt_thread_t *thread, + int detach, + void *(*start_routine)( void * ), + void *arg ) +{ + int rc; + if( !options_done ) + get_options(); + ERROR_IF( !threading_enabled, "ldap_pvt_thread_create" ); + + if( wrap_threads ) { + ldap_debug_thread_call_t *call = malloc( + sizeof( ldap_debug_thread_call_t ) ); + assert( call != NULL ); + call->start_routine = start_routine; + call->arg = arg; + start_routine = ldap_debug_thread_wrapper; + arg = call; + } + if( threadID ) { + with_thread_info_lock({ + rc = ldap_int_thread_create( thread, detach, start_routine, arg ); + if( rc == 0 ) + add_thread_info( "ldap_pvt_thread_create", thread, detach ); + }); + } else { + rc = ldap_int_thread_create( thread, detach, start_routine, arg ); + } + if( rc ) { + ERROR( rc, "ldap_pvt_thread_create" ); + if( wrap_threads ) + free( arg ); + } else { + if( tracethreads ) { + char buf[40], buf2[40]; + fprintf( stderr, + "== thr_debug: Created thread %s%s from thread %s ==\n", + thread_name( buf, sizeof(buf), *thread ), + detach ? " (detached)" : "", + thread_name( buf2, sizeof(buf2), ldap_pvt_thread_self() ) ); + } + adjust_count( Idx_unexited_thread, +1 ); + if( !detach ) + adjust_count( Idx_unjoined_thread, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_join( ldap_pvt_thread_t thread, void **thread_return ) +{ + int rc; + ldap_debug_thread_t *t = NULL; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_join" ); + if( tracethreads ) { + char buf[40], buf2[40]; + fprintf( stderr, "== thr_debug: Joining thread %s in thread %s ==\n", + thread_name( buf, sizeof(buf), thread ), + thread_name( buf2, sizeof(buf2), ldap_pvt_thread_self() ) ); + } + if( threadID ) + with_thread_info_lock( { + t = get_thread_info( thread, "ldap_pvt_thread_join" ); + ERROR_IF( thread_info_detached( t ), "ldap_pvt_thread_join" ); + } ); + rc = ldap_int_thread_join( thread, thread_return ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_join" ); + } else { + if( threadID ) + with_thread_info_lock( + remove_thread_info( t, "ldap_pvt_thread_join" ) ); + adjust_count( Idx_unjoined_thread, -1 ); + } + + return rc; +} + +int +ldap_pvt_thread_kill( ldap_pvt_thread_t thread, int signo ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_kill" ); + if( tracethreads ) { + char buf[40], buf2[40]; + fprintf( stderr, + "== thr_debug: Killing thread %s (sig %i) from thread %s ==\n", + thread_name( buf, sizeof(buf), thread ), signo, + thread_name( buf2, sizeof(buf2), ldap_pvt_thread_self() ) ); + } + rc = ldap_int_thread_kill( thread, signo ); + ERROR_IF( rc, "ldap_pvt_thread_kill" ); + return rc; +} + +int +ldap_pvt_thread_yield( void ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_yield" ); + rc = ldap_int_thread_yield(); + ERROR_IF( rc, "ldap_pvt_thread_yield" ); + return rc; +} + +ldap_pvt_thread_t +ldap_pvt_thread_self( void ) +{ +#if 0 /* Function is used by ch_free() via slap_sl_contxt() in slapd */ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_self" ); +#endif + return ldap_int_thread_self(); +} + +int +ldap_pvt_thread_cond_init( ldap_pvt_thread_cond_t *cond ) +{ + int rc; + init_usage( &cond->usage, "ldap_pvt_thread_cond_init" ); + rc = ldap_int_thread_cond_init( WRAPPED( cond ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_cond_init" ); + destroy_usage( &cond->usage ); + } else { + adjust_count( Idx_cond, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_cond_destroy( ldap_pvt_thread_cond_t *cond ) +{ + int rc; + check_usage( &cond->usage, "ldap_pvt_thread_cond_destroy" ); + rc = ldap_int_thread_cond_destroy( WRAPPED( cond ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_cond_destroy" ); + } else { + destroy_usage( &cond->usage ); + adjust_count( Idx_cond, -1 ); + } + return rc; +} + +int +ldap_pvt_thread_cond_signal( ldap_pvt_thread_cond_t *cond ) +{ + int rc; + check_usage( &cond->usage, "ldap_pvt_thread_cond_signal" ); + rc = ldap_int_thread_cond_signal( WRAPPED( cond ) ); + ERROR_IF( rc, "ldap_pvt_thread_cond_signal" ); + return rc; +} + +int +ldap_pvt_thread_cond_broadcast( ldap_pvt_thread_cond_t *cond ) +{ + int rc; + check_usage( &cond->usage, "ldap_pvt_thread_cond_broadcast" ); + rc = ldap_int_thread_cond_broadcast( WRAPPED( cond ) ); + ERROR_IF( rc, "ldap_pvt_thread_cond_broadcast" ); + return rc; +} + +int +ldap_pvt_thread_cond_wait( + ldap_pvt_thread_cond_t *cond, + ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + ldap_int_thread_t owner; + check_usage( &cond->usage, "ldap_pvt_thread_cond_wait:cond" ); + check_usage( &mutex->usage, "ldap_pvt_thread_cond_wait:mutex" ); + adjust_count( Idx_locked_mutex, -1 ); + owner = GET_OWNER( mutex ); + ASSERT_OWNER( mutex, "ldap_pvt_thread_cond_wait" ); + RESET_OWNER( mutex ); + rc = ldap_int_thread_cond_wait( WRAPPED( cond ), WRAPPED( mutex ) ); + ASSERT_NO_OWNER( mutex, "ldap_pvt_thread_cond_wait" ); + SET_OWNER( mutex, rc ? owner : ldap_int_thread_self() ); + adjust_count( Idx_locked_mutex, +1 ); + ERROR_IF( rc, "ldap_pvt_thread_cond_wait" ); + return rc; +} + +int +ldap_pvt_thread_mutex_recursive_init( ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + init_usage( &mutex->usage, "ldap_pvt_thread_mutex_recursive_init" ); + rc = ldap_int_thread_mutex_recursive_init( WRAPPED( mutex ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_mutex_recursive_init" ); + destroy_usage( &mutex->usage ); + } else { + RESET_OWNER( mutex ); + adjust_count( Idx_mutex, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_mutex_init( ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + init_usage( &mutex->usage, "ldap_pvt_thread_mutex_init" ); + rc = ldap_int_thread_mutex_init( WRAPPED( mutex ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_mutex_init" ); + destroy_usage( &mutex->usage ); + } else { + RESET_OWNER( mutex ); + adjust_count( Idx_mutex, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_mutex_destroy( ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + check_usage( &mutex->usage, "ldap_pvt_thread_mutex_destroy" ); + ASSERT_NO_OWNER( mutex, "ldap_pvt_thread_mutex_destroy" ); + rc = ldap_int_thread_mutex_destroy( WRAPPED( mutex ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_mutex_destroy" ); + } else { + destroy_usage( &mutex->usage ); + RESET_OWNER( mutex ); + adjust_count( Idx_mutex, -1 ); + } + return rc; +} + +int +ldap_pvt_thread_mutex_lock( ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + check_usage( &mutex->usage, "ldap_pvt_thread_mutex_lock" ); + rc = ldap_int_thread_mutex_lock( WRAPPED( mutex ) ); + if( rc ) { + ERROR_IF( rc, "ldap_pvt_thread_mutex_lock" ); + } else { + ASSERT_NO_OWNER( mutex, "ldap_pvt_thread_mutex_lock" ); + SET_OWNER( mutex, ldap_int_thread_self() ); + adjust_count( Idx_locked_mutex, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_mutex_trylock( ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + check_usage( &mutex->usage, "ldap_pvt_thread_mutex_trylock" ); + rc = ldap_int_thread_mutex_trylock( WRAPPED( mutex ) ); + if( rc == 0 ) { + ASSERT_NO_OWNER( mutex, "ldap_pvt_thread_mutex_trylock" ); + SET_OWNER( mutex, ldap_int_thread_self() ); + adjust_count( Idx_locked_mutex, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_mutex_unlock( ldap_pvt_thread_mutex_t *mutex ) +{ + int rc; + check_usage( &mutex->usage, "ldap_pvt_thread_mutex_unlock" ); + ASSERT_OWNER( mutex, "ldap_pvt_thread_mutex_unlock" ); + RESET_OWNER( mutex ); /* Breaks if this thread did not own the mutex */ + rc = ldap_int_thread_mutex_unlock( WRAPPED( mutex ) ); + if( rc ) { + ERROR_IF( rc, "ldap_pvt_thread_mutex_unlock" ); + } else { + adjust_count( Idx_locked_mutex, -1 ); + } + return rc; +} + + +/* Wrappers for LDAP_THREAD_RDWR_IMPLEMENTATION: */ + +int +ldap_pvt_thread_rdwr_init( ldap_pvt_thread_rdwr_t *rwlock ) +{ + int rc; + init_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_init" ); + rc = ldap_int_thread_rdwr_init( WRAPPED( rwlock ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_rdwr_init" ); + destroy_usage( &rwlock->usage ); + } else { + adjust_count( Idx_rdwr, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_rdwr_destroy( ldap_pvt_thread_rdwr_t *rwlock ) +{ + int rc; + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_destroy" ); + rc = ldap_int_thread_rdwr_destroy( WRAPPED( rwlock ) ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_rdwr_destroy" ); + } else { + destroy_usage( &rwlock->usage ); + adjust_count( Idx_rdwr, -1 ); + } + return rc; +} + +int +ldap_pvt_thread_rdwr_rlock( ldap_pvt_thread_rdwr_t *rwlock ) +{ + int rc; + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_rlock" ); + rc = ldap_int_thread_rdwr_rlock( WRAPPED( rwlock ) ); + ERROR_IF( rc, "ldap_pvt_thread_rdwr_rlock" ); + return rc; +} + +int +ldap_pvt_thread_rdwr_rtrylock( ldap_pvt_thread_rdwr_t *rwlock ) +{ + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_rtrylock" ); + return ldap_int_thread_rdwr_rtrylock( WRAPPED( rwlock ) ); +} + +int +ldap_pvt_thread_rdwr_runlock( ldap_pvt_thread_rdwr_t *rwlock ) +{ + int rc; + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_runlock" ); + rc = ldap_int_thread_rdwr_runlock( WRAPPED( rwlock ) ); + ERROR_IF( rc, "ldap_pvt_thread_rdwr_runlock" ); + return rc; +} + +int +ldap_pvt_thread_rdwr_wlock( ldap_pvt_thread_rdwr_t *rwlock ) +{ + int rc; + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_wlock" ); + rc = ldap_int_thread_rdwr_wlock( WRAPPED( rwlock ) ); + ERROR_IF( rc, "ldap_pvt_thread_rdwr_wlock" ); + return rc; +} + +int +ldap_pvt_thread_rdwr_wtrylock( ldap_pvt_thread_rdwr_t *rwlock ) +{ + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_wtrylock" ); + return ldap_int_thread_rdwr_wtrylock( WRAPPED( rwlock ) ); +} + +int +ldap_pvt_thread_rdwr_wunlock( ldap_pvt_thread_rdwr_t *rwlock ) +{ + int rc; + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_wunlock" ); + rc = ldap_int_thread_rdwr_wunlock( WRAPPED( rwlock ) ); + ERROR_IF( rc, "ldap_pvt_thread_rdwr_wunlock" ); + return rc; +} + +#if defined(LDAP_RDWR_DEBUG) && !defined(LDAP_THREAD_HAVE_RDWR) + +int +ldap_pvt_thread_rdwr_readers( ldap_pvt_thread_rdwr_t *rwlock ) +{ + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_readers" ); + return ldap_int_thread_rdwr_readers( WRAPPED( rwlock ) ); +} + +int +ldap_pvt_thread_rdwr_writers( ldap_pvt_thread_rdwr_t *rwlock ) +{ + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_writers" ); + return ldap_int_thread_rdwr_writers( WRAPPED( rwlock ) ); +} + +int +ldap_pvt_thread_rdwr_active( ldap_pvt_thread_rdwr_t *rwlock ) +{ + check_usage( &rwlock->usage, "ldap_pvt_thread_rdwr_active" ); + return ldap_int_thread_rdwr_active( WRAPPED( rwlock ) ); +} + +#endif /* LDAP_RDWR_DEBUG && !LDAP_THREAD_HAVE_RDWR */ + + +/* Some wrappers for LDAP_THREAD_POOL_IMPLEMENTATION: */ +#ifdef LDAP_THREAD_POOL_IMPLEMENTATION + +int +ldap_pvt_thread_pool_init( + ldap_pvt_thread_pool_t *tpool, + int max_threads, + int max_pending ) +{ + int rc; + if( !options_done ) + get_options(); + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_init" ); + rc = ldap_int_thread_pool_init( tpool, max_threads, max_pending ); + if( rc ) { + ERROR( rc, "ldap_pvt_thread_pool_init" ); + } else { + adjust_count( Idx_tpool, +1 ); + } + return rc; +} + +int +ldap_pvt_thread_pool_submit( + ldap_pvt_thread_pool_t *tpool, + ldap_pvt_thread_start_t *start_routine, void *arg ) +{ + int rc, has_pool; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_submit" ); + has_pool = (tpool && *tpool); + rc = ldap_int_thread_pool_submit( tpool, start_routine, arg ); + if( has_pool ) + ERROR_IF( rc, "ldap_pvt_thread_pool_submit" ); + return rc; +} + +int +ldap_pvt_thread_pool_maxthreads( + ldap_pvt_thread_pool_t *tpool, + int max_threads ) +{ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_maxthreads" ); + return ldap_int_thread_pool_maxthreads( tpool, max_threads ); +} + +int +ldap_pvt_thread_pool_backload( ldap_pvt_thread_pool_t *tpool ) +{ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_backload" ); + return ldap_int_thread_pool_backload( tpool ); +} + +int +ldap_pvt_thread_pool_destroy( ldap_pvt_thread_pool_t *tpool, int run_pending ) +{ + int rc, has_pool; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_destroy" ); + has_pool = (tpool && *tpool); + rc = ldap_int_thread_pool_destroy( tpool, run_pending ); + if( has_pool ) { + if( rc ) { + ERROR( rc, "ldap_pvt_thread_pool_destroy" ); + } else { + adjust_count( Idx_tpool, -1 ); + } + } + return rc; +} + +int +ldap_pvt_thread_pool_close( ldap_pvt_thread_pool_t *tpool, int run_pending ) +{ + int rc, has_pool; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_close" ); + has_pool = (tpool && *tpool); + rc = ldap_int_thread_pool_close( tpool, run_pending ); + if( has_pool && rc ) { + ERROR( rc, "ldap_pvt_thread_pool_close" ); + } + return rc; +} + +int +ldap_pvt_thread_pool_free( ldap_pvt_thread_pool_t *tpool ) +{ + int rc, has_pool; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_free" ); + has_pool = (tpool && *tpool); + rc = ldap_int_thread_pool_free( tpool ); + if( has_pool ) { + if( rc ) { + ERROR( rc, "ldap_pvt_thread_pool_free" ); + } else { + adjust_count( Idx_tpool, -1 ); + } + } + return rc; +} + +int +ldap_pvt_thread_pool_pause( ldap_pvt_thread_pool_t *tpool ) +{ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_pause" ); + return ldap_int_thread_pool_pause( tpool ); +} + +int +ldap_pvt_thread_pool_resume( ldap_pvt_thread_pool_t *tpool ) +{ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_resume" ); + return ldap_int_thread_pool_resume( tpool ); +} + +int +ldap_pvt_thread_pool_getkey( + void *xctx, + void *key, + void **data, + ldap_pvt_thread_pool_keyfree_t **kfree ) +{ +#if 0 /* Function is used by ch_free() via slap_sl_contxt() in slapd */ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_getkey" ); +#endif + return ldap_int_thread_pool_getkey( xctx, key, data, kfree ); +} + +int +ldap_pvt_thread_pool_setkey( + void *xctx, + void *key, + void *data, + ldap_pvt_thread_pool_keyfree_t *kfree, + void **olddatap, + ldap_pvt_thread_pool_keyfree_t **oldkfreep ) +{ + int rc; + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_setkey" ); + rc = ldap_int_thread_pool_setkey( + xctx, key, data, kfree, olddatap, oldkfreep ); + ERROR_IF( rc, "ldap_pvt_thread_pool_setkey" ); + return rc; +} + +void +ldap_pvt_thread_pool_purgekey( void *key ) +{ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_purgekey" ); + ldap_int_thread_pool_purgekey( key ); +} + +void * +ldap_pvt_thread_pool_context( void ) +{ +#if 0 /* Function is used by ch_free() via slap_sl_contxt() in slapd */ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_context" ); +#endif + return ldap_int_thread_pool_context(); +} + +void +ldap_pvt_thread_pool_context_reset( void *vctx ) +{ + ERROR_IF( !threading_enabled, "ldap_pvt_thread_pool_context_reset" ); + ldap_int_thread_pool_context_reset( vctx ); +} + +#endif /* LDAP_THREAD_POOL_IMPLEMENTATION */ + +#endif /* LDAP_THREAD_DEBUG */ |