2 * util/support/threads.c
4 * Copyright 2004,2005,2006 by the Massachusetts Institute of Technology.
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.
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.
27 * Preliminary thread support.
33 #include "k5-thread.h"
34 #include "k5-platform.h"
37 MAKE_INIT_FUNCTION(krb5int_thread_support_init
);
38 MAKE_FINI_FUNCTION(krb5int_thread_support_fini
);
40 #ifndef ENABLE_THREADS /* no thread support */
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
];
47 int krb5int_pthread_loaded (void)
55 static CRITICAL_SECTION key_lock
;
57 void *values
[K5_KEY_MAX
];
59 static void (*destructors
[K5_KEY_MAX
])(void *);
60 static unsigned char destructors_set
[K5_KEY_MAX
];
62 void krb5int_thread_detach_hook (void)
64 /* XXX Memory leak here!
65 Need to destroy all TLS objects we know about for this thread. */
69 err
= CALL_INIT_FUNCTION(krb5int_thread_support_init
);
73 t
= TlsGetValue(tls_idx
);
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
];
85 /* Stub function not used on Windows. */
86 int krb5int_pthread_loaded (void)
90 #else /* POSIX threads */
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. */
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
];
100 /* This is not safe yet!
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
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. */
111 struct tsd_block
*next
;
112 void *values
[K5_KEY_MAX
];
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)
125 if (flag_pthread_loaded
== -1)
126 flag_pthread_loaded
= 1;
128 /* Could we have been called twice? */
129 flag_pthread_loaded
= 0;
131 static pthread_once_t loaded_test_once
= PTHREAD_ONCE_INIT
;
132 int krb5int_pthread_loaded (void)
134 int x
= flag_pthread_loaded
;
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
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;
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
;
170 static struct tsd_block tsd_if_single
;
171 # define GET_NO_PTHREAD_TSD() (&tsd_if_single)
173 # define GET_NO_PTHREAD_TSD() (abort(),(struct tsd_block *)0)
176 static pthread_key_t key
;
177 static void thread_termination(void *);
179 static void thread_termination (void *tptr
)
181 int err
= k5_mutex_lock(&key_lock
);
183 int i
, pass
, none_found
;
184 struct tsd_block
*t
= tptr
;
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.
191 Shouldn't actually happen, if we're careful, but check just in
196 while (pass
< 4 && !none_found
) {
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
];
202 (*destructors
[i
])(v
);
208 err
= k5_mutex_unlock(&key_lock
);
211 /* remove thread from global linked list */
214 #endif /* no threads vs Win32 vs POSIX */
216 void *k5_getspecific (k5_key_t keynum
)
221 err
= CALL_INIT_FUNCTION(krb5int_thread_support_init
);
225 assert(keynum
>= 0 && keynum
< K5_KEY_MAX
);
226 assert(destructors_set
[keynum
] == 1);
228 #ifndef ENABLE_THREADS
232 #elif defined(_WIN32)
234 t
= TlsGetValue(tls_idx
);
238 if (K5_PTHREADS_LOADED
)
239 t
= pthread_getspecific(key
);
241 t
= GET_NO_PTHREAD_TSD();
247 return t
->values
[keynum
];
250 int k5_setspecific (k5_key_t keynum
, void *value
)
255 err
= CALL_INIT_FUNCTION(krb5int_thread_support_init
);
259 assert(keynum
>= 0 && keynum
< K5_KEY_MAX
);
260 assert(destructors_set
[keynum
] == 1);
262 #ifndef ENABLE_THREADS
266 #elif defined(_WIN32)
268 t
= TlsGetValue(tls_idx
);
271 t
= malloc(sizeof(*t
));
274 for (i
= 0; i
< K5_KEY_MAX
; i
++)
276 /* add to global linked list */
278 err
= TlsSetValue(tls_idx
, t
);
281 return GetLastError();
287 if (K5_PTHREADS_LOADED
) {
288 t
= pthread_getspecific(key
);
291 t
= malloc(sizeof(*t
));
294 for (i
= 0; i
< K5_KEY_MAX
; i
++)
296 /* add to global linked list */
298 err
= pthread_setspecific(key
, t
);
305 t
= GET_NO_PTHREAD_TSD();
310 t
->values
[keynum
] = value
;
314 int k5_key_register (k5_key_t keynum
, void (*destructor
)(void *))
318 err
= CALL_INIT_FUNCTION(krb5int_thread_support_init
);
322 assert(keynum
>= 0 && keynum
< K5_KEY_MAX
);
324 #ifndef ENABLE_THREADS
326 assert(destructors_set
[keynum
] == 0);
327 destructors
[keynum
] = destructor
;
328 destructors_set
[keynum
] = 1;
331 #elif defined(_WIN32)
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
);
343 err
= k5_mutex_lock(&key_lock
);
345 assert(destructors_set
[keynum
] == 0);
346 destructors_set
[keynum
] = 1;
347 destructors
[keynum
] = destructor
;
348 err
= k5_mutex_unlock(&key_lock
);
355 int k5_key_delete (k5_key_t keynum
)
357 assert(keynum
>= 0 && keynum
< K5_KEY_MAX
);
359 #ifndef ENABLE_THREADS
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;
368 #elif defined(_WIN32)
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
);
385 /* XXX RESOURCE LEAK:
387 Need to destroy the allocated objects first! */
389 err
= k5_mutex_lock(&key_lock
);
391 assert(destructors_set
[keynum
] == 1);
392 destructors_set
[keynum
] = 0;
393 destructors
[keynum
] = NULL
;
394 k5_mutex_unlock(&key_lock
);
403 int krb5int_call_thread_support_init (void)
405 return CALL_INIT_FUNCTION(krb5int_thread_support_init
);
408 #include "cache-addrinfo.h"
410 #ifdef DEBUG_THREADS_STATS
412 static FILE *stats_logfile
;
415 int krb5int_thread_support_init (void)
419 #ifdef SHOW_INITFINI_FUNCS
420 printf("krb5int_thread_support_init\n");
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
;
430 #ifndef ENABLE_THREADS
432 /* Nothing to do for TLS initialization. */
434 #elif defined(_WIN32)
436 tls_idx
= TlsAlloc();
437 /* XXX This can raise an exception if memory is low! */
438 InitializeCriticalSection(&key_lock
);
442 err
= k5_mutex_finish_init(&key_lock
);
445 if (K5_PTHREADS_LOADED
) {
446 err
= pthread_key_create(&key
, thread_termination
);
453 err
= krb5int_init_fac();
457 err
= krb5int_err_init();
464 void krb5int_thread_support_fini (void)
466 if (! INITIALIZER_RAN (krb5int_thread_support_init
))
469 #ifdef SHOW_INITFINI_FUNCS
470 printf("krb5int_thread_support_fini\n");
473 #ifndef ENABLE_THREADS
477 #elif defined(_WIN32)
479 /* ... free stuff ... */
481 DeleteCriticalSection(&key_lock
);
485 if (! INITIALIZER_RAN(krb5int_thread_support_init
))
487 if (K5_PTHREADS_LOADED
)
488 pthread_key_delete(key
);
489 /* ... delete stuff ... */
490 k5_mutex_destroy(&key_lock
);
494 #ifdef DEBUG_THREADS_STATS
495 fflush(stats_logfile
);
496 /* XXX Should close if not stderr, in case unloading library but
503 #ifdef DEBUG_THREADS_STATS
505 k5_mutex_lock_update_stats(k5_debug_mutex_stats
*m
,
506 k5_mutex_stats_tmp startwait
)
509 k5_debug_timediff_t tdiff
, tdiff2
;
511 now
= get_current_time();
512 (void) krb5int_call_thread_support_init();
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
;
526 krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats
*m
)
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
;
542 get_stddev(struct k5_timediff_stats sp
, int count
)
544 long double mu
, mu_squared
, rho_squared
;
545 mu
= (long double) sp
.valsum
/ count
;
546 mu_squared
= mu
* mu
;
548 = SUM(x_i^2 - 2*mu*x_i + mu^2)
549 = SUM(x_i^2) - 2*mu*SUM(x_i) + N*mu^2
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
);
557 krb5int_mutex_report_stats(k5_mutex_t
*m
)
561 /* Tweak this to only record data on "interesting" locks. */
562 if (m
->stats
.count
< 10)
564 if (m
->stats
.lockwait
.valsum
< 10 * m
->stats
.count
)
567 p
= strrchr(m
->loc_created
.filename
, '/');
569 p
= m
->loc_created
.filename
;
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");
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
,
587 (unsigned long) m
->stats
.lockheld
.valmin
,
588 (double) m
->stats
.lockheld
.valsum
/ m
->stats
.count
,
589 (unsigned long) m
->stats
.lockheld
.valmax
,
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
597 #undef krb5int_mutex_lock_update_stats
599 krb5int_mutex_lock_update_stats(k5_debug_mutex_stats
*m
,
600 k5_mutex_stats_tmp startwait
)
603 #undef krb5int_mutex_unlock_update_stats
605 krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats
*m
)
608 #undef krb5int_mutex_report_stats
610 krb5int_mutex_report_stats(k5_mutex_t
*m
)
615 /* Mutex allocation functions, for use in plugins that may not know
616 what options a given set of libraries was compiled with. */
618 krb5int_mutex_alloc (k5_mutex_t
**m
)
623 ptr
= malloc (sizeof (k5_mutex_t
));
626 err
= k5_mutex_init (ptr
);
636 krb5int_mutex_free (k5_mutex_t
*m
)
638 (void) k5_mutex_destroy (m
);
642 /* Callable versions of the various macros. */
644 krb5int_mutex_lock (k5_mutex_t
*m
)
646 return k5_mutex_lock (m
);
649 krb5int_mutex_unlock (k5_mutex_t
*m
)
651 return k5_mutex_unlock (m
);