[net.sources] When does

radford@calgary.UUCP (Radford Neal) (01/01/70)

> >As I read the manual (I didn't read the code) pause will only return when a
> >signal is "caught".  If the semantics were that it would also return
> >(immediately) if there were no signal handlers active (since they revert
> >when caught), the race condition would also disappear.  
> 
> This sounds nice at first, but gives rise to some even weirder
> situations... [e.g. multiple interrupt handlers]

I think the thing to do if you want to go this route is to have a 
flag in the process table entry which is set to one whenever a signal routine
is activated and cleared to zero by pause(). If pause is called when
it is one, it returns immediately (after clearing the flag).

This works as long as people have not been doing things like:

     alarm(1);
     pause();

but have instead been doing:

     alarm_caught = 0;
     alarm(1);
     while (!alarm_caught) pause(); /* alarm_caught set in signal routine */

They should have been doing this anyway, or their code will break if
there are other signal handlers active.

BTW, the routine I posted for avoiding the race (on a VAX) has a slight bug. 
Here it is again, with bug noted:

----------------------------------------------------------------------

/* PAUSE_FUDGE - Fudge routines to fix up pause race problem */

/* This module allows someone to wait until some condition has been
   made true by an interrupt routine without wasting cp time in a 
   polling loop. 

   The Unix 'pause' system call will suspend a process until an
   interrupt (i.e. signal) is received. This is not directly 
   usable in a wait loop however, since between a check for a 
   condition and a call of pause an interrupt may occur which
   would have made the condition true. So the following wait
   loop may hang up:

           while (!condition) pause();

   The following wait loop is to be used instead:

           for (;;)
           { jk_set_up_pause();
             if (condition) break;
             jk_maybe_do_pause();
           }

   The way this works is that jk_set_up_pause creates a routine
   which will perform a pause system call. jk_maybe_do_pause will
   execute this routine. The interrupt routine should be written
   to call the routine jk_disable_pause, which changes the routine
   created by jk_set_up_pause to do nothing instead of a pause.
   This is done by a change of a single Vax machine instruction to
   nop's. 

*/

static char pause_routine[5];		/* Pause system call or nop's */

/* Set up a routine to do a "pause" system call. */

jk_set_up_pause()
{ register char *p;
  p = &pause_routine[2];
  *p++ = 0274; *p++ = 035;	/* chmk $pause */   /* BUG BUG BUG BUG BUG */
  *p++ = 04;			/* ret */
}

/* Execute routine to do pause system call, unless it has been nop'ed out. */

jk_maybe_do_pause()
{ (*(void (*)())pause_routine)();
}

/* Disable pause call by replacing system call with nop's */

jk_disable_pause()
{ register char *p;
  p = &pause_routine[2];
  *p++ = 01; *p++ = 01;		/* nop; nop */
}

----------------------------------------------------------------------

The problem is that the signal may arrive between the *p++ = 0274; and the
*p++ = 035; which would lead to the final routine called by maybe_do_pause
having *half* a chmk instruction (anything might happen then, I haven't
looked to see what instruction has op-code 035). This can be fixed by
storing BOTH bytes of the chmk $pause instruction with a single assignment,
though that depends on the compiler really generating a 16-bit move rather
than two 8-bit moves (it almost certainly will).

I think the moral of all this is:

     - Don't use signal handlers if you can avoid it, especially for anything
       other than printing an error message and exiting.

     - If you have to use signal handlers, you better think REALLY carefully
       about what could happen, or you may write a program which fails 
       inexplicably once every few weeks.


     Radford Neal
     The University of Calgary

jeff@heurikon.UUCP (Jeffrey Mattox) (03/06/85)

Answer:  When you least expect it and, thanks to Murphy, when
you least want it.  In fact, there is a small, but non-zero,
probability that:  alarm(ANYTHING) == alarm(INFINITY).

We came across this little kernel bug the other day when trying
to figure out why certain programs were hanging on a simple
"sleep(1);" statement.  Although this happens on System V, I've
been told this problem is common to most non-BSD UNIXes.

   sleep(arg)	/* A simplified view of the sleep subroutine */
   {
	...
	alarm(arg);	/* sets  p_clktim=arg  in proc table */
	...		/* a critical time */
	pause();	/* waits for SIGALRM: --pp->p_clktim == 0 */
   }

   If an alarm(1) is executed *just* prior to a one second time
   tick, and if the time tick occurs before the pause(), then the
   pp->p_clktim value hits zero in clock() before the pause() is
   done, and the alarm signal will be missed by the pause.  This
   results in an INFINITE sleep.  If the process is suspended for
   more than one second prior to the pause, then alarms longer
   than one second could hang, too.

We'd welcome suggestions on how to fix this problem; the simpler,
the better (although the simpler, the-less-likely-it-will-fix-it).

See for yourself.  Here's an adaptive program which encourages a
sleep(1) hang.  You may need to manually adjust the initial loop
count estimation algorithm, or enter an initial value as an argument.
The ease with which this hangs is scary.
------------------------------cut here-------------------------------
						/* slphang.c */
#include <signal.h>
#include <stdio.h>

/* This program is an adaptive sleep(1) tester.  When called
 * without an argument, the logic first runs one loop over a one
 * second period to estimate the initial loop count value.  If there
 * is an argument, it is taken to be a decimal value which is used
 * as the initial delay loop counter.  On our system, a value of
 * 55000 is a good starting point.
 *
 * Sleep(1)'s are done and the loop count is increased or decreased
 * depending on whether or not the delay loop took more or less than
 * one full second to execute.  The target is to loop until *just*
 * prior to a one second clock tick, hoping to set the alarm value
 * and have the clock tick occur before the sleep() does a pause().
 *
 * While running, a SIGINT will print out the current loop delay value.
 * A "+" will print if the delay loop is being increased; a "-" if the
 * delay is being decreased.  The program can be run in the background.
 * Ideally, the system should be "quiet" when executing this so the
 * adaption will work.  Restart the program if it doesn't adapt (hang)
 * within one minute.
 *
 * Expect a bunch of "+" marks to start; if it starts with "-", it may
 * not converge.  If you get alternating "+" and "-" you're close, or
 * your system is too busy for this to work.  When you don't get
 * either (+ or -) for more than two seconds, it's hung.
 *
 * The ideal output should look something like this:
 * "++++++---+--+-+--++-+-"
 *
 * A SIGINT will cause the delay value to be printed and also will
 * "unhang" the sleep, so the program will continue printing "+"
 * and "-" until another hang.
 *
 * Use SIGQUIT to exit.
 */

int	delay;
short	alrmflag,dummy;

main(argc,argv)
int argc;
char *argv[];
{
	int	onintr();
	register int i;
	int	delta;
	long	time1,time2;
	short	lastflag = 0;

	if ( argc > 1 )
		delay = atoi(argv[1]);

	signal(SIGINT,onintr);
	signal(SIGALRM,onintr);
	sleep(1);	/* synchronize to the clock, hope we don't hang here! */
	if ( delay == 0 ) {	/* argument? */
		alarm(1);	/* set alrmflag in one second */
		for (i=0; i<999999 && !alrmflag ; i++);
		delay = i - i/3 - i/7;	/* make a guess */
	}
	delta = delay/12;		/* initial adaptive +- increment */
	time(&time2);			/* init time2 */
	while (1) {
		time1 = time2;		/* last loop time */
		for (i=0; i<delay && !dummy ; i++);/* delay almost one sec */
		sleep(1);		/* sleep and hope for tick real soon */
		time(&time2);		/* time after sleep */
		if ( time2-time1 > 1 ) {	/* using too big a delay */
			if ( lastflag == 1 ) {	/* want two in a row */
				delta = (delta/2) | 1;	/* decrease delta */
				delay -= delta;	/* adapt */
				lastflag = 0;
			} else {
				lastflag = 1;
			}
			putc('-',stderr);
		} else {			/* using too small a delay */
			delay += delta;		/* adapt */
			putc('+',stderr);
			lastflag = 0;
		}
	}
}
onintr(sig)
{
	if ( sig == SIGALRM ) {
		alrmflag = 1;
		return;
	}
	signal(sig,onintr);
	printf("\ndly=%d\n",delay);
}
----
/"""\	Jeffrey Mattox, Heurikon Corp, Madison, WI
|O.O|	{harpo, hao, philabs}!seismo!uwvax!heurikon!jeff  (news & mail)
\_=_/				     ihnp4!heurikon!jeff  (mail - best)

radford@calgary.UUCP (Radford Neal) (03/07/85)

> We came across this little kernel bug the other day when trying
> to figure out why certain programs were hanging on a simple
> "sleep(1);" statement.  Although this happens on System V, I've
> been told this problem is common to most non-BSD UNIXes.
> 
>    sleep(arg)	/* A simplified view of the sleep subroutine */
>    {
> 	...
> 	alarm(arg);	/* sets  p_clktim=arg  in proc table */
> 	...		/* a critical time */
> 	pause();	/* waits for SIGALRM: --pp->p_clktim == 0 */
>    }
> 
>    If an alarm(1) is executed *just* prior to a one second time
>    tick, and if the time tick occurs before the pause(), then the
>    pp->p_clktim value hits zero in clock() before the pause() is
>    done, and the alarm signal will be missed by the pause.  This
>    results in an INFINITE sleep.  If the process is suspended for
>    more than one second prior to the pause, then alarms longer
>    than one second could hang, too.

I wrote the following fudge routine to handle a similar problem 
when using 4.1 BSD. The 4.2 signal stuff allows a cleaner (though
less efficient) solution. It only works on the VAX as written, though
adaptation to other machines would probably be possible.


/* PAUSE_FUDGE - Fudge routines to fix up pause race problem */

/* This module allows someone to wait until some condition has been
   made true by an interrupt routine without wasting cp time in a 
   polling loop. 

   The Unix 'pause' system call will suspend a process until an
   interrupt (i.e. signal) is received. This is not directly 
   usable in a wait loop however, since between a check for a 
   condition and a call of pause an interrupt may occur which
   would have made the condition true. So the following wait
   loop may hang up:

           while (!condition) pause();

   The following wait loop is to be used instead:

           for (;;)
           { jk_set_up_pause();
             if (condition) break;
             jk_maybe_do_pause();
           }

   The way this works is that jk_set_up_pause creates a routine
   which will perform a pause system call. jk_maybe_do_pause will
   execute this routine. The interrupt routine should be written
   to call the routine jk_disable_pause, which changes the routine
   created by jk_set_up_pause to do nothing instead of a pause.
   This is done by a change of a single Vax machine instruction to
   nop's. 

*/

static char pause_routine[5];		/* Pause system call or nop's */

/* Set up a routine to do a "pause" system call. */

jk_set_up_pause()
{ register char *p;
  p = &pause_routine[2];
  *p++ = 0274; *p++ = 035;	/* chmk $pause */
  *p++ = 04;			/* ret */
}

/* Execute routine to do pause system call, unless it has been nop'ed out. */

jk_maybe_do_pause()
{ (*(void (*)())pause_routine)();
}

/* Disable pause call by replacing system call with nop's */

jk_disable_pause()
{ register char *p;
  p = &pause_routine[2];
  *p++ = 01; *p++ = 01;		/* nop; nop */
}

andrews@calgary.UUCP (Keith Andrews) (03/08/85)

> I wrote the following fudge routine to handle a similar problem 
> when using 4.1 BSD. The 4.2 signal stuff allows a cleaner (though
> less efficient) solution. It only works on the VAX as written, though
> adaptation to other machines would probably be possible.
> 
> 
> static char pause_routine[5];		/* Pause system call or nop's */
> 
> /* Set up a routine to do a "pause" system call. */
> 
> jk_set_up_pause()
> { register char *p;
>   p = &pause_routine[2];
>   *p++ = 0274; *p++ = 035;	/* chmk $pause */
>   *p++ = 04;			/* ret */
> }
> 
			........
> 
> jk_maybe_do_pause()
> { (*(void (*)())pause_routine)();
> }
> 
> /* Disable pause call by replacing system call with nop's */
> 
> jk_disable_pause()
> { register char *p;
>   p = &pause_routine[2];
>   *p++ = 01; *p++ = 01;		/* nop; nop */
> }

Although this solution to the problem may be sound, this method of implementing
it is, er... dumb (to say the least).  The following implementation is much
easier to understand, simpler and (Oh wow!) portable:


		int (*foo)();
		int pause();
			...
		{
			...
			/* Point at the pause() call */
			foo = pause;
			/* Set up alarm */
			alarm(1);
			/* Actually do pause (well, maybe) */
			(*foo)();
			/* Proceed merrily along */
		}

		int null()
		{}	/* Do nothing function */

		alarm_signal_handler()
		{
			foo = null;
		}


					Keith Andrews
					U of Calgary, Dept. of CS

radford@calgary.UUCP (Radford Neal) (03/09/85)

> > (Routine posted by me that handles the race problem caused by 
> >  code like  alarm(1); pause();  by using self-modifying code 
> >  to destroy the pause system call in the interrupt routine activated
> >  by the alarm signal.)

> Although this solution to the problem may be sound, this method of implementing
> it is, er... dumb (to say the least).  The following implementation is much
> easier to understand, simpler and (Oh wow!) portable:
> 
> 
> 		int (*foo)();
> 		int pause();
> 			...
> 		{
> 			...
> 			/* Point at the pause() call */
> 			foo = pause;
> 			/* Set up alarm */
> 			alarm(1);
> 			/* Actually do pause (well, maybe) */
> 			(*foo)();
> 			/* Proceed merrily along */
> 		}
> 
> 		int null()
> 		{}	/* Do nothing function */
> 
> 		alarm_signal_handler()
> 		{
> 			foo = null;
> 		}
> 
> 
> 					Keith Andrews
> 					U of Calgary, Dept. of CS

Unfortunately, this implementation of the solution DOES NOT WORK!

One must make sure that no race occurs if the interrupt occurs at ANY
time, right up to the instruction which transfers control to the
kernel. In the above, it is still possible for the pause procedure to
be called before the interrupt but for it to occur before the chmk
instruction *within* the the pause procedure is executed. Since a VAX
procedure call takes quite a while, this is not all that unlikely. It
is also possible that the statement (*foo)(); itself will be compiled
into several instructions (it definitely will be on a 68000).

There may be a marginally cleaner way of doing this using "indirect
system calls", which exist on PDP11 Unices (at least). I see no way
of doing it in a machine-independent manner without the appropriate
kernel extensions such as 4.2 has. The code-modification technique
might still be useful on a 4.2 system if interrupts occur lots of
times, making the appropriate sighold (or whatever, I haven't read the
manual recently) calls too time-consuming.

    Radford Neal
    The University of Calgary

wapd@houxj.UUCP (Bill Dietrich) (03/11/85)

I tried the program sent in a previous article on System V release 2
on a VAX-11/780, a 3B-20S and a 3B-5.  No hang on any of them.
The program settled down to a pattern like '+-----+---+----+----'
after about 10 characters, on each machine.

				Bill Dietrich
				ihnp4!houxj!wapd

ptw@encore.UUCP (P. Tucker Withington) (03/12/85)

It seems to me that the ill-defined functionality of alarm, signal, and pause
is just a plain, old, *bug* that should be addressed by AT&T and/or the Unix
standards committee rather than hack-fixed.  (Maybe they already did?)

My proposal (2 cent royalty):

The *kernel* should maintain a per-process "wakeup waiting" flag that is
cleared by signal, set when a signal is caught in user mode, and causes pause
to return immediately (as if interrupted) if it is set when a pause is invoked.
I believe this would both eliminate the race/vulnerable condition and stay
within the original intent of the semantics of signals and pause.  (To the
extent that read, write, and wait have similar semantics w.r.t. signals, they
should also use the flag.)

                               o.o      --tucker
                                ~

ptw@encore.UUCP (P. Tucker Withington) (03/12/85)

As long as I'm opening myself up to flames...

It just occurred to me that with a simple change to the semantics of pause the
"wakeup waiting" flag could be considered to already be in the kernel:

As I read the manual (I didn't read the code) pause will only return when a
signal is "caught".  If the semantics were that it would also return
(immediately) if there were no signal handlers active (since they revert when
caught), the race condition would also disappear.  Pause could even return a
different error in this case, informing you that there is nothing to pause for
(in the spirit of ECHILD) if you really want to go whole hog.

                               o.o      --tucker
                                ~

(I know, I really should be using 4.2 and I do, but SysV ain't all *that* bad
either.)

jack@boring.UUCP (03/15/85)

In article <179@encore.UUCP> ptw@encore.UUCP (P. Tucker Withington) writes:
>As I read the manual (I didn't read the code) pause will only return when a
>signal is "caught".  If the semantics were that it would also return
>(immediately) if there were no signal handlers active (since they revert when
>caught), the race condition would also disappear.  

This sounds nice at first, but gives rise to some even weirder
situations.
 For instance, I have a program that uses this feature, and then,
later on, decide to put an interrupt handler in it, in case the
user wishes to abort the program.
 Guess what? Suddenly my program stops working!! I wouldn't want
to look for *that* bug........
 An other approach would be to let pause() return if there is no
signal handler for SIGALRM, but I'm sure that there are people
who use pause() for things totally unrelated to alarm() calls.

 By the way, I *do* agree that it might not be a bad idea to
have pause() return when there are no signal handlers installed,
but I think it is not a complete solution to this race condition.
-- 
	Jack Jansen, {decvax|philabs|seismo}!mcvax!jack
It's wrong to wish on space hardware.