Hi Eliot


On 16.09.2014, at 20:18, Eliot Miranda <eliot.miranda@gmail.com> wrote:

Hi Max,

On Tue, Sep 16, 2014 at 11:05 AM, Max Leske <maxleske@gmail.com> wrote:
Hi

As always when I want to check if a process has died I get very confused by #isTerminated and I�m wondering if I just don�t get how it�s supposed to work or if there are others that share my confusion.

Old implementation:

isTerminated

        self isActiveProcess ifTrue: [^ false].
        ^suspendedContext isNil
          or: ["If the suspendedContext is the bottomContext it is the block in Process>>newProcess.
                   If so, and the pc is greater than the startpc, the bock has alrteady sent and returned
                   from value and there is nothing more to do."
                suspendedContext isBottomContext
                and: [ suspendedContext pc > suspendedContext startpc ] ]


Pharo 4 implementation:

isTerminated
        self isActiveProcess ifTrue: [^ false].
        ^suspendedContext isNil
         or: ["If the suspendedContext is the bottomContext it is the block in Process>>newProcess.
                  If so, and the pc is greater than the startpc, the bock has alrteady sent and returned
                  from value and there is nothing more to do.�
                suspendedContext isBottomContext
                and: [ suspendedContext isDead not �<������������������������� new"
                and: [ suspendedContext pc > suspendedContext startpc ] ] ]


The old implementation would break if the suspended context was dead (i.e. the pc was nil) because the send of #> would produce an MNU.
The new implementation doesn�t fix that, even though it looks like it at first glance: if the pc is nil, the #> send will still happen -> MNU.

Off the top of my head it would seem that it should be isDead or: [] not isDead not and:

isTerminated
        self isActiveProcess ifTrue: [^ false].
        ^suspendedContext isNil
         or: ["If the suspendedContext is the bottomContext it is the block in Process>>newProcess.
                  If so, and the pc is greater than the startpc, the bock has alrteady sent and returned
                  from value and there is nothing more to do.�
                suspendedContext isBottomContext
                and: [ suspendedContext isDead
                          or: [ suspendedContext pc > suspendedContext startpc ] ] ] 

Phew. Glad you see that the same way.

isDead
        ^ pc isNil

and maybe (suspendedContext pc ifNil: [true] ifNotNil: [:pc| pc > suspendedContext startpc]) is more obvious.

Anyway, neither implementation will reliably tell me if the process has been terminated:
- an inactive process will be suspended when #terminate is sent and report that it has not been terminated (#isSuspended -> true, #isTerminated -> false)

except that it *hasn't* been terminated, it is merely in the process of termination.  It isn't terminated until all unwind blocks have run, right?

True. Easy to forget when you can just kill -9 on the console� :)

 
- a properly terminated process will raise an MNU (although apparently not always�?)

but that's a bug the change I suggested will fix.
 
- all the states in between: no clue

I would like to know two things:
1. how can I check if a process has already *received* a #terminate? (I would then assume that the process will die eventually)

I don't think you can without putting a critical section around terminate and adding some process-specific variable that is set when you send terminate.  termination is not instantaneous (unwind blocks have to be run), so it is potentially interruptible.

I�ve thought about this a bit. I wouldn�t really care if I don�t get a correct answer about the termination status immediately but I want to have it before the process terminates. In case of something like the following:

([ 10 seconds asDelay wait ] forkAt: 11) terminate

it can (potentially) take a very long time for the process to terminate. So if I have to poll 3 or 4 times that�s ok but I don�t want to wait for minutes.
One solution would be to take your idea of the variable, but without the critical block:

terminate 
"Stop the process that the receiver represents forever.  Unwind to execute pending ensure:/ifCurtailed: blocks before terminating."

| ctxt unwindBlock oldList |
terminating := true.  �<����������������������������������� changed"
self isActiveProcess 
ifTrue: [
ctxt := thisContext.
[ ctxt := ctxt findNextUnwindContextUpTo: nil.
ctxt isNil ] whileFalse: 
[ (ctxt tempAt: 2) ifNil:
[ ctxt tempAt: 2 put: nil.
unwindBlock := ctxt tempAt: 1.
thisContext terminateTo: ctxt.
unwindBlock value ]].
thisContext terminateTo: nil.
self suspend ]
ifFalse: [
"Always suspend the process first so it doesn't accidentally get woken up"
oldList := self suspend.
suspendedContext ifNotNil:[
"Figure out if we are terminating the process while waiting in Semaphore>>critical:
In this case, pop the suspendedContext so that we leave the ensure: block inside
Semaphore>>critical: without signaling the semaphore."
(oldList class == Semaphore and:[
suspendedContext method == (Semaphore compiledMethodAt: #critical:)]) ifTrue:[
suspendedContext := suspendedContext home.].
 "If we are terminating a process halfways through an unwind, try to complete that unwind block first."
(suspendedContext findNextUnwindContextUpTo: nil) ifNotNil: [ :outer |
(suspendedContext findContextSuchThat: [ :c | c closure == (outer tempAt: 1)]) 
ifNotNil: [ :inner |
"This is an unwind block currently under evaluation"
suspendedContext runUntilErrorOrReturnFrom: inner ]].
ctxt := self popTo: suspendedContext bottomContext.
ctxt == suspendedContext bottomContext ifFalse: [
self debug: ctxt title: 'Unwind error during termination']] ].



isTerminating
^ terminating ifNil: [ ^ false ]


With this small modification I can run the example from above and immediately see that it will die eventually.

I�m aware that one process might not see the change to the variable immediately but as I said, I wouldn�t really care.


What do you think?

Cheers,
Max

 
2. how can I check if a process is *actually* dead? (in case a �half dead� process will still unwind or whatever)

see above.
 

What would be necessary to make those tests (or better ones) possible (rewrite the whole process implementation?)?


Cheers,
Max



--
best,
Eliot