From 8dabbc6240ca32b8ab5de3c1b8da373b799319cc Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 8 Jul 2004 22:39:33 +0000 Subject: [PATCH] *** empty log message *** svn path=/trunk/boinc/; revision=3817 --- lib/susp.C | 373 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 lib/susp.C diff --git a/lib/susp.C b/lib/susp.C new file mode 100644 index 0000000000..b16b0da405 --- /dev/null +++ b/lib/susp.C @@ -0,0 +1,373 @@ +/* + * susp.c + * + * Demonstrate an implementation of thread suspend and resume (similar + * to the Solaris thr_suspend/thr_continue functions) using portable + * POSIX functions. + * + * Note 1: Use of suspend and resume requires extreme care. You can + * easily deadlock your application by suspending a thread that holds + * some resource -- for example, a thread calling printf to print a + * message may have libc mutexes locked, and no other thread will be + * able to return from printf until the suspended thread is resumed. + * + * Note 2: This program is called "susp" rather than "suspend" to + * avoid confusion (by your shell) with the suspend command. + * + * Note 3: This simple program will fail if any thread terminates + * during the test. The value of ITERATIONS must be adjusted to a + * value sufficiently large that the main thread can complete its two + * suspend/continue loops before any thread_routine threads terminate. + */ +#include +#include +#include +#include +#include "errors.h" + +#define THREAD_COUNT 20 +#define ITERATIONS 80000 + +typedef struct { + int inuse; /* 1 if in use, else 0 */ + pthread_t id; /* Thread ID */ + } Victim_t; + +sem_t sem; +int thread_count = THREAD_COUNT; +int iterations = ITERATIONS; +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; +pthread_once_t once = PTHREAD_ONCE_INIT; +Victim_t *array = NULL; +int bottom = 0; + +/* + * Handle SIGUSR1 in the target thread, to suspend it until receiving + * SIGUSR2 (resume). Note that this is run with both SIGUSR1 and + * SIGUSR2 blocked. Having SIGUSR2 blocked prevents a resume before we + * can finish the suspend protocol. + */ +void +suspend_signal_handler (int sig) +{ + sigset_t signal_set; + + /* + * Block all signals except SIGUSR2 while suspended. + */ + sigfillset (&signal_set); + sigdelset (&signal_set, SIGUSR2); + sem_post (&sem); + sigsuspend (&signal_set); + + /* + * Once here, the resume signal handler has run to completion. + */ + return; +} + +/* + * Handle SIGUSR2 in the target thread, to resume it. Note that + * the signal handler does nothing. It exists only because we need + * to cause sigsuspend() to return. + */ +void +resume_signal_handler (int sig) +{ + return; +} + +/* + * Dynamically initialize the "suspend package" when first used + * (called by pthread_once). + */ +void +suspend_init_routine (void) +{ + int status; + struct sigaction sigusr1, sigusr2; + + /* + * Initialize a semaphore, to be used by the signal handler to + * confirm suspension. We only need one, because suspend & resume + * are fully serialized by a mutex. + */ + status = sem_init (&sem, 0, 1); + if (status == -1) + errno_abort ("Initializing semaphore"); + + /* + * Allocate the suspended threads array. This array is used to guarantee + * idempotency. + */ + bottom = 10; + array = (Victim_t*) calloc (bottom, sizeof (Victim_t)); + + /* + * Install the signal handlers for suspend/resume. + * + * We add SIGUSR2 to the sa_mask field for the SIGUSR1 handler. That + * avoids a race if one thread suspends the target while another resumes + * that same target. (The SIGUSR2 signal cannot be delivered before the + * target thread calls sigsuspend.) + */ + sigusr1.sa_flags = 0; + sigusr1.sa_handler = suspend_signal_handler; + sigemptyset (&sigusr1.sa_mask); + sigaddset (&sigusr1.sa_mask, SIGUSR2); + + sigusr2.sa_flags = 0; + sigusr2.sa_handler = resume_signal_handler; + sigemptyset (&sigusr2.sa_mask); + + status = sigaction (SIGUSR1, &sigusr1, NULL); + if (status == -1) + errno_abort ("Installing suspend handler"); + + status = sigaction (SIGUSR2, &sigusr2, NULL); + if (status == -1) + errno_abort ("Installing resume handler"); + + return; +} + +/* + * Suspend a thread by sending it a signal (SIGUSR1), which will + * block the thread until another signal (SIGUSR2) arrives. + * + * Multiple calls to thd_suspend for a single thread have no + * additional effect on the thread -- a single thd_continue + * call will cause it to resume execution. + */ +int +thd_suspend (pthread_t target_thread) +{ + int status; + int i; + + /* + * The first call to thd_suspend will initialize the + * package. + */ + status = pthread_once (&once, suspend_init_routine); + if (status != 0) + return status; + + /* + * Serialize access to suspend, makes life easier + */ + status = pthread_mutex_lock (&mut); + if (status != 0) + return status; + + /* + * Threads that are suspended are added to the target_array; a request to + * suspend a thread already listed in the array is ignored. + */ + for (i = 0; i < bottom; i++) { + if (array[i].inuse + && pthread_equal (array[i].id, target_thread)) { + status = pthread_mutex_unlock (&mut); + return status; + } + } + + /* + * Ok, we really need to suspend this thread. So, lets find the location + * in the array that we'll use. If we run off the end, realloc the array + * for more space. We allocate 2 new array slots; we'll use one, and leave + * the other for the next time. + */ + i = 0; + while (i < bottom && array[i].inuse) + i++; + + if (i >= bottom) { + bottom += 2; + array = (Victim_t*)realloc ( + array, (bottom * sizeof (Victim_t))); + if (array == NULL) { + pthread_mutex_unlock (&mut); + return errno; + } + array[bottom-1].inuse = 0; /* Clear new last entry */ + } + + /* + * Initialize the target's data. We initialize a semaphore to synchronize + * with the signal handler. After sending the signal, we wait until the + * signal handler posts the semaphore. Then we can destroy the semaphore, + * because we won't need it again. + */ + array[i].id = target_thread; + array[i].inuse = 1; + + status = pthread_kill (target_thread, SIGUSR1); + if (status != 0) { + pthread_mutex_unlock (&mut); + return status; + } + + /* + * Wait for the victim to acknowledge suspension. + */ + while ((status = sem_wait (&sem)) != 0) { + if (errno != EINTR) { + pthread_mutex_unlock (&mut); + return errno; + } + } + + status = pthread_mutex_unlock (&mut); + return status; +} + +/* + * Resume a suspended thread by sending it SIGUSR2 to break + * it out of the sigsuspend() in which it's waiting. If the + * target thread isn't suspended, return with success. + */ +int +thd_continue (pthread_t target_thread) +{ + int status; + int i = 0; + + /* + * The first call to thd_suspend will initialize the package. + */ + status = pthread_once (&once, suspend_init_routine); + if (status != 0) + return status; + + /* + * Serialize access to suspend, makes life easier. + */ + status = pthread_mutex_lock (&mut); + if (status != 0) + return status; + + /* + * Make sure the thread is in the suspend array. If not, it hasn't been + * suspended (or it has already been resumed) and we can just carry on. + */ + while (i < bottom && array[i].inuse + && pthread_equal (array[i].id, target_thread)) + i++; + + if (i >= bottom) { + pthread_mutex_unlock (&mut); + return 0; + } + + /* + * Signal the thread to continue, and remove the thread from + * the suspended array. + */ + status = pthread_kill (target_thread, SIGUSR2); + if (status != 0) { + pthread_mutex_unlock (&mut); + return status; + } + + array[i].inuse = 0; /* Clear array element */ + status = pthread_mutex_unlock (&mut); + return status; +} + +static void * +thread_routine (void *arg) +{ + int number = (int)arg; + int status; + int i; + char buffer[128]; + + for (i = 1; i <= iterations; i++) { + /* + * Every time each thread does 2000 interations, print a progress + * report. + */ + if (i % 2000 == 0) { + sprintf ( + buffer, "Thread %02d: %d\n", + number, i); + write (1, buffer, strlen (buffer)); + } + + sched_yield (); + } + + return (void *)0; +} + +int +main (int argc, char *argv[]) +{ + pthread_t threads[THREAD_COUNT]; + pthread_attr_t detach; + int status; + void *result; + int i; + + status = pthread_attr_init (&detach); + if (status != 0) + err_abort (status, "Init attributes object"); + status = pthread_attr_setdetachstate ( + &detach, PTHREAD_CREATE_DETACHED); + if (status != 0) + err_abort (status, "Set create-detached"); + + for (i = 0; i< THREAD_COUNT; i++) { + status = pthread_create ( + &threads[i], &detach, thread_routine, (void *)i); + if (status != 0) + err_abort (status, "Create thread"); + } + + sleep (1); + + for (i = 0; i < THREAD_COUNT/2; i++) { + printf ("Suspending thread %d.\n", i); + status = thd_suspend (threads[i]); + if (status != 0) + err_abort (status, "Suspend thread"); + } + + printf ("Sleeping ...\n"); + sleep (1); + + for (i = 0; i < THREAD_COUNT/2; i++) { + printf ("Continuing thread %d.\n", i); + status = thd_continue (threads[i]); + if (status != 0) + err_abort (status, "Suspend thread"); + } + + for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) { + printf ("Suspending thread %d.\n", i); + status = thd_suspend (threads[i]); + if (status != 0) + err_abort (status, "Suspend thread"); + } + + printf ("Sleeping ...\n"); + sleep (1); + + for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) { + printf ("Continuing thread %d.\n", i); + status = thd_continue (threads[i]); + if (status != 0) + err_abort (status, "Continue thread"); + } + + /* + * Request that each thread terminate. We don't bother waiting for them; + * just trust that they will terminate in "reasonable time". When the last + * thread exits, the process will exit. + */ + for (i = 0; i < THREAD_COUNT; i++) + pthread_cancel (threads[i]); + + pthread_exit (NULL); /* Let threads finish */ +}