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

Eliot Miranda eliot.miranda at gmail.com
Thu Mar 4 01:23:58 UTC 2010


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

> What do you guys think about this? It behaves no worse than the current
> implementation if it encountered a problematic ensure: block in #terminate
> but it does try to complete one when it finds it. I think that short of
> changing the overall terminate semantics this is pretty close to as good as
> it gets. The test illustrates the problem.
>
> Eliot - could you check that my usage of findContextSuchThat / closures /
> runUntilErrorOrReturn: looks reasonable? I'm still relatively new to closure
> land in these areas.
>

That's fine.  But running unwinds in a different process is, I think,
unwise.  Process identity is important and unwinds should be run in the
context of the process itself.  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/97e6d5ea/attachment.htm


More information about the Squeak-dev mailing list