From: george anzinger <george@mvista.com>

The attached patch should fix the system hang referenced below (works 
on my system) and also the deliver to a specific thread issue.  I 
don't have a test for this last issue so feed back is desired.  Once 
you pass it I will send it in.

Ulrich Drepper wrote:
> 
>   http://people.redhat.com/drepper/t-overr.bz2
> 
> is a statically linked binary.  You'll see that the program hangs when
> it tries to disarm the timer.



 include/linux/sched.h |    1 
 kernel/posix-timers.c |   68 +++++++++++++++++++++++++++++++-------------------
 kernel/signal.c       |    2 -
 3 files changed, 45 insertions(+), 26 deletions(-)

diff -puN include/linux/sched.h~posix-timer-hang-fix include/linux/sched.h
--- 25/include/linux/sched.h~posix-timer-hang-fix	2003-04-02 23:46:41.000000000 -0800
+++ 25-akpm/include/linux/sched.h	2003-04-02 23:46:41.000000000 -0800
@@ -546,6 +546,7 @@ extern void block_all_signals(int (*noti
 extern void unblock_all_signals(void);
 extern void release_task(struct task_struct * p);
 extern int send_sig_info(int, struct siginfo *, struct task_struct *);
+extern int specific_send_sig_info(int, struct siginfo *, struct task_struct *);
 extern int force_sig_info(int, struct siginfo *, struct task_struct *);
 extern int __kill_pg_info(int sig, struct siginfo *info, pid_t pgrp);
 extern int kill_pg_info(int, struct siginfo *, pid_t);
diff -puN kernel/posix-timers.c~posix-timer-hang-fix kernel/posix-timers.c
--- 25/kernel/posix-timers.c~posix-timer-hang-fix	2003-04-02 23:46:41.000000000 -0800
+++ 25-akpm/kernel/posix-timers.c	2003-04-02 23:46:41.000000000 -0800
@@ -77,6 +77,20 @@ static spinlock_t idr_lock = SPIN_LOCK_U
 # define timer_active(tmr) BARFY	// error to use outside of SMP
 # define set_timer_inactive(tmr) do { } while (0)
 #endif
+
+/*
+ * For some reason mips/mips64 define the SIGEV constants plus 128.
+ * Here we define a mask to get rid of the common bits.	 The
+ * optimizer should make this costless to all but mips.
+ */
+#if (ARCH == mips) || (ARCH == mips64)
+#define MIPS_SIGEV ~(SIGEV_NONE & \
+		      SIGEV_SIGNAL & \
+		      SIGEV_THREAD &  \
+		      SIGEV_THREAD_ID)
+#else
+#define MIPS_SIGEV (int)-1
+#endif
 /*
  * The timer ID is turned into a timer address by idr_find().
  * Verifying a valid ID consists of:
@@ -225,10 +239,9 @@ static void schedule_next_timer(struct k
 	struct now_struct now;
 
 	/* Set up the timer for the next interval (if there is one) */
-	if (!timr->it_incr) {
-		set_timer_inactive(timr);
+	if (!timr->it_incr) 
 		return;
-	}
+
 	posix_get_now(&now);
 	do {
 		posix_bump_timer(timr);
@@ -258,7 +271,7 @@ void do_schedule_next_timer(struct sigin
 
 	timr = lock_timer(info->si_tid, &flags);
 
-	if (!timr || !timr->it_requeue_pending)
+	if (!timr || timr->it_requeue_pending != info->si_sys_private)
 		goto exit;
 
 	schedule_next_timer(timr);
@@ -276,7 +289,17 @@ exit:
  * indicating that the signal was either not queued or was queued
  * without an info block.  In this case, we will not get a call back to
  * do_schedule_next_timer() so we do it here.  This should be rare...
+
+ * An interesting problem can occure if, while a signal, and thus a call
+ * back is pending, the timer is rearmed, i.e. stopped and restarted.
+ * We then need to sort out the call back and do the right thing.  What
+ * we do is to put a counter in the info block and match it with the
+ * timers copy on the call back.  If they don't match, we just ignore
+ * the call back.  Note that we do allow the timer to be deleted while
+ * a signal is pending.  The standard says we can allow that signal to
+ * be delivered, and we do.
  */
+static int pendcount;
 
 static void timer_notify_task(struct k_itimer *timr)
 {
@@ -291,12 +314,20 @@ static void timer_notify_task(struct k_i
 	info.si_code = SI_TIMER;
 	info.si_tid = timr->it_id;
 	info.si_value = timr->it_sigev_value;
-	if (!timr->it_incr)
-		set_timer_inactive(timr);
-	else
-		timr->it_requeue_pending = info.si_sys_private = 1;
-
-	ret = send_sig_info(info.si_signo, &info, timr->it_process);
+	if( !++pendcount ) ++pendcount;
+	if (timr->it_incr){
+		/*
+		 * Don't allow a call back counter of zero...
+		 */
+		if( !++pendcount ) 
+			++pendcount;
+		timr->it_requeue_pending = info.si_sys_private = pendcount;
+	}
+	if( timr->it_sigev_notify & SIGEV_THREAD_ID & MIPS_SIGEV){
+		ret = specific_send_sig_info(info.si_signo, &info,
+					     timr->it_process);
+	}else
+		ret = send_sig_info(info.si_signo, &info, timr->it_process);
 	switch (ret) {
 
 	default:
@@ -332,23 +363,11 @@ static void posix_timer_fn(unsigned long
 	unsigned long flags;
 
 	spin_lock_irqsave(&timr->it_lock, flags);
+	set_timer_inactive(timr);
 	timer_notify_task(timr);
 	unlock_timer(timr, flags);
 }
 
-/*
- * For some reason mips/mips64 define the SIGEV constants plus 128.
- * Here we define a mask to get rid of the common bits.	 The
- * optimizer should make this costless to all but mips.
- */
-#if (ARCH == mips) || (ARCH == mips64)
-#define MIPS_SIGEV ~(SIGEV_NONE & \
-		      SIGEV_SIGNAL & \
-		      SIGEV_THREAD &  \
-		      SIGEV_THREAD_ID)
-#else
-#define MIPS_SIGEV (int)-1
-#endif
 
 static inline struct task_struct * good_sigevent(sigevent_t * event)
 {
@@ -847,8 +866,7 @@ static inline int do_timer_delete(struct
 {
 	timer->it_incr = 0;
 #ifdef CONFIG_SMP
-	if (timer_active(timer) &&
-	    !del_timer(&timer->it_timer) && !timer->it_requeue_pending)
+	if (timer_active(timer) && !del_timer(&timer->it_timer))
 		/*
 		 * It can only be active if on an other cpu.  Since
 		 * we have cleared the interval stuff above, it should
diff -puN kernel/signal.c~posix-timer-hang-fix kernel/signal.c
--- 25/kernel/signal.c~posix-timer-hang-fix	2003-04-02 23:46:41.000000000 -0800
+++ 25-akpm/kernel/signal.c	2003-04-02 23:46:41.000000000 -0800
@@ -749,7 +749,7 @@ out_set:
 	(((sig) < SIGRTMIN) && sigismember(&(sigptr)->signal, (sig)))
 
 
-static int
+int
 specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
 {
 	int ret = 0;

_