[comp.sys.amiga.tech] Tasks and Signals

papa@pollux.usc.edu (Marco Papa) (11/15/88)

I have started playing with signals and subtasks and I run into a very strange
problem.

Consider the following program, which is a combination of Sam Dicker's 
program VTbeep that used a subtask to create a VT100-like beep, and 
Ali Ozer's clock program.  In my combination I let the subtask update
the "main" task menu bar once a minute.  The main program has provisions
for killing the clock, using SIGBREAKF_CTRL_C, and signalling it to restart.

The problem is that when I signal the subtask to restart, with SIGF_CLOCK,
the timer always returns without Wait()-ing as requested.  The code that
shows this is between #if 0 and #endif.  Take those out to see what I mean.
You'll have to reboot your machine in this case since the sub-task never gets
pre-empted again.  If anybody can see what I don't see, I'd appreciate a
comment.  By the way, DateStamp() is done always in the main task and not in 
the subtask since, as Rob Peck teaches, it is an AmigaDOS routine and cannot 
be executed by a task, only by a process.

The entire source and makefile follows.  This is compiled for MANX 3.60.

#!/bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #!/bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	taskclock.c
#	makefile
# This archive created: Mon Nov 14 22:39:06 1988
# By:	Marco Papa (Felsina Software, Los Angeles, CA)
export PATH; PATH=/bin:$PATH
echo shar: extracting "'taskclock.c'" '(9556 characters)'
if test -f 'taskclock.c'
then
	echo shar: over-writing existing file "'taskclock.c'"
fi
cat << \SHAR_EOF > 'taskclock.c'
/*
 * taskclock.c
 */

/*
from:
VTbeep.c -- Copyright (C) 1986 Same Dicker, Commodore/Amiga Inc.
and
LedClock.c -- Copyright (C) 1987 Ali T. Ozer
*/

#include <exec/types.h>
#include <functions.h>
#include <intuition/intuition.h>
#include <exec/devices.h>
#include <exec/memory.h>
#include <devices/timer.h>
#include <graphics/gfxmacros.h>
#include "libraries/dos.h"

#define CLOCKNAME	"VTClock"
#define SIGB_CLOCK	31
#define SIGF_CLOCK	(1 << 31)
#define SIGF_PORT	(1 << replyPort->mp_SigBit)

#define TWENTYFOURHOUR 1
#define FANCYPANIC     1

#define COPYRIGHT "TaskClock 1.0 -- Marco Papa, Ali T. Ozer, Sam Dicker"

#if FANCYPANIC
#define Panic(arg)  WarnUser(arg)
#else 
#define Panic(arg)  CloseThings(1)
#endif

struct Window      *MyWin;
struct Library     *IntuitionBase;
struct Library     *GfxBase;
struct timerequest *timerIOB;
unsigned long      WindowSig, TimerSig;
int                curminutes;
long		   qtime;
long timevec[3];		/* for DateStamp() */

/* OpenTimer will open the timer device and also do all the initialization
** necessary to send timer requests... 
*/
OpenTimer (tr)
struct timerequest *tr;
{
  struct MsgPort *timerport;

  while ((timerport = CreatePort (NULL, 0L)) == NULL) Panic ("No port");

  OpenDevice (TIMERNAME, UNIT_VBLANK, tr, 0L);
  tr->tr_node.io_Message.mn_ReplyPort = timerport;
  tr->tr_node.io_Command = TR_ADDREQUEST;

}

  static struct NewWindow MyWinInfo =
  { 0, 0, 640, 20, /* Lft,Top,Wd,Hgt */
    -1,-1,                   /* Detail pen, Block pen (-1 = use screens) */
    CLOSEWINDOW,                                           /* IDCMPflags */
    SMART_REFRESH | WINDOWDEPTH | WINDOWDRAG | WINDOWCLOSE | 
	NOCAREREFRESH | ACTIVATE,
    NULL, NULL, NULL,              /* FirstGadget, Menu Checkmark, Title */
    NULL, NULL,                                        /* Screen, Bitmap */
    0, 0, 0, 0,                     /* Min Width/Height Max Width/Height */
    WBENCHSCREEN                                                 /* Type */
  };

/* OpenThings opens the Amiga libraries, the window. and the timer.
** If something goes wrong, a requester will pop up to warn the user.
** (If FANCYPANIC is defined as zero, then no requester pops up --- the
** program just quits.)
*/
OpenThings ()
{

  if (((IntuitionBase = OpenLibrary("intuition.library",0L)) == NULL) ||
      ((GfxBase = OpenLibrary("graphics.library",0L)) == NULL)) CloseThings (1);

  while (!(MyWin = OpenWindow(&MyWinInfo))) Panic ("No window");
  SetWindowTitles (MyWin, "JUNK", COPYRIGHT);

  WindowSig = 1L << MyWin->UserPort->mp_SigBit;
}  

/* SendTimerRequest sends a timer request for the indicated amount of seconds.
** Assumes the timer request block pointed to by tr has already been properly
** setup --- including the io_Command field. 
*/
SendTimerRequest (tr, seconds)
struct timerequest *tr;
unsigned long seconds;
{
  tr->tr_time.tv_micro = 0L;
  tr->tr_time.tv_secs  = seconds;
  SendIO (tr);
}
  
#if FANCYPANIC

/* WarnUser is called when something goes wrong during initialization.
** WarnUser assumes Intuition is opened! (Now if that's not the case,
** then you're in trouble...)
*/

WarnUser (reason)
UBYTE *reason;
{
  static struct IntuiText postxt  = {3,1,COMPLEMENT,4,4,NULL,(UBYTE *)"Retry",NULL};
  static struct IntuiText negtxt  = {3,1,COMPLEMENT,4,4,NULL,(UBYTE *)"Sigh",NULL};
  static struct IntuiText bodytxt = {3,1,COMPLEMENT,4,6,NULL,NULL,NULL};

  bodytxt.IText = reason;
  if (AutoRequest (NULL,&bodytxt,&postxt,&negtxt,0L,0L,300L,60L) == FALSE)
#if 0
     CloseThings (1);
#else
	;
#endif
}

#endif

/* CloseTimer closes the timer pointed to by tr. If no reply port is present,
** then CloseTimer assumes the timer was never opened.
*/
CloseTimer (tr)
struct timerequest *tr;
{
  struct MsgPort *msgp;
  if (msgp = tr->tr_node.io_Message.mn_ReplyPort) {
    DeletePort (msgp);  
    CloseDevice (tr);
  }
}

/* CloseThings releases all the resources obtained by the program.
*/
CloseThings(error_code)
int error_code;
{ 
  if (MyWin)         CloseWindow(MyWin);
  if (GfxBase)       CloseLibrary(GfxBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  exit (error_code);
}

char mytime[80];
char *fmt = "TaskClock - %d:%02d F:%ldK C:%ldK %d:%02d";

static int hourC = 0;
static int minC = 0;

/* WriteTime writes out the new time. 
*/
WriteTime (minutes)
int minutes;
{
  int hours, minute;
  ULONG availfast, availchip;

#if TWENTYFOURHOUR
  hours = minutes / 60;
#else
  hours = (minutes / 60 + 11) % 12 + 1;
#endif
  minute = minutes % 60;    
  /* AM if minutes < 720, but we don't worry about this... */
  availfast = AvailMem(MEMF_FAST)/1024;
  availchip = AvailMem(MEMF_CHIP)/1024;

  sprintf(mytime, fmt, hours, minute, availfast, availchip, hourC, minC);

  SetWindowTitles (MyWin, mytime, COPYRIGHT);
  minC++;
  if (minC == 60) { minC = 0; hourC++; }
}    

/* ShowTheTime reads the time, updates the display, and sends a new timer
** request.
*/
ShowTheTime ()
{
  WriteTime (curminutes);
  /* Timer request for the next top of minute */
  SendTimerRequest (timerIOB, qtime);
  curminutes++;
  qtime = 60L;
}

main ()
{
  OpenThings  ();

  DateStamp (&timevec[0]);
  curminutes = (int)(timevec[1]);
  qtime = 60L - (long)(timevec[2] / 50L);

  if (VTClock()) {		/* start the clock task */
#if 0
	/* -----> IF YOU ADD THIS CODE IT LOOPS FOREVER <------- */

	/* reset after 1 minute */
	printf("Waiting to reset...\n");
	Delay(3000L);			/* 1 minute */
	printf("Resetting...\n");
	if (VTClock() == NULL) {
		printf("Reset!\n");
	}
#endif
	/* Now sit down, relax, and wait for an IDCMP event... */
	Wait (WindowSig);
	/* assume it is closewindow */
	KillVTClock();
  }
  CloseThings (0);
}

/* Including the following two lines prevents the linker from bringing in
** the two functions _wb_parse and _cli_parse called by the initialization
** code in _main. Reduces code size by about 900 bytes for Manx 3.40a.
*/
void _wb_parse () {}
void _cli_parse () {}

VTClock()
{
    struct Task *clockTCB;
    VOID clockTask();

    /* prevent clock child task, if it already exists, from going away before
     * it is signaled */
    Forbid();

    /* find the task by name */
    clockTCB = (struct Task *)FindTask(CLOCKNAME);
    /* check if the task exists */
    if (clockTCB == NULL)
    {
	/* it doesn't exist, so create it */
	sava4();	/* $$$$$$$$ */
	clockTCB = (struct Task *)CreateTask(CLOCKNAME, (long) 25, (long) clockTask, 1000L);
    }
    else
    {
	/* it already exist so signal it so restart it's timer */
	DateStamp (&timevec[0]);
	curminutes = (int)(timevec[1]);
	qtime = 60L - (long)(timevec[2] / 50L);
	Signal(clockTCB, SIGF_CLOCK);
	/* ---------> IT NEVER GETS HERE !!! <-------------- */
	clockTCB = NULL;		/* fake it */
    }
    Permit();

    printf("Clocktcb = %ld\n", (long) clockTCB);

    /* return success */
    return(clockTCB != NULL);
}

/* kill any clcok update in progress.  This is necessary before exiting the
 * main program; otherwise, if an update is playing, when the clock times out
 * and the child task wakes up its code segment may be gone */

KillVTClock()
{
    struct Task *clockTCB;

    do {
	/* prevent clock child task, if it already exists, from going away
	 * before it is signaled */
	Forbid();

	/* find the task by name */
	clockTCB = (struct Task *)FindTask(CLOCKNAME);

	/* check if the task exists */
	if (clockTCB != NULL) {
		/* it already exist so signal it so go away */
		Signal(clockTCB, SIGBREAKF_CTRL_C);

		/* give it a chance to wake up, if it is lower priority */
		Delay(10);
	}
	Permit();
	printf("Clocktcb = %ld\n", (long) clockTCB);

	/* if it existed, kill it again */
    } while (clockTCB != NULL);
}

/* update clock child task */

VOID clockTask()
{
    struct MsgPort *replyPort;
    ULONG signals;
    
    geta4();

    /* allocate signal used to re-start clock */
    if (AllocSignal(SIGB_CLOCK) == SIGB_CLOCK) {
	/* create reply port for timer and sound I/O block */
	replyPort = (struct MsgPort *)CreatePort(NULL);

	if (replyPort != NULL) {
	    /* create timer I/O block */
	    timerIOB = (struct timerequest *)
	    CreateExtIO(replyPort, sizeof(struct timerequest));

	    if (timerIOB != NULL) {

		/* open timer device */
		if (OpenDevice(TIMERNAME, UNIT_VBLANK, timerIOB, 0) == 0) {

		    timerIOB->tr_node.io_Command = TR_ADDREQUEST;

/* from this point on the task in cannot
   be pre-empted.  This prevents the
   parent task from signaling it to
   restart the timer while it is cleaning
   up */

Forbid();

do {
    /* start the timer */

    ShowTheTime();

        /* wait for:
	. the timer to time out (SIGF_PORT) ,
	. a signal to restart to timer (SIGF_CLOCK) , or
	. a signal to quit (SIGBREAKF_CTRL_C) */

		signals = Wait(SIGBREAKF_CTRL_C |
		SIGF_CLOCK | SIGF_PORT);

		    if ((signals & SIGF_CLOCK) != 0) {
		 	/* reset the time if signaled */
			hourC = 10;
			minC = 0;
		    }

		    /* if the timer is still going, kill it */
		    if (CheckIO(timerIOB) == 0) {
			AbortIO(timerIOB);
		    }
		    WaitIO(timerIOB);	/* otherwise just reply the message */

		    } while ((signals & SIGBREAKF_CTRL_C) == 0);

		    /* clean up */

		    CloseDevice(timerIOB);
		} /* if OpenDevice */
		DeleteExtIO(timerIOB, sizeof(struct timerequest));
	    } /* if timerIOB */
	    DeletePort(replyPort);
	} /* if replyPort */
	FreeSignal(SIGB_CLOCK);
    } /* if AllocSignal */
}

#asm
a4sav  dc.l    0
 
        public  _sava4
_sava4
        lea     a4sav,a0
        move.l  a4,(a0)
        rts
 
        public  _geta4
_geta4
        move.l  a4sav,a4
        rts
#endasm
SHAR_EOF
echo shar: extracting "'makefile'" '(74 characters)'
if test -f 'makefile'
then
	echo shar: over-writing existing file "'makefile'"
fi
cat << \SHAR_EOF > 'makefile'
taskclock: taskclock.c
	cc -n +L taskclock.c
	ln +cd -g taskclock.o -lc32
SHAR_EOF
#	End of shell archive
exit 0


-- Marco Papa 'Doc'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
uucp:...!pollux!papa       BIX:papa       ARPAnet:pollux!papa@oberon.usc.edu
 "There's Alpha, Beta, Gamma and Diga!" -- Leo Schwab [quoting Rick Unland]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=