signal handler priority issue
David Xu
davidxu at freebsd.org
Fri Jun 11 06:00:44 GMT 2004
I think there is a race in GC_suspend_handler,
code between sem_post and sigsuspend has race, let me demostrate:
Master : pthread_kill(slave1, SIGUSR1);
Master : semwait
Slave1 : sempost
Master : semwait return
Master : pthread_kill(slave, SIGUSR2);
Master : pthread_kill(slave2, SIGUSR1);
...
Slave1 : scheduler switched to slave1, found there
is SIGUSR2 in pending set
Slave1 : invoke SIGUSR2 handler withinsa_handler
Slave1 : SIGUSR2 handler return
Slave1 : sigsuspend SIGUSR2
because SIGUSR2 was already handled, it hangs
in sigsuspend, and never return.
It seems the code has the race bug. I think you should
use sigaction and set additional mask for SIGUSR1.
code looks like this:
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGUSR2);
sa.sa_handler = GC_suspend_handler;
sigaction(SIGUSR1, &sa, NULL);
this code will block out SIGUSR2 in GC_suspend_handler,
and when you call sigsuspend for SIGUSR2, it should return
if there is SIGUSR2 pending or it SIGUSR2 comes later.
David Xu
Sean McNeil wrote:
> On Thu, 2004-06-10 at 22:04, David Xu wrote:
>
>>Can you provide some code to demostrate the problem ? I have interest.
>>
>>David Xu
>
>
> Yes, here is some code snippets from boehm-gc:
>
> master thread doing:
>
> int GC_suspend_all()
> {
> int n_live_threads = 0;
> int i;
> GC_thread p;
> int result;
> pthread_t my_thread = pthread_self();
>
> GC_stopping_thread = my_thread; /* debugging only. */
> GC_stopping_pid = getpid(); /* debugging only.
> */
> for (i = 0; i < THREAD_TABLE_SZ; i++) {
> for (p = GC_threads[i]; p != 0; p = p -> next) {
> if (p -> id != my_thread) {
> if (p -> flags & FINISHED) continue;
> if (p -> stop_info.last_stop_count == GC_stop_count)
> continue;
> if (p -> thread_blocked) /* Will wait */ continue;
> n_live_threads++;
> #if DEBUG_THREADS
> GC_printf1("Sending suspend signal to 0x%lx\n", p -> id);
> #endif
>
> result = pthread_kill(p -> id, SIG_SUSPEND);
> switch(result) {
> case ESRCH:
> /* Not really there anymore. Possible? */
> n_live_threads--;
> break;
> case 0:
> break;
> default:
> ABORT("pthread_kill failed");
> }
> }
> }
> }
> return n_live_threads;
> }
>
> and slave threads doing:
>
> void GC_suspend_handler(int sig)
> {
> int dummy;
> pthread_t my_thread = pthread_self();
> GC_thread me;
> sigset_t mask;
> # ifdef PARALLEL_MARK
> word my_mark_no = GC_mark_no;
> /* Marker can't proceed until we acknowledge. Thus this is
> */
> /* guaranteed to be the mark_no correspending to our
> */
> /* suspension, i.e. the marker can't have incremented it yet.
> */
> # endif
> word my_stop_count = GC_stop_count;
>
> if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
>
> #if DEBUG_THREADS
> GC_printf1("Suspending 0x%lx\n", my_thread);
> #endif
>
> me = GC_lookup_thread(my_thread);
> /* The lookup here is safe, since I'm doing this on behalf */
> /* of a thread which holds the allocation lock in order */
> /* to stop the world. Thus concurrent modification of the */
> /* data structure is impossible. */
> if (me -> stop_info.last_stop_count == my_stop_count) {
> /* Duplicate signal. OK if we are retrying. */
> if (!GC_retry_signals) {
> WARN("Duplicate suspend signal in thread %lx\n",
> pthread_self());
> }
> return;
> }
> # ifdef SPARC
> me -> stop_info.stack_ptr = (ptr_t)GC_save_regs_in_stack();
> # else
> me -> stop_info.stack_ptr = (ptr_t)(&dummy);
> # endif
> # ifdef IA64
> me -> backing_store_ptr = (ptr_t)GC_save_regs_in_stack();
> # endif
>
> /* Tell the thread that wants to stop the world that this */
> /* thread has been stopped. Note that sem_post() is */
> /* the only async-signal-safe primitive in LinuxThreads. */
> sem_post(&GC_suspend_ack_sem);
> me -> stop_info.last_stop_count = my_stop_count;
>
> #if DEBUG_THREADS
> GC_printf2("Waiting for restart #%d of 0x%lx\n", my_stop_count,
> my_thread);
> #endif
>
> /* Wait until that thread tells us to restart by sending */
> /* this thread a SIG_THR_RESTART signal. */
> /* SIG_THR_RESTART should be masked at this point. Thus there
> */
> /* is no race. */
> if (sigfillset(&mask) != 0) ABORT("sigfillset() failed");
> if (sigdelset(&mask, SIG_THR_RESTART) != 0) ABORT("sigdelset()
> failed");
> # ifdef NO_SIGNALS
> if (sigdelset(&mask, SIGINT) != 0) ABORT("sigdelset() failed");
> if (sigdelset(&mask, SIGQUIT) != 0) ABORT("sigdelset() failed");
> if (sigdelset(&mask, SIGTERM) != 0) ABORT("sigdelset() failed");
> if (sigdelset(&mask, SIGABRT) != 0) ABORT("sigdelset() failed");
> # endif
> do {
> me->stop_info.signal = 0;
> sigsuspend(&mask); /* Wait for signal */
> } while (me->stop_info.signal != SIG_THR_RESTART);
> /* If the RESTART signal gets lost, we can still lose. That should
> be */
> /* less likely than losing the SUSPEND signal, since we don't do
> much */
> /* between the sem_post and sigsuspend.
> */
> /* We'd need more handshaking to work around that, since we don't
> want */
> /* to accidentally leave a RESTART signal pending, thus causing us
> to */
> /* continue prematurely in a future round.
> */
>
> #if DEBUG_THREADS
> GC_printf1("Continuing 0x%lx\n", my_thread);
> #endif
> }
>
> and here is the output with debug messages:
>
> Stopping the world from 0x50d000
> Sending suspend signal to 0x9d1400
> Suspending 0x9d1400
> World stopped from 0x50d000
> Pushing stacks from thread 0x50d000
> Stack for thread 0x9d1400 = [7fffffeed95c,7fffffeee000)
> Stack for thread 0x50d000 = [7fffffffcf00,800000000000)
> World starting
> Sending restart signal to 0x9d1400
> World started
> Buildfile: build.xml
> Stopping the world from 0x50d000
> Sending suspend signal to 0x9d1400
> Waiting for restart #2 of 0x9d1400
>
> There are other things causing output, but you can see that the slave
> isn't getting to the sigsuspend before it is evoked again from the
> master thread.
>
>
>
More information about the freebsd-threads
mailing list