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