[comp.sys.amiga] Spawning tasks which draw . . .

rokicki@rocky.STANFORD.EDU (Tomas Rokicki) (12/02/87)

/*
 *   bug.c
 *
 *   The interlock demonstrated by this program took me over a day to find,
 *   so I thought I'd pass it on to save someone else some pain.
 *
 *   Be careful with functions that use the blitter in tasks that you plan
 *   to RemTask() from the parent (or someone else!)  If the blitter is
 *   allocated when the RemTask() is executed, it will stay allocated and
 *   the system will lock up tighter than a drum.  To fix, simply Forbid()/
 *   Permit() around all the blitter using sections (like a sequence of
 *   Move/Draws.)
 *
 *   Compile and run this program (Manx).  If you run it with the command:
 *      bug
 *   with no arguments, it locks up the machine.  If you run it instead with
 *      bug forbid
 *   it will return correctly.  The only difference between the two actions
 *   is the void subroutine it launches; one has the Forbid()/Permit() pair,
 *   the other doesn't, thus showing the problem.
 */
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/tasks.h>
#include <exec/execbase.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <graphics/gfxmacros.h>
#include "functions.h"
struct IntuitionBase *IntuitionBase;
struct GfxBase       *GfxBase;
struct Task *task ;
struct RastPort *rastport ;
struct Screen *screen ;
char *stackmem ;
#define STACKSIZE (1000)
static struct NewScreen newscreen = {
   0, 0,
   640, 400,
   1,
   0, 1,
   HIRES | LACE | SCREENQUIET,
   CUSTOMSCREEN,
   NULL,
   NULL,
   NULL,
   NULL } ;
void task1() {
   geta4() ;
   while (1) {
      Move(rastport, 10L, 10L) ;
      Draw(rastport, 600L, 390L) ;
   }
}
void task2() {
   geta4() ;
   while (1) {
      Forbid() ;
      Move(rastport, 10L, 10L) ;
      Draw(rastport, 600L, 390L) ;
      Permit() ;
   }
}
main(argc, argv)
int argc ;
char *argv[] ;
{
   if ((IntuitionBase=
          (struct IntuitionBase *)OpenLibrary("intuition.library",0L))==NULL ||
       (GfxBase=
          (struct GfxBase *)OpenLibrary("graphics.library",0L))==NULL ||
       (screen = OpenScreen(&newscreen))==NULL ||
       (task = (struct Task *)AllocMem((long)sizeof(struct Task),
             MEMF_CLEAR | MEMF_PUBLIC))==NULL ||
       (stackmem = (char *)AllocMem((long)STACKSIZE, MEMF_CLEAR))==NULL)
      cleanup() ;
   rastport = &(screen->RastPort) ;
   task->tc_Node.ln_Pri = -20 ;
   task->tc_Node.ln_Type = NT_TASK ;
   task->tc_Node.ln_Name = "Line" ;
   task->tc_SPLower = (APTR)stackmem ;
   task->tc_SPUpper = task->tc_SPReg = (APTR)(stackmem + STACKSIZE - 8) ;
   AddTask(task, (argc > 1 ? task2 : task1), 0L) ;
   Delay(50L) ;
   RemTask(task) ;
   cleanup() ;
}
cleanup() {
   if (task)
      FreeMem(task, (long)sizeof(struct Task)) ;
   if (stackmem)
      FreeMem(stackmem, (long)STACKSIZE) ;
   if (screen)
      CloseScreen(screen) ;
   if (IntuitionBase)
      CloseLibrary(IntuitionBase) ;
   if (GfxBase)
      CloseLibrary(GfxBase) ;
   exit(0) ;
}

grant@hpindda.HP.COM (Grant Haidinyak) (12/03/87)

 >*   Be careful with functions that use the blitter in tasks that you plan
 >*   to RemTask() from the parent (or someone else!)  If the blitter is
 >*   allocated when the RemTask() is executed, it will stay allocated and
 >*   the system will lock up tighter than a drum.  To fix, simply Forbid()/
 >*   Permit() around all the blitter using sections (like a sequence of
 >*   Move/Draws.)

Wouldn't it be nice, if instead of doing a Forbid/Permit and locking out
the multitasking feature of the Amiga, have a pair of routines, something
like SetCritical/ResetCritical, which wouldn't allow a task to be aborted

Just a thought (my one for the week)

    Grant

ewhac@well.UUCP (Leo 'Bols Ewhac' Schwab) (12/05/87)

In article <787@rocky.STANFORD.EDU> rokicki@rocky.STANFORD.EDU (Tomas Rokicki) writes:
>/*
> *   bug.c
> *
> *   Be careful with functions that use the blitter in tasks that you plan
> *   to RemTask() from the parent (or someone else!)  If the blitter is
> *   allocated when the RemTask() is executed, it will stay allocated and
> *   the system will lock up tighter than a drum.  To fix, simply Forbid()/
> *   Permit() around all the blitter using sections (like a sequence of
> *   Move/Draws.)
> *
> *   Compile and run this program (Manx).  If you run it with the command:
> *      bug
> *   with no arguments, it locks up the machine.  If you run it instead with
> *      bug forbid
> *   it will return correctly.  The only difference between the two actions
> *   is the void subroutine it launches; one has the Forbid()/Permit() pair,
> *   the other doesn't, thus showing the problem.
> */

	Dale pointed this out to me when I asked if I could asynchronously
longjmp() out of a graphics operation.  Here are the things he enumerated:
--------
Graphics may allocate tmp memory for some blit operations. In general
we would like it to give it back.
Graphics may own the blitter, we would want it to disown it also.
Graphics calls LockLayer if the rastport has a layer attached to it.
Actually it calls LockLayerRom which is a simple ObtainSemaphire [sic].
The Layerlibrary actually calls the graphics library to lock the layer
so cosmic serenity is not disrupted.
--------

	It should also be noted that asynchronously RemTask()ing a running
program is *NOT* kosher under any circumstances.  Even if it's just a
CPU-bound goodie that spins in place, it still isn't a good idea (in my
opinion).  The aforementioned Cosmic Serenity could be disrupted in ways we
don't understand yet.

	That's why all sub-tasks that I write have some way of being
terminated externally (via message or signal).  Once I'm sure the task has
completed, then I RemTask() it.  I used to do a Wait(0L) to stop the task
before RemTask()ing it, but the Manx CreateTask() function does things
differently now, so I let the task exit, and it kills itself nicely (or
something like that).

	God, I'm tired.....

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
Leo L. Schwab -- The Guy in The Cape	ihnp4!ptsfa -\
 \_ -_		Recumbent Bikes:	      dual ---> !{well,unicom}!ewhac
O----^o	      The Only Way To Fly.	      hplabs / (pronounced "AE-wack")
"Although there are technical differences between the quality of images
created on the Amiga and on our system, we feel that viewers could be misled
to believe otherwise, even with your disclaimers to the contrary."
		-- Ralph J. Guggenheim, Pixar
"AMIGAS!!??  I thought we bought Pixars!  Geez, I can't tell the difference."
		-- Max Headroom Producer

peter@sugar.UUCP (Peter da Silva) (12/07/87)

>  >*   If the blitter is allocated when the RemTask() is executed, it
>  >*   will stay allocated and the system will lock up. To fix, simply
>  >*   Forbid()/Permit() around all the blitter using sections...
> Wouldn't it be nice, if instead of doing a Forbid/Permit and locking out
> the multitasking feature of the Amiga, have a pair of routines, something
> like SetCritical/ResetCritical, which wouldn't allow a task to be aborted...

Even better: why don't you and your task use semaphores or signals
to let the parent task know when it can remove the child task.

Here's a scenario that works out just fine in real life: The parent
sends the child a message. The child does its work and sends the message
back. Then the parent RemTask()s the child.

Just like the WorkBench does it.
-- 
-- Peter da Silva  `-_-'  ...!hoptoad!academ!uhnix1!sugar!peter
-- Disclaimer: These U aren't mere opinions... these are *values*.

dillon@CORY.BERKELEY.EDU (Matt Dillon) (12/08/87)

    Assumptions:  PopCLI-like program where child and parent are in the
    same segment.  Where Parent must be able to kill the child and know
    exactly when the child no longer exists.

	Parent spawns child at low priority
	Child runs
	Parent wants to kill child:
	    Parent sends child a prearranged signal and increases child's
	     priority.  Parent then Waits for a reply.
	    Child sends Parent a prearranged signal when it is ready to die
	    Child does a Wait(0).
	    Parent kills child. (rather than child kills itself).

    As far as RemTask goes, it doesn't care if the task in question is 
    waiting or running.... or in the middle of a critical library call!

			-Matt

grant@hpindda.HP.COM (Grant Haidinyak) (12/10/87)

>Even better: why don't you and your task use semaphores or signals
>to let the parent task know when it can remove the child task.

The reason you would want an OS supported way of setting a critical region
it is that what if there is no explicit syncronization between the parent
and the child (i.e. exec), then how do you abort the task/process.

The scenario that I am talking about is:  Joe Helpless User executes a 
program, for some reason, the program goes into a tight loop, Joe then 
types a control-c (or what ever), and the program is stopped, and ALL of the
resources that it had allocated would be freed.  Wouldn't it be nice....

Hey, if unix can do it why cann't we!!

>-- Peter da Silva  `-_-'  ...!hoptoad!academ!uhnix1!sugar!peter

Grant 

ewhac@well.UUCP (Leo 'Bols Ewhac' Schwab) (12/11/87)

In article <8712081929.AA17657@cory.Berkeley.EDU> dillon@CORY.BERKELEY.EDU (Matt Dillon) writes:
>	Parent spawns child at low priority
>	Child runs
>	Parent wants to kill child:
>	    Parent sends child a prearranged signal and increases child's
>	     priority.  Parent then Waits for a reply.
>	    Child sends Parent a prearranged signal when it is ready to die
>	    Child does a Wait(0).
>	    Parent kills child. (rather than child kills itself).
>
	A more general (and better, in my opinion) way to go about this is
as follows:
-------
		PARENT				CHILD
	Parent sends kill signal to
	 child.
	Parent waits for reply.
					Child receives kill signal.
					Child cleans up anything it
					 allocated.
					Child Forbid()s.
					Child replies signal.
					Child waits for Godot (Wait (0L)).
	Parent receives reply.
	Parent RemTask()s child.
-------
	For AmigaDOS processes, the procedure is slightly different:
-------
		PARENT				CHILD
	Parent sends kill signal to
	 child.
	Parent waits for reply.
					Child receives kill signal.
					Child cleans up anything it
					 allocated.
					Child Forbid()s.
					Child replies signal.
					Child **exits**.  This preserves
					 DOS's sanity.
	Parent receives reply.
	Parent **UnloadSeg()s** child.
--------
	Comments?

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
Leo L. Schwab -- The Guy in The Cape	ihnp4!ptsfa -\
 \_ -_		Recumbent Bikes:	      dual ---> !{well,unicom}!ewhac
O----^o	      The Only Way To Fly.	      hplabs / (pronounced "AE-wack")
"Work FOR?  I don't work FOR anybody!  I'm just having fun."  -- The Doctor

flynn@oscvax.UUCP (Flynn D. Fishman) (12/19/87)

In article <4696@well.UUCP> ewhac@well.UUCP (Leo 'Bols Ewhac' Schwab) writes:
>	A more general (and better, in my opinion) way to go about this is
>as follows:
>-------
>		PARENT				CHILD
>	Parent sends kill signal to
>	 child.
>	Parent waits for reply.
>					Child receives kill signal.
>					Child cleans up anything it
>					 allocated.
>					Child Forbid()s.
                    ^^^^^^^^^^^^^^^^^
>					Child replies signal.
>					Child waits for Godot (Wait (0L)).
>	Parent receives reply.
>	Parent RemTask()s child.
>-------

My question is this, why should the Child Forbid(), and who does the
Permit() is that Automatic.  I do not see any reason why the Child should
Forbid() since the parent is locked waiting for a reply anyways.

Also.  I am using Manx 3.4a, and have my Task in the same file as the
parent.  What I do is have my task check to see if he/she got a message
directing him/her to die (exit nicely).  When he/she gets the message
(s)he clears all the memory (s)he allocated, etc. and just falls of the
end of the world, either by letting her code run out or via return;
I think this has its advantages in that a task can be killed by some other
process if neccessary.

As well since I am a Multitasking Novice, (ok novice), is
RemTask neccessary?  And what exactly does it do.

On another note (c minor) if I spawn my
task as indicated before, (using only local variables) and the mainline
goes bye bye, does my system guru and start spouting smoke or does the
memory used by my task still exist? (I vote for the guru but I
have not had time to check and see)

p.s. I just got a 1084, and do not seem to have any problems with it, In
fact I like the pictures 10+ times better that the 2002 at work (but the
2002 seems to be worse than normal)

-- 
Please ignore any spelling, or grammarical errors, I am using a really
lousy keyboard.  (The fact that I do not speak to goodlooking english does
not help the matter much either)

_Flynn D. Fishman, Esquire

(Ontario Science Centre)
   ...{watmath,ihnp4,decvax,cbosgd}!utgpu!oscvax!flynn

Soon to be (as I am going back to school)
   ...{watmath,ihnp4,decvax,cbosgd}!watcgl!fdfishman

ewhac@well.UUCP (Leo 'Bols Ewhac' Schwab) (12/21/87)

In article <554@oscvax.UUCP> flynn@oscvax.UUCP (Flynn D. Fishman) writes:
>In article <4696@well.UUCP> ewhac@well.UUCP (Leo 'Bols Ewhac' Schwab) writes:
>>		PARENT				CHILD
>>	Parent sends kill signal to
>>	 child.
>>	Parent waits for reply.
>>					Child receives kill signal.
>>					Child cleans up anything it
>>					 allocated.
>>					Child Forbid()s.
>					^^^^^^^^^^^^^^^^
>>					Child replies signal.
>>					Child waits for Godot (Wait (0L)).
>>	Parent receives reply.
>>	Parent RemTask()s child.
>
>My question is this, why should the Child Forbid(), and who does the
>Permit() is that Automatic.  I do not see any reason why the Child should
>Forbid() since the parent is locked waiting for a reply anyways.
>
	The Permit() is implicit when you Wait() for Godot (#define GODOT
0L), or when you exit.

	Consider the following scenario.  You are the parent.  You spawn a
child process with a priority lower than your own.  While you (the parent)
are waiting for events, the child gets to run.  Eventually, you wish to kill
off the child.  So you send a pre-arranged signal to the child and wait for
the reply signal.

	The child cleans up, Forbid()s, replys the signal, and then either
Wait()s or exits.  By calling Forbid(), the child prevents the parent from
waking up immediately on the signal.  If the Forbid() weren't there, the
child would get pre-empted the instant it sent the signal off, and the
parent would wake up and blissfully remove the child task before it had
truly finished executing.

>On another note (c minor) if I spawn my
>task as indicated before, (using only local variables) and the mainline
>goes bye bye, does my system guru and start spouting smoke or does the
>memory used by my task still exist? (I vote for the guru but I
>have not had time to check and see)
>
	You have cast your ballot correctly.  Since your child task is part
of the parent's executable image, the DOS can't tell the difference when your
parent exits, and will unload both of you.  You will Guru either immediately
or when the next program overlays the memory you're running out of.  The
latter case will be much more difficult to track down as a bug.

	Moral:  Unless your subtask was LoadSeg()d, terminate all your
children before you terminate yourself.

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
Leo L. Schwab -- The Guy in The Cape	ihnp4!ptsfa -\
 \_ -_		Recumbent Bikes:	      dual ---> !{well,unicom}!ewhac
O----^o	      The Only Way To Fly.	      hplabs / (pronounced "AE-wack")
"Work FOR?  I don't work FOR anybody!  I'm just having fun."  -- The Doctor