[squeak-dev] Re: [Pharo-project] #ensure: issues

Eliot Miranda eliot.miranda at gmail.com
Thu Mar 4 02:32:17 UTC 2010


On Wed, Mar 3, 2010 at 6:13 PM, Andreas Raab <andreas.raab at gmx.de> wrote:

> On 3/3/2010 5:52 PM, Eliot Miranda wrote:
>
>> But running unwinds in anything other than the owning process is
>> /broken/
>>
>
> No, Eliot, it's *different*. If it were outright broken then lots of unwind
> actions shouldn't work. That is not the case.


No, it is /broken/.  What exception handlers are in effect when an unwind is
run from another process?  Its not just tat process identity isn't being
preserved (which I would argue is important but not essential) its that
unwinds are being run in an entirely arbitrary context which has nothing to
do with their running normally.  e.g.

givethAndTakethAway: aCollection
     [[aCollection do: [:k| mydict at: k put: self thing].
       self doStuff] ensure: [aCollection do: [:k| mydict removeKey: k]]
        on: KeyNotFoundError
        do: [:ex| ex proceedWith: nil]

will remove whatever keys in mydict are in aCollection when run normally,
but when run if terminated from another process will break if any keys in
aCollection have already been removed from mydict.

Running unwinds in another process is /broken/, period.



> In fact, I am more and more starting to think that it may be the *right*
> thing to do because you get the process context associated with the
> terminating process allowing to do something like:
>
>  "kill the process silently"
>  [p terminate]
>    on: Timeout do:[:ex| ex return "skip it"]
>    on: Error do:["ignore"].
>

You could do this better by saying e.g.

      p terminateOn: Error do: [:ex| ex return "skip it"]

and have terminateOn:do: et al install the exception handler at the bottom
of stack.


> There are obviously issues if the unwind actions refer to process local
> state; but then again perhaps providing said context for the duration of the
> unwind is the right thing, along the lines of:
>
> Process>>terminate
>
>  self isActiveProcess ifFalse:[
>    Processor activeProcess evaluate: [self unwind] onBehalfOf: self.
>  ].
>
> This for example would guard errors during unwind as initially intended
> while ensuring that process relative state is retrieved correctly.
>

Yes, but what if I do something like

         [processMemo add: Processor activeProcess.
          self doStuff]
                ensure: [processMemo remove: Processor activeProcess]
?

I'm going to be required to know that within unwind blocks Processor
activeProcess lies and I need to use a temporary?  As in:

         | me |
         [processMemo add: (me := Processor activeProcess).
          self doStuff]
                ensure: [processMemo remove: me]

I hope not!  Its a very non-obvious gotcher.  It doesn't scale.  If

         [processMemo recordThisProcess.
          self doStuff]
                ensure: [processMemo discountThisProcess]

the contagion has spread.

In any case, I think the issue is *significantly* more faceted than just
> saying "it's broken".
>

It may have facets but that doesn't mean that running unwinds in other than
the current process isn't badly broken, and isn't relatively easy to fix ;)



>
> Cheers,
>  - Andreas


best,
Eliot


>
>
>
>  and arranging that a process runs its own unwinds on
>> termination is straight-forward.  The VW code does a few other things,
>> but the way it transfers control from one process to another is easy to
>> do in Squeak.
>>
>>
>>    Cheers,
>>      - Andreas
>>
>>
>>      Here's the VisualWorks code:
>>
>>
>>        terminate
>>        "Terminate the receiver process, by sending the Process
>>        terminateSignal.
>>        Allow all unwind blocks to run, even if they are currently in
>>        progress."
>>
>>        "If the receiver is the active process then raise the
>>        terminateSignal
>>          which should be
>>        caught by the handler in Process class>>forBlock:priority:.  The
>>        handler
>>        will return,
>>        causing all unwind blocks to run, and then will invoke
>>        finalTerminate,
>>        actually terminating the receiver."
>>        Processor activeProcess == self ifTrue:
>>        [ [ Process terminateSignal raise ] on: Signal noHandlerSignal do:
>>        [ :ex |
>>        "Usually, this can only happen if a Process was terminated
>>        unnaturally.
>>        Try to recover gracefully."
>>        Process terminateSignal == ex unhandledException class
>>        ifTrue: [ ex return ]
>>        ifFalse: [ ex pass ].
>>        ].
>>        ^self finalTerminate
>>        ].
>>
>>        "If the receiver is not the active process and its
>>        suspendedContext is
>>        nil then
>>        it is already suspended."
>>        suspendedContext == nil ifTrue: [^self].
>>
>>        "Suspend the receiver, then place a block atop the receiver's
>>        stack to
>>        invoke this method as the active process, and resume the receiver."
>>        interruptProtect critical:
>>        [| savedContext interruptContext outerMostUnwind curr |
>>        myList == nil ifFalse: [self suspend].
>>        curr := suspendedContext.
>>        [curr == nil] whileFalse:
>>        [curr method isMarkedForUnwindInAction
>>        ifTrue: [outerMostUnwind := curr.
>>        curr := curr skipOverUnwindingBlocks].
>>        curr := curr findNextMarkedUpTo: nil].
>>        (outerMostUnwind ~~ nil and: [outerMostUnwind method
>>        numTempsOnly > 0])
>>        ifTrue:
>>        [outerMostUnwind localAt: outerMostUnwind method numArgs+1 put:
>>        true]
>>        ifFalse:
>>        [savedContext := suspendedContext.
>>        interruptContext := [self terminate] newContext.
>>        interruptContext sender: savedContext.
>>        suspendedContext := interruptContext].
>>        self resume]
>>
>>        So...
>>        - it only runs unwinds in the context of the process, pushing a
>>        block
>>        that will run terminate if other than the current process tries to
>>        terminate it
>>        - it tried to discover whether the attempt to terminate from
>> another
>>        process was made while unwind blocks are being run and marks any
>>        such
>>        block as having completed (i.e. it'll short-circuit an
>>        already-executing
>>        unwind block).
>>
>>
>>
>>        P.S. Here's the identification of an unwind in action:
>>
>>        BlockClosure>>valueAsUnwindBlockFrom: aContextOrNil
>>        "Unwind blocks are evaluated using this wrapper.
>>        This method is marked as special.  When the
>>        system searches for unwind blocks, it skips over
>>        all contexts between this context and the context
>>        passed in as an argument.  If the argument is
>>        nil, it skips over the sender of this context.
>>
>>        The purpose of this is that errors inside an
>>        unwind block should not evaluate that unwind
>>        block, and they should not circumvent the
>>        running of other unwind blocks lower on the
>>        stack."
>>
>>        <exception: #unwindInAction>
>>
>>        | shouldTerminate |
>>        "The first temporary variable in this method is treated specially
>> by
>>        Process>>terminate,
>>        which may set it to true. If that happens, terminate the process
>>        at the
>>        end of the unwind action.
>>        Normally we should be able to take for granted that
>>        shouldTerminate has
>>        nil as its initial
>>        value, but since this variable is modified by another process, it's
>>        possible that the other
>>        process could set the value to true when this process is waiting
>>        at the
>>        initial PC of the method.
>>        In that case, when this process resumes, it needs to make sure
>>        that it
>>        doesn't overwrite
>>        the true with a false.
>>
>>        On a purely theoretical level, there's still room for a race
>>        condition
>>        between testing for nil
>>        and assigning false to the variable, but in practise our execution
>>        technology doesn't allow
>>        for a process switch between the test and the assignment, so the
>>        only
>>        way it could be a
>>        problem in practise is if the method were being debugged or run
>>        by a VI
>>        level interpreter
>>        at the time that some other process wanted to terminate it. At this
>>        time, the risk of that kind
>>        of situation seems so much smaller thatn the risk of a process
>>        switch at
>>        the initial PC, that
>>        we're willing to fix only the initial PC case."
>>        shouldTerminate == nil ifTrue: [shouldTerminate := false].
>>        self value.
>>        shouldTerminate
>>        ifTrue:
>>        [Processor activeProcess terminate].
>>
>>        CompiledCode>>isMarkedForUnwindInAction
>>        "Answer true if marked for unwind in action."
>>
>>        ^false
>>
>>        MarkedMethod>>isMarkedForUnwindInAction
>>        "Answer true if method is marked for unwinding in action."
>>
>>        ^markType == #unwindInAction
>>        and I can spell practice ;)
>>
>>
>>        HTH
>>        Eliot
>>
>>
>>            Cheers,
>>              - Andreas
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20100303/45e70fa6/attachment.htm


More information about the Squeak-dev mailing list