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