[M3devel] Performance issues with Process.Wait under userthreads

Tony Hosking hosking at cs.purdue.edu
Sun Feb 13 21:37:32 CET 2011


This all seems pretty reasonable.

On Feb 12, 2011, at 3:43 PM, Mika Nystrom wrote:

> Hi again m3devel (especially Tony), 
> 
> I have finally taken a bite of a problem that has been annoying me
> for a very, very long time.
> 
> Under user threads, the code for Process.Wait is as follows (see 
> ThreadPosix.m3):
> 
> PROCEDURE WaitProcess (pid: int; VAR status: int): int =
>  (* ThreadPThread.m3 and ThreadPosix.m3 are very similar. *)
>  CONST Delay = 0.1D0;
>  BEGIN
>    LOOP
>      WITH r = Uexec.waitpid(pid, ADR(status), Uexec.WNOHANG) DO
>        IF r # 0 THEN RETURN r END;
>      END;
>      Pause(Delay);
>    END;
>  END WaitProcess;
> 
> It inserts a 0.1 second delay after each failed waitpid.  This is extremely
> annoying for programs that start a long sequence of child processes and
> wait for them in sequence.  Namely, the compiler itself.  As a result
> the cm3 compiler (and PM3's m3build) are normally very very slow when
> using user threads.  For about the last ten years, I've had a hacked
> up m3build (for my PM3 installation) that skips the Pause and busy-waits
> instead.  
> 
> Note there is another problem here.  Since the Modula-3 runtime ignores
> SIGCHLD, no zombie processes are created since the Unix system automatically
> reaps the child processes.  I can see this would be a problem since PIDs
> are eventually reused and ... couldn't Uexec.waitpid wind up referring to
> the wrong process??
> 
> I will further note that the comment in Process.i3 reads as follows:
> 
> PROCEDURE Wait(p: T): ExitCode;
> 
>    Wait until the process with handle p terminates, then free the operating system resources associated with the process and return an exit code indicating the reason for its termination. It is a checked runtime error to call Wait twice on the same process handle. 
> 
> I am going to take this as fair warning that Process.Create *may* use
> resources that are not going to be released until Process.Wait has been
> called.
> 
> I have modified (in my local copy of CM3) the system as follows.
> I have come up with a semi-general mechanism for immediately unblocking
> a thread on the receipt of a unix signal.  
> 
> 1. The system relies on changing
> ThreadPosix.XPause such that if a signal is allowed to wake up a threads,
> that fact is recorded in a new field in the thread's descriptor
> record (of type ThreadPosix.T).  
> 
> 2. On receipt of a waited-for unix signal, a mask is set and control
> is passed to the thread scheduler which maintains the non-zero mask for
> exactly one iteration through the thread ring.
> 
> 3. If a thread is paused and waiting for EITHER a signal or some time,
> the thread is released for running and the thread's waiting state is 
> cleared.
> 
> The changes are more or less as follows:
> 
> 1. I have added a new field of type "int" to ThreadPosix.T:
> 
>      (* if state = pausing, the time at which we can restart *)
>      waitingForTime: Time.T;
> 
> +     (* if state = pausing, the signal that truncates the pause *)
> +     waitingForSig: int := -1;
> +
>      (* true if we are waiting during an AlertWait or AlertJoin
>         or AlertPause *)
>      alertable: BOOLEAN := FALSE;
> 
> 
> 2. Modifications to pause:
> 
> + PROCEDURE SigPause(n: LONGREAL; sig: int)=
> +   <*FATAL Alerted*>
> +   VAR until := n + Time.Now ();
> +   BEGIN
> +     XPause(until, FALSE, sig);
> +   END SigPause;
> +
>  PROCEDURE AlertPause(n: LONGREAL) RAISES {Alerted}=
>    VAR until := n + Time.Now ();
>    BEGIN
>      XPause(until, TRUE);
>    END AlertPause;
> 
> ! PROCEDURE XPause (READONLY until: Time.T; alertable := FALSE; sig:int := -1)
> !   RAISES {Alerted} =
>    BEGIN
>      INC (inCritical);
>        self.waitingForTime := until;
>        self.alertable := alertable;
> +       IF sig # -1 THEN
> +         self.waitingForSig := sig
> +       END;
>        ICannotRun (State.pausing);
>      DEC (inCritical);
>      InternalYield ();
> 
> 3. The received-signals mask:
> 
> ! CONST MaxSigs = 64;
> ! TYPE Sig = [ 0..MaxSigs-1 ];
> !
> ! (* in order to listen to other signals, they have to be enabled in
> !    allow_sigvtalrm as well *)
> ! VAR (*CONST*) SIGCHLD := ValueOfSIGCHLD();
> !
> !     gotSigs := SET OF Sig { };
> !
> 
> ValueOfSIGCHLD() is a C function used to get the value of the SIGCHLD
> constant without guessing at it (in ThreadPosixC.c).
> 
> 4. changes to the signal handler:
> 
> ! PROCEDURE switch_thread (sig: int) RAISES {Alerted} =
>    BEGIN
>      allow_sigvtalrm ();
> !
> !     INC(inCritical);
> !     (* mark signal as being delivered *)
> !     IF sig >= 0 AND sig < MaxSigs THEN
> !       gotSigs := gotSigs + SET OF Sig { sig }
> !     END;
> !     DEC(inCritical);
> !
> !     IF inCritical = 0 AND heapState.inCritical = 0 THEN
> !       InternalYield ()
> !     END;
>    END switch_thread;
> 
> Note that I don't know if INC/DEC(inCritical) does exactly the right
> thing here.
> 
> 5. changes to the scheduler:
> 
>   a. thread wakeup
>              IF t.alertable AND t.alertPending THEN
>                CanRun (t);
>                EXIT;
> +
> +             ELSIF t.waitingForSig IN gotSigs THEN
> +               t.waitingForSig := -1;
> +               CanRun(t);
> +               EXIT;
> 
>              ELSIF t.waitingForTime <= now THEN
>                CanRun (t);
>                EXIT;
> !
> 
>   b. clearing the mask
> 
>        END;
>      END;
> 
> +     gotSigs := SET OF Sig {};
> +
>      IF t.state = State.alive AND (scanned OR NOT someBlocking) THEN
>        IF perfOn THEN PerfRunning (t.id); END;
>        (* At least one thread wants to run; transfer to it *)
> 
> 6. changes to WaitProcess (Process.Wait):
> 
>  PROCEDURE WaitProcess (pid: int; VAR status: int): int =
>    (* ThreadPThread.m3 and ThreadPosix.m3 are very similar. *)
> !   CONST Delay = 10.0D0;
>    BEGIN
>      LOOP
>        WITH r = Uexec.waitpid(pid, ADR(status), Uexec.WNOHANG) DO
>          IF r # 0 THEN RETURN r END;
>        END;
> !       SigPause(Delay,SIGCHLD);
>      END;
>    END WaitProcess;
> 
> 7. install signal handler even if program is single-threaded:
> 
>  BEGIN
> +   (* we need to call set up the signal handler for other reasons than
> +      just thread switching now *)
> +   setup_sigvtalrm (switch_thread);
>  END ThreadPosix.
> 
> 8. modify signal handler in ThreadPosixC.c to catch SIGCHLD:
> 
>    sigemptyset(&ThreadSwitchSignal);
>    sigaddset(&ThreadSwitchSignal, SIG_TIMESLICE);
> +   sigaddset(&ThreadSwitchSignal, SIGCHLD);
> 
>    act.sa_handler = handler;
>    act.sa_flags = SA_RESTART;
>    sigemptyset(&(act.sa_mask));
>    if (sigaction (SIG_TIMESLICE, &act, NULL)) abort();
> +   if (sigaction (SIGCHLD, &act, NULL)) abort();
> 
> I'll send the complete diff in a separate message for those who want
> to study it more closely.
> 
> I propose the above changes for inclusion in the current CM3 repository.
> 
>    Mika




More information about the M3devel mailing list