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