Home | History | Annotate | Download | only in support
      1 /*
      2  * util/support/threads.c
      3  *
      4  * Copyright 2004,2005,2006 by the Massachusetts Institute of Technology.
      5  * All Rights Reserved.
      6  *
      7  * Export of this software from the United States of America may
      8  *   require a specific license from the United States Government.
      9  *   It is the responsibility of any person or organization contemplating
     10  *   export to obtain such a license before exporting.
     11  *
     12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
     13  * distribute this software and its documentation for any purpose and
     14  * without fee is hereby granted, provided that the above copyright
     15  * notice appear in all copies and that both that copyright notice and
     16  * this permission notice appear in supporting documentation, and that
     17  * the name of M.I.T. not be used in advertising or publicity pertaining
     18  * to distribution of the software without specific, written prior
     19  * permission.  Furthermore if you modify this software you must label
     20  * your software as modified software and not distribute it in such a
     21  * fashion that it might be confused with the original M.I.T. software.
     22  * M.I.T. makes no representations about the suitability of
     23  * this software for any purpose.  It is provided "as is" without express
     24  * or implied warranty.
     25  *
     26  *
     27  * Preliminary thread support.
     28  */
     29 
     30 #include <assert.h>
     31 #include <stdlib.h>
     32 #include <errno.h>
     33 #include "k5-thread.h"
     34 #include "k5-platform.h"
     35 #include "supp-int.h"
     36 
     37 MAKE_INIT_FUNCTION(krb5int_thread_support_init);
     38 MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
     39 
     40 #ifndef ENABLE_THREADS /* no thread support */
     41 
     42 static void (*destructors[K5_KEY_MAX])(void *);
     43 struct tsd_block { void *values[K5_KEY_MAX]; };
     44 static struct tsd_block tsd_no_threads;
     45 static unsigned char destructors_set[K5_KEY_MAX];
     46 
     47 int krb5int_pthread_loaded (void)
     48 {
     49     return 0;
     50 }
     51 
     52 #elif defined(_WIN32)
     53 
     54 static DWORD tls_idx;
     55 static CRITICAL_SECTION key_lock;
     56 struct tsd_block {
     57   void *values[K5_KEY_MAX];
     58 };
     59 static void (*destructors[K5_KEY_MAX])(void *);
     60 static unsigned char destructors_set[K5_KEY_MAX];
     61 
     62 void krb5int_thread_detach_hook (void)
     63 {
     64     /* XXX Memory leak here!
     65        Need to destroy all TLS objects we know about for this thread.  */
     66     struct tsd_block *t;
     67     int i, err;
     68 
     69     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
     70     if (err)
     71 	return;
     72 
     73     t = TlsGetValue(tls_idx);
     74     if (t == NULL)
     75 	return;
     76     for (i = 0; i < K5_KEY_MAX; i++) {
     77 	if (destructors_set[i] && destructors[i] && t->values[i]) {
     78 	    void *v = t->values[i];
     79 	    t->values[i] = 0;
     80 	    (*destructors[i])(v);
     81 	}
     82     }
     83 }
     84 
     85 /* Stub function not used on Windows. */
     86 int krb5int_pthread_loaded (void)
     87 {
     88     return 0;
     89 }
     90 #else /* POSIX threads */
     91 
     92 /* Must support register/delete/register sequence, e.g., if krb5 is
     93    loaded so this support code stays in the process, and gssapi is
     94    loaded, unloaded, and loaded again.  */
     95 
     96 static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;
     97 static void (*destructors[K5_KEY_MAX])(void *);
     98 static unsigned char destructors_set[K5_KEY_MAX];
     99 
    100 /* This is not safe yet!
    101 
    102    Thread termination concurrent with key deletion can cause two
    103    threads to interfere.  It's a bit tricky, since one of the threads
    104    will want to remove this structure from the list being walked by
    105    the other.
    106 
    107    Other cases, like looking up data while the library owning the key
    108    is in the process of being unloaded, we don't worry about.  */
    109 
    110 struct tsd_block {
    111     struct tsd_block *next;
    112     void *values[K5_KEY_MAX];
    113 };
    114 
    115 #ifdef HAVE_PRAGMA_WEAK_REF
    116 # pragma weak pthread_getspecific
    117 # pragma weak pthread_setspecific
    118 # pragma weak pthread_key_create
    119 # pragma weak pthread_key_delete
    120 # pragma weak pthread_create
    121 # pragma weak pthread_join
    122 static volatile int flag_pthread_loaded = -1;
    123 static void loaded_test_aux(void)
    124 {
    125     if (flag_pthread_loaded == -1)
    126 	flag_pthread_loaded = 1;
    127     else
    128 	/* Could we have been called twice?  */
    129 	flag_pthread_loaded = 0;
    130 }
    131 static pthread_once_t loaded_test_once = PTHREAD_ONCE_INIT;
    132 int krb5int_pthread_loaded (void)
    133 {
    134     int x = flag_pthread_loaded;
    135     if (x != -1)
    136 	return x;
    137     if (&pthread_getspecific == 0
    138 	|| &pthread_setspecific == 0
    139 	|| &pthread_key_create == 0
    140 	|| &pthread_key_delete == 0
    141 	|| &pthread_once == 0
    142 	|| &pthread_mutex_lock == 0
    143 	|| &pthread_mutex_unlock == 0
    144 	|| &pthread_mutex_destroy == 0
    145 	|| &pthread_mutex_init == 0
    146 	|| &pthread_self == 0
    147 	|| &pthread_equal == 0
    148 	/* Any program that's really multithreaded will have to be
    149 	   able to create threads.  */
    150 	|| &pthread_create == 0
    151 	|| &pthread_join == 0
    152 	/* Okay, all the interesting functions -- or stubs for them --
    153 	   seem to be present.  If we call pthread_once, does it
    154 	   actually seem to cause the indicated function to get called
    155 	   exactly one time?  */
    156 	|| pthread_once(&loaded_test_once, loaded_test_aux) != 0
    157 	|| pthread_once(&loaded_test_once, loaded_test_aux) != 0
    158 	/* This catches cases where pthread_once does nothing, and
    159 	   never causes the function to get called.  That's a pretty
    160 	   clear violation of the POSIX spec, but hey, it happens.  */
    161 	|| flag_pthread_loaded < 0) {
    162 	flag_pthread_loaded = 0;
    163 	return 0;
    164     }
    165     /* If we wanted to be super-paranoid, we could try testing whether
    166        pthread_get/setspecific work, too.  I don't know -- so far --
    167        of any system with non-functional stubs for those.  */
    168     return flag_pthread_loaded;
    169 }
    170 static struct tsd_block tsd_if_single;
    171 # define GET_NO_PTHREAD_TSD()	(&tsd_if_single)
    172 #else
    173 # define GET_NO_PTHREAD_TSD()	(abort(),(struct tsd_block *)0)
    174 #endif
    175 
    176 static pthread_key_t key;
    177 static void thread_termination(void *);
    178 
    179 static void thread_termination (void *tptr)
    180 {
    181     int err = k5_mutex_lock(&key_lock);
    182     if (err == 0) {
    183         int i, pass, none_found;
    184         struct tsd_block *t = tptr;
    185 
    186         /* Make multiple passes in case, for example, a libkrb5 cleanup
    187             function wants to print out an error message, which causes
    188             com_err to allocate a thread-specific buffer, after we just
    189             freed up the old one.
    190 
    191             Shouldn't actually happen, if we're careful, but check just in
    192             case.  */
    193 
    194         pass = 0;
    195         none_found = 0;
    196         while (pass < 4 && !none_found) {
    197             none_found = 1;
    198             for (i = 0; i < K5_KEY_MAX; i++) {
    199                 if (destructors_set[i] && destructors[i] && t->values[i]) {
    200                     void *v = t->values[i];
    201                     t->values[i] = 0;
    202                     (*destructors[i])(v);
    203                     none_found = 0;
    204                 }
    205             }
    206         }
    207         free (t);
    208         err = k5_mutex_unlock(&key_lock);
    209    }
    210 
    211     /* remove thread from global linked list */
    212 }
    213 
    214 #endif /* no threads vs Win32 vs POSIX */
    215 
    216 void *k5_getspecific (k5_key_t keynum)
    217 {
    218     struct tsd_block *t;
    219     int err;
    220 
    221     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
    222     if (err)
    223 	return NULL;
    224 
    225     assert(keynum >= 0 && keynum < K5_KEY_MAX);
    226     assert(destructors_set[keynum] == 1);
    227 
    228 #ifndef ENABLE_THREADS
    229 
    230     t = &tsd_no_threads;
    231 
    232 #elif defined(_WIN32)
    233 
    234     t = TlsGetValue(tls_idx);
    235 
    236 #else /* POSIX */
    237 
    238     if (K5_PTHREADS_LOADED)
    239 	t = pthread_getspecific(key);
    240     else
    241 	t = GET_NO_PTHREAD_TSD();
    242 
    243 #endif
    244 
    245     if (t == NULL)
    246 	return NULL;
    247     return t->values[keynum];
    248 }
    249 
    250 int k5_setspecific (k5_key_t keynum, void *value)
    251 {
    252     struct tsd_block *t;
    253     int err;
    254 
    255     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
    256     if (err)
    257 	return err;
    258 
    259     assert(keynum >= 0 && keynum < K5_KEY_MAX);
    260     assert(destructors_set[keynum] == 1);
    261 
    262 #ifndef ENABLE_THREADS
    263 
    264     t = &tsd_no_threads;
    265 
    266 #elif defined(_WIN32)
    267 
    268     t = TlsGetValue(tls_idx);
    269     if (t == NULL) {
    270 	int i;
    271 	t = malloc(sizeof(*t));
    272 	if (t == NULL)
    273 	    return errno;
    274 	for (i = 0; i < K5_KEY_MAX; i++)
    275 	    t->values[i] = 0;
    276 	/* add to global linked list */
    277 	/*	t->next = 0; */
    278 	err = TlsSetValue(tls_idx, t);
    279 	if (!err) {
    280 	    free(t);
    281 	    return GetLastError();
    282 	}
    283     }
    284 
    285 #else /* POSIX */
    286 
    287     if (K5_PTHREADS_LOADED) {
    288 	t = pthread_getspecific(key);
    289 	if (t == NULL) {
    290 	    int i;
    291 	    t = malloc(sizeof(*t));
    292 	    if (t == NULL)
    293 		return errno;
    294 	    for (i = 0; i < K5_KEY_MAX; i++)
    295 		t->values[i] = 0;
    296 	    /* add to global linked list */
    297 	    t->next = 0;
    298 	    err = pthread_setspecific(key, t);
    299 	    if (err) {
    300 		free(t);
    301 		return err;
    302 	    }
    303 	}
    304     } else {
    305 	t = GET_NO_PTHREAD_TSD();
    306     }
    307 
    308 #endif
    309 
    310     t->values[keynum] = value;
    311     return 0;
    312 }
    313 
    314 int k5_key_register (k5_key_t keynum, void (*destructor)(void *))
    315 {
    316     int err;
    317 
    318     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
    319     if (err)
    320 	return err;
    321 
    322     assert(keynum >= 0 && keynum < K5_KEY_MAX);
    323 
    324 #ifndef ENABLE_THREADS
    325 
    326     assert(destructors_set[keynum] == 0);
    327     destructors[keynum] = destructor;
    328     destructors_set[keynum] = 1;
    329     err = 0;
    330 
    331 #elif defined(_WIN32)
    332 
    333     /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
    334     EnterCriticalSection(&key_lock);
    335     assert(destructors_set[keynum] == 0);
    336     destructors_set[keynum] = 1;
    337     destructors[keynum] = destructor;
    338     LeaveCriticalSection(&key_lock);
    339     err = 0;
    340 
    341 #else /* POSIX */
    342 
    343     err = k5_mutex_lock(&key_lock);
    344     if (err == 0) {
    345 	assert(destructors_set[keynum] == 0);
    346 	destructors_set[keynum] = 1;
    347 	destructors[keynum] = destructor;
    348 	err = k5_mutex_unlock(&key_lock);
    349     }
    350 
    351 #endif
    352     return 0;
    353 }
    354 
    355 int k5_key_delete (k5_key_t keynum)
    356 {
    357     assert(keynum >= 0 && keynum < K5_KEY_MAX);
    358 
    359 #ifndef ENABLE_THREADS
    360 
    361     assert(destructors_set[keynum] == 1);
    362     if (destructors[keynum] && tsd_no_threads.values[keynum])
    363 	(*destructors[keynum])(tsd_no_threads.values[keynum]);
    364     destructors[keynum] = 0;
    365     tsd_no_threads.values[keynum] = 0;
    366     destructors_set[keynum] = 0;
    367 
    368 #elif defined(_WIN32)
    369 
    370     /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
    371     EnterCriticalSection(&key_lock);
    372     /* XXX Memory leak here!
    373        Need to destroy the associated data for all threads.
    374        But watch for race conditions in case threads are going away too.  */
    375     assert(destructors_set[keynum] == 1);
    376     destructors_set[keynum] = 0;
    377     destructors[keynum] = 0;
    378     LeaveCriticalSection(&key_lock);
    379 
    380 #else /* POSIX */
    381 
    382     {
    383 	int err;
    384 
    385 	/* XXX RESOURCE LEAK:
    386 
    387 	   Need to destroy the allocated objects first!  */
    388 
    389 	err = k5_mutex_lock(&key_lock);
    390 	if (err == 0) {
    391 	    assert(destructors_set[keynum] == 1);
    392 	    destructors_set[keynum] = 0;
    393 	    destructors[keynum] = NULL;
    394 	    k5_mutex_unlock(&key_lock);
    395 	}
    396     }
    397 
    398 #endif
    399 
    400     return 0;
    401 }
    402 
    403 int krb5int_call_thread_support_init (void)
    404 {
    405     return CALL_INIT_FUNCTION(krb5int_thread_support_init);
    406 }
    407 
    408 #include "cache-addrinfo.h"
    409 
    410 #ifdef DEBUG_THREADS_STATS
    411 #include <stdio.h>
    412 static FILE *stats_logfile;
    413 #endif
    414 
    415 int krb5int_thread_support_init (void)
    416 {
    417     int err;
    418 
    419 #ifdef SHOW_INITFINI_FUNCS
    420     printf("krb5int_thread_support_init\n");
    421 #endif
    422 
    423 #ifdef DEBUG_THREADS_STATS
    424     /*    stats_logfile = stderr; */
    425     stats_logfile = fopen("/dev/tty", "w+");
    426     if (stats_logfile == NULL)
    427       stats_logfile = stderr;
    428 #endif
    429 
    430 #ifndef ENABLE_THREADS
    431 
    432     /* Nothing to do for TLS initialization.  */
    433 
    434 #elif defined(_WIN32)
    435 
    436     tls_idx = TlsAlloc();
    437     /* XXX This can raise an exception if memory is low!  */
    438     InitializeCriticalSection(&key_lock);
    439 
    440 #else /* POSIX */
    441 
    442     err = k5_mutex_finish_init(&key_lock);
    443     if (err)
    444 	return err;
    445     if (K5_PTHREADS_LOADED) {
    446 	err = pthread_key_create(&key, thread_termination);
    447 	if (err)
    448 	    return err;
    449     }
    450 
    451 #endif
    452 
    453     err = krb5int_init_fac();
    454     if (err)
    455 	return err;
    456 
    457     err = krb5int_err_init();
    458     if (err)
    459 	return err;
    460 
    461     return 0;
    462 }
    463 
    464 void krb5int_thread_support_fini (void)
    465 {
    466     if (! INITIALIZER_RAN (krb5int_thread_support_init))
    467 	return;
    468 
    469 #ifdef SHOW_INITFINI_FUNCS
    470     printf("krb5int_thread_support_fini\n");
    471 #endif
    472 
    473 #ifndef ENABLE_THREADS
    474 
    475     /* Do nothing.  */
    476 
    477 #elif defined(_WIN32)
    478 
    479     /* ... free stuff ... */
    480     TlsFree(tls_idx);
    481     DeleteCriticalSection(&key_lock);
    482 
    483 #else /* POSIX */
    484 
    485     if (! INITIALIZER_RAN(krb5int_thread_support_init))
    486 	return;
    487     if (K5_PTHREADS_LOADED)
    488 	pthread_key_delete(key);
    489     /* ... delete stuff ... */
    490     k5_mutex_destroy(&key_lock);
    491 
    492 #endif
    493 
    494 #ifdef DEBUG_THREADS_STATS
    495     fflush(stats_logfile);
    496     /* XXX Should close if not stderr, in case unloading library but
    497        not exiting.  */
    498 #endif
    499 
    500     krb5int_fini_fac();
    501 }
    502 
    503 #ifdef DEBUG_THREADS_STATS
    504 void KRB5_CALLCONV
    505 k5_mutex_lock_update_stats(k5_debug_mutex_stats *m,
    506 			   k5_mutex_stats_tmp startwait)
    507 {
    508   k5_debug_time_t now;
    509   k5_debug_timediff_t tdiff, tdiff2;
    510 
    511   now = get_current_time();
    512   (void) krb5int_call_thread_support_init();
    513   m->count++;
    514   m->time_acquired = now;
    515   tdiff = timediff(now, startwait);
    516   tdiff2 = tdiff * tdiff;
    517   if (m->count == 1 || m->lockwait.valmin > tdiff)
    518     m->lockwait.valmin = tdiff;
    519   if (m->count == 1 || m->lockwait.valmax < tdiff)
    520     m->lockwait.valmax = tdiff;
    521   m->lockwait.valsum += tdiff;
    522   m->lockwait.valsqsum += tdiff2;
    523 }
    524 
    525 void KRB5_CALLCONV
    526 krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
    527 {
    528   k5_debug_time_t now = get_current_time();
    529   k5_debug_timediff_t tdiff, tdiff2;
    530   tdiff = timediff(now, m->time_acquired);
    531   tdiff2 = tdiff * tdiff;
    532   if (m->count == 1 || m->lockheld.valmin > tdiff)
    533     m->lockheld.valmin = tdiff;
    534   if (m->count == 1 || m->lockheld.valmax < tdiff)
    535     m->lockheld.valmax = tdiff;
    536   m->lockheld.valsum += tdiff;
    537   m->lockheld.valsqsum += tdiff2;
    538 }
    539 
    540 #include <math.h>
    541 static double
    542 get_stddev(struct k5_timediff_stats sp, int count)
    543 {
    544   long double mu, mu_squared, rho_squared;
    545   mu = (long double) sp.valsum / count;
    546   mu_squared = mu * mu;
    547   /* SUM((x_i - mu)^2)
    548      = SUM(x_i^2 - 2*mu*x_i + mu^2)
    549      = SUM(x_i^2) - 2*mu*SUM(x_i) + N*mu^2
    550 
    551      Standard deviation rho^2 = SUM(...) / N.  */
    552   rho_squared = (sp.valsqsum - 2 * mu * sp.valsum + count * mu_squared) / count;
    553   return sqrt(rho_squared);
    554 }
    555 
    556 void KRB5_CALLCONV
    557 krb5int_mutex_report_stats(k5_mutex_t *m)
    558 {
    559   char *p;
    560 
    561   /* Tweak this to only record data on "interesting" locks.  */
    562   if (m->stats.count < 10)
    563     return;
    564   if (m->stats.lockwait.valsum < 10 * m->stats.count)
    565     return;
    566 
    567   p = strrchr(m->loc_created.filename, '/');
    568   if (p == NULL)
    569     p = m->loc_created.filename;
    570   else
    571     p++;
    572   fprintf(stats_logfile, "mutex @%p: created at line %d of %s\n",
    573 	  (void *) m, m->loc_created.lineno, p);
    574   if (m->stats.count == 0)
    575     fprintf(stats_logfile, "\tnever locked\n");
    576   else {
    577     double sd_wait, sd_hold;
    578     sd_wait = get_stddev(m->stats.lockwait, m->stats.count);
    579     sd_hold = get_stddev(m->stats.lockheld, m->stats.count);
    580     fprintf(stats_logfile,
    581 	    "\tlocked %d time%s; wait %lu/%f/%lu/%fus, hold %lu/%f/%lu/%fus\n",
    582 	    m->stats.count, m->stats.count == 1 ? "" : "s",
    583 	    (unsigned long) m->stats.lockwait.valmin,
    584 	    (double) m->stats.lockwait.valsum / m->stats.count,
    585 	    (unsigned long) m->stats.lockwait.valmax,
    586 	    sd_wait,
    587 	    (unsigned long) m->stats.lockheld.valmin,
    588 	    (double) m->stats.lockheld.valsum / m->stats.count,
    589 	    (unsigned long) m->stats.lockheld.valmax,
    590 	    sd_hold);
    591   }
    592 }
    593 #else
    594 /* On Windows, everything defined in the export list must be defined.
    595    The UNIX systems where we're using the export list don't seem to
    596    care.  */
    597 #undef krb5int_mutex_lock_update_stats
    598 void KRB5_CALLCONV
    599 krb5int_mutex_lock_update_stats(k5_debug_mutex_stats *m,
    600 				k5_mutex_stats_tmp startwait)
    601 {
    602 }
    603 #undef krb5int_mutex_unlock_update_stats
    604 void KRB5_CALLCONV
    605 krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
    606 {
    607 }
    608 #undef krb5int_mutex_report_stats
    609 void KRB5_CALLCONV
    610 krb5int_mutex_report_stats(k5_mutex_t *m)
    611 {
    612 }
    613 #endif
    614 
    615 /* Mutex allocation functions, for use in plugins that may not know
    616    what options a given set of libraries was compiled with.  */
    617 int KRB5_CALLCONV
    618 krb5int_mutex_alloc (k5_mutex_t **m)
    619 {
    620     k5_mutex_t *ptr;
    621     int err;
    622 
    623     ptr = malloc (sizeof (k5_mutex_t));
    624     if (ptr == NULL)
    625 	return errno;
    626     err = k5_mutex_init (ptr);
    627     if (err) {
    628 	free (ptr);
    629 	return err;
    630     }
    631     *m = ptr;
    632     return 0;
    633 }
    634 
    635 void KRB5_CALLCONV
    636 krb5int_mutex_free (k5_mutex_t *m)
    637 {
    638     (void) k5_mutex_destroy (m);
    639     free (m);
    640 }
    641 
    642 /* Callable versions of the various macros.  */
    643 int KRB5_CALLCONV
    644 krb5int_mutex_lock (k5_mutex_t *m)
    645 {
    646     return k5_mutex_lock (m);
    647 }
    648 int KRB5_CALLCONV
    649 krb5int_mutex_unlock (k5_mutex_t *m)
    650 {
    651     return k5_mutex_unlock (m);
    652 }
    653