#ensure: issues (was: Re: [Pharo-project] Pharo by Example vol 2: new chapter available)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
51 messages Options
123
Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Levente Uzonyi-2
On Thu, 4 Mar 2010, Igor Stasenko wrote:

> On 4 March 2010 01:56, Nicolas Cellier
> <[hidden email]> wrote:
>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>
>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>>>>>
>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>>>>>
>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>>>>>
>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>>>>>
>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>>>>>> evaluated no matter what happens.' ?
>>>>>>>> But now you saying that it may not be executed in some conditions
>>>>>>>> (when user pressing abandon button, causing process to be terminated).
>>>>>>>
>>>>>>> It's simple: don't terminate process X from another process if process
>>>>>>> X
>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>>>>>>> terminate it, make sure that the execution of the block will continue
>>>>>>> somehow (I don't care how).
>>>>>>
>>>>>> You're missing Igors point which is that in his example the halt /
>>>>>> Transcript *was* in the ensure block and as a result you're
>>>>>> contradicting
>>>>>> yourself here. Let's go back to Igor's example:
>>>>>>
>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>>>>>
>>>>>> The halt is inside the ensure block. If you terminate the process from
>>>>>> the
>>>>>> debugger, it would be logical from your statement that the Transcript
>>>>>> message would be executed - after all it's " executing a termiation
>>>>>> block
>>>>>> (aka #ensure: block)" and so it can't be terminated by your reasoning.
>>>>>> However, when Igor was pointing this out you replied with "I didn't say
>>>>>> that. I said evaluate it the same way as normal code." which is
>>>>>> inconsistent
>>>>>> with the other statement.
>>>>>
>>>>> That shows my lack of knowledge about how the debugger works.
>>>>>
>>>>>>
>>>>>>> I think every user of #ensure: expects that the termination blocks are
>>>>>>> executed even if the process which is executing the receiver of
>>>>>>> #ensure:
>>>>>>> is terminated. And it actually happens in all but this case.
>>>>>>
>>>>>> The question of terminating processes is always tricky. I don't think
>>>>>> that
>>>>>> your proposal would actually work in practice - it could easily result
>>>>>> in
>>>>>> processes that cannot be terminated due to a simple bug in an ensure
>>>>>> block.
>>>>>> Personally, I'd rather say that the more useful behavior would be
>>>>>> something
>>>>>> along the lines of saying that process termination either skips the
>>>>>> current
>>>>>> ensure block (assuming there's a bug and it should get the heck out of
>>>>>> it
>>>>>> but try to evaluate the remaining ones) or that there need to be two
>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to be
>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all the
>>>>>> ensure blocks.
>>>>>
>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>>>>> unexpected
>>>>> things like this.
>>>>> If you read the comment of Process >> #terminate, you may assume that
>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>>>>> #terminate, but that's not true.
>>>>>
>>>>> "Stop the process that the receiver represents forever.  Unwind to
>>>>> execute
>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>>>>>
>>>>>
>>>>> Levente
>>>>>
>>>>
>>>> The only way I see to solve your problem would be to execute the
>>>> unwind block in another process...
>>>> Quite technical and costly !
>>>
>>> It's our problem. Just look at the senders of #ensure: and imagine what will
>>> happen if the termination block is not evaluated.
>>> I think there's another way (though it might be my lack of knowledge again).
>>> After suspending the process which is about to be terminated we can check if
>>> it's executing a termination block. It it's not, we are safe to continue the
>>> termination, otherwise we can do something else which ensures that the
>>> termination block is evaluated.
>>>
>>
>> Maybe...
>> Unfortunately, you did not tell how you will distinguish well behaved
>> unwind-blocks from Igor's example...
>>
>
> Yes, then what prevents me from writing:
>
> [ [ ] ensure: [ self doCrazyThings ] ] fork.
What prevents you from writing: Object superclass: Object. ?
Nothing, but you don't do that, do you?

>
> and now given assumption that any code which placed inside ensure
> block should always run to the end, without chances being terminated,
> will have ill side effects.

You can terminate it (maybe not the usual way).

> The #ensure: means, that interpreter will have a chance to enter that
> block eventually, but it should not mean that it will keep running the
> code there until normal or non-local return from that block.

Then it doesn't ensure anything at all, so it should be called
#tryToEvaluateThisToo:.


Levente

> Othewise, you losing a way to terminate unwanted, ill-behaved process.
>
>> Nicolas
>>
>>>
>>> Levente
>>>
>>>>
>>>> Nicolas
>>>>
>>>>>>
>>>>>> Cheers,
>>>>>>  - Andreas
>>>>>>
>
> --
> Best regards,
> Igor Stasenko AKA sig.
>
>

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Nicolas Cellier
In reply to this post by Levente Uzonyi-2
2010/3/4 Levente Uzonyi <[hidden email]>:

> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>
>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>
>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>
>>>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>>>
>>>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>>>
>>>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>>>>>>>
>>>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>>>>>>>
>>>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>>>>>>>
>>>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>>>>>>>
>>>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>>>>>>>> evaluated no matter what happens.' ?
>>>>>>>>>> But now you saying that it may not be executed in some conditions
>>>>>>>>>> (when user pressing abandon button, causing process to be
>>>>>>>>>> terminated).
>>>>>>>>>
>>>>>>>>> It's simple: don't terminate process X from another process if
>>>>>>>>> process
>>>>>>>>> X
>>>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>>>>>>>>> terminate it, make sure that the execution of the block will
>>>>>>>>> continue
>>>>>>>>> somehow (I don't care how).
>>>>>>>>
>>>>>>>> You're missing Igors point which is that in his example the halt /
>>>>>>>> Transcript *was* in the ensure block and as a result you're
>>>>>>>> contradicting
>>>>>>>> yourself here. Let's go back to Igor's example:
>>>>>>>>
>>>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>>>>>>>
>>>>>>>> The halt is inside the ensure block. If you terminate the process
>>>>>>>> from
>>>>>>>> the
>>>>>>>> debugger, it would be logical from your statement that the
>>>>>>>> Transcript
>>>>>>>> message would be executed - after all it's " executing a termiation
>>>>>>>> block
>>>>>>>> (aka #ensure: block)" and so it can't be terminated by your
>>>>>>>> reasoning.
>>>>>>>> However, when Igor was pointing this out you replied with "I didn't
>>>>>>>> say
>>>>>>>> that. I said evaluate it the same way as normal code." which is
>>>>>>>> inconsistent
>>>>>>>> with the other statement.
>>>>>>>
>>>>>>> That shows my lack of knowledge about how the debugger works.
>>>>>>>
>>>>>>>>
>>>>>>>>> I think every user of #ensure: expects that the termination blocks
>>>>>>>>> are
>>>>>>>>> executed even if the process which is executing the receiver of
>>>>>>>>> #ensure:
>>>>>>>>> is terminated. And it actually happens in all but this case.
>>>>>>>>
>>>>>>>> The question of terminating processes is always tricky. I don't
>>>>>>>> think
>>>>>>>> that
>>>>>>>> your proposal would actually work in practice - it could easily
>>>>>>>> result
>>>>>>>> in
>>>>>>>> processes that cannot be terminated due to a simple bug in an ensure
>>>>>>>> block.
>>>>>>>> Personally, I'd rather say that the more useful behavior would be
>>>>>>>> something
>>>>>>>> along the lines of saying that process termination either skips the
>>>>>>>> current
>>>>>>>> ensure block (assuming there's a bug and it should get the heck out
>>>>>>>> of
>>>>>>>> it
>>>>>>>> but try to evaluate the remaining ones) or that there need to be two
>>>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to
>>>>>>>> be
>>>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all
>>>>>>>> the
>>>>>>>> ensure blocks.
>>>>>>>
>>>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>>>>>>> unexpected
>>>>>>> things like this.
>>>>>>> If you read the comment of Process >> #terminate, you may assume that
>>>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>>>>>>> #terminate, but that's not true.
>>>>>>>
>>>>>>> "Stop the process that the receiver represents forever.  Unwind to
>>>>>>> execute
>>>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>>>>>>>
>>>>>>>
>>>>>>> Levente
>>>>>>>
>>>>>>
>>>>>> The only way I see to solve your problem would be to execute the
>>>>>> unwind block in another process...
>>>>>> Quite technical and costly !
>>>>>
>>>>> It's our problem. Just look at the senders of #ensure: and imagine what
>>>>> will
>>>>> happen if the termination block is not evaluated.
>>>>> I think there's another way (though it might be my lack of knowledge
>>>>> again).
>>>>> After suspending the process which is about to be terminated we can
>>>>> check
>>>>> if
>>>>> it's executing a termination block. It it's not, we are safe to
>>>>> continue
>>>>> the
>>>>> termination, otherwise we can do something else which ensures that the
>>>>> termination block is evaluated.
>>>>>
>>>>
>>>> Maybe...
>>>> Unfortunately, you did not tell how you will distinguish well behaved
>>>> unwind-blocks from Igor's example...
>>>
>>> I'd give the responsibility to the developer to write termination blocks
>>> which are safe to evaluate. So I assume that all such blocks behave well.
>>> If
>>> something goes wrong you can kill the process with a more aggressive
>>> method (as Andreas suggested), but you lose the guarantee that the
>>> current
>>> termination block will be evaluated.
>>>
>>>
>>> Levente
>>>
>>
>> I would tend to focus first on the senders of terminate since
>> terminating is unsafe...
>
> The comment suggest to me that it's safe to use it, because it will evaluate
> the termination blocks.
>

Yes, my question was wrong: is the trunk concerned with the problem ?
But it does not answer your question: how my own application could
terminate reliably.

Nicolas

>
> Levente
>
>> How many of them would deserve to be transformed in a terminateRequest ?
>>
>> Nicolas
>>
>>>>
>>>> Nicolas
>>>>
>>>>>
>>>>> Levente
>>>>>
>>>>>>
>>>>>> Nicolas
>>>>>>
>>>>>>>>
>>>>>>>> Cheers,
>>>>>>>>  - Andreas
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>
>>>
>>>
>>>
>>>
>>
>
>
>
>

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Andreas.Raab
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.

Cheers,
   - Andreas




ProcessTerminateBug-testTerminationDuringUnwind.st (628 bytes) Download Attachment
Process-terminate.st (1K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Igor Stasenko
In reply to this post by Eliot Miranda-2
On 4 March 2010 02:34, Eliot Miranda <[hidden email]> wrote:

>
>
> 2010/3/3 Andreas Raab <[hidden email]>
>>
>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>
>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>
>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>> evaluated no matter what happens.' ?
>>>> But now you saying that it may not be executed in some conditions
>>>> (when user pressing abandon button, causing process to be terminated).
>>>
>>> It's simple: don't terminate process X from another process if process X
>>> is executing a termiation block (aka #ensure: block). Or if you
>>> terminate it, make sure that the execution of the block will continue
>>> somehow (I don't care how).
>>
>> You're missing Igors point which is that in his example the halt /
>> Transcript *was* in the ensure block and as a result you're contradicting
>> yourself here. Let's go back to Igor's example:
>>
>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>
>> The halt is inside the ensure block. If you terminate the process from the
>> debugger, it would be logical from your statement that the Transcript
>> message would be executed - after all it's " executing a termiation block
>> (aka #ensure: block)" and so it can't be terminated by your reasoning.
>> However, when Igor was pointing this out you replied with "I didn't say
>> that. I said evaluate it the same way as normal code." which is inconsistent
>> with the other statement.
>>
>>> I think every user of #ensure: expects that the termination blocks are
>>> executed even if the process which is executing the receiver of #ensure:
>>> is terminated. And it actually happens in all but this case.
>>
>> The question of terminating processes is always tricky. I don't think that
>> your proposal would actually work in practice - it could easily result in
>> processes that cannot be terminated due to a simple bug in an ensure block.
>> Personally, I'd rather say that the more useful behavior would be something
>> along the lines of saying that process termination either skips the current
>> ensure block (assuming there's a bug and it should get the heck out of it
>> but try to evaluate the remaining ones) or that there need to be two
>> terminations - one that is 'soft' and won't allow ensure blocks to be
>> skipped and one that is 'hard' (kill -9 hard) and just ignores all the
>> ensure blocks.
>
> That's what we did in VisualWorks.  Normal terminate is as you say.  It
> tries to run unwinds until an error, and then if necessary one wields
> terminateWithExtremePrejudice and kills the thing stone dead.

Its easy to follow that road, since its cheap and looks like logical
choice: add another layer on top of existing one,
to ensure correct (or stable) behavior.
But i would say, that given solution is as good as it costs :)
The fact is, that to terminate the process is "safe" way, you have to
run the code. And once you running a code, it means that someone may
want to terminate it. So, there are no guarantees that nobody else
wouldn't want to terminate your code which 'terminates' some process.
So, its and endless loop.. Lets write a code to ensure that
termination code runs to an end, termination termination code runs to
end and so on...
A solution lies on the surface: do not run any code on termination. Period.
Then, consequently, change the concept: do not expect that any bit of
code will have chances to run, and hence, remove the #ensure: from
use. Too bad, it is too late to go that way, since we having #ensure:
blocks used everywhere.

The main use of #ensure: block is to keep things in a consistent
state, regardless of what is happens inside receiver block:
[ self goneWild ] ensure: [ self becomeSaneAgain ]

Not sure for all, but i think for most of the cases, one could use a
transaction-like approach:

| copy |
copy := self copy.
copy goneWild.
copy isEveryThingOK ifTrue: [ self become: copy ] ifFalse: [ "do nothing" ]

the concept is simple: make a backups before doing risky things. In
this way, you always having an easy way to get back to consistent
state, despite what was the outcome of intended operation.

But of course, i realizing that its too late to change that :)

>>
>> Cheers,
>>  - Andreas


--
Best regards,
Igor Stasenko AKA sig.

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Eliot Miranda-2
In reply to this post by Andreas.Raab


On Wed, Mar 3, 2010 at 5:07 PM, Andreas Raab <[hidden email]> 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







Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Igor Stasenko
In reply to this post by Levente Uzonyi-2
2010/3/4 Levente Uzonyi <[hidden email]>:

> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>
>> On 4 March 2010 01:56, Nicolas Cellier
>> <[hidden email]> wrote:
>>>
>>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>>
>>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>>
>>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>>>>>>
>>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>>>>>>
>>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>>>>>>
>>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>>>>>>
>>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>>>>>>> evaluated no matter what happens.' ?
>>>>>>>>> But now you saying that it may not be executed in some conditions
>>>>>>>>> (when user pressing abandon button, causing process to be
>>>>>>>>> terminated).
>>>>>>>>
>>>>>>>> It's simple: don't terminate process X from another process if
>>>>>>>> process
>>>>>>>> X
>>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>>>>>>>> terminate it, make sure that the execution of the block will
>>>>>>>> continue
>>>>>>>> somehow (I don't care how).
>>>>>>>
>>>>>>> You're missing Igors point which is that in his example the halt /
>>>>>>> Transcript *was* in the ensure block and as a result you're
>>>>>>> contradicting
>>>>>>> yourself here. Let's go back to Igor's example:
>>>>>>>
>>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>>>>>>
>>>>>>> The halt is inside the ensure block. If you terminate the process
>>>>>>> from
>>>>>>> the
>>>>>>> debugger, it would be logical from your statement that the Transcript
>>>>>>> message would be executed - after all it's " executing a termiation
>>>>>>> block
>>>>>>> (aka #ensure: block)" and so it can't be terminated by your
>>>>>>> reasoning.
>>>>>>> However, when Igor was pointing this out you replied with "I didn't
>>>>>>> say
>>>>>>> that. I said evaluate it the same way as normal code." which is
>>>>>>> inconsistent
>>>>>>> with the other statement.
>>>>>>
>>>>>> That shows my lack of knowledge about how the debugger works.
>>>>>>
>>>>>>>
>>>>>>>> I think every user of #ensure: expects that the termination blocks
>>>>>>>> are
>>>>>>>> executed even if the process which is executing the receiver of
>>>>>>>> #ensure:
>>>>>>>> is terminated. And it actually happens in all but this case.
>>>>>>>
>>>>>>> The question of terminating processes is always tricky. I don't think
>>>>>>> that
>>>>>>> your proposal would actually work in practice - it could easily
>>>>>>> result
>>>>>>> in
>>>>>>> processes that cannot be terminated due to a simple bug in an ensure
>>>>>>> block.
>>>>>>> Personally, I'd rather say that the more useful behavior would be
>>>>>>> something
>>>>>>> along the lines of saying that process termination either skips the
>>>>>>> current
>>>>>>> ensure block (assuming there's a bug and it should get the heck out
>>>>>>> of
>>>>>>> it
>>>>>>> but try to evaluate the remaining ones) or that there need to be two
>>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to be
>>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all
>>>>>>> the
>>>>>>> ensure blocks.
>>>>>>
>>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>>>>>> unexpected
>>>>>> things like this.
>>>>>> If you read the comment of Process >> #terminate, you may assume that
>>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>>>>>> #terminate, but that's not true.
>>>>>>
>>>>>> "Stop the process that the receiver represents forever.  Unwind to
>>>>>> execute
>>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>>>>>>
>>>>>>
>>>>>> Levente
>>>>>>
>>>>>
>>>>> The only way I see to solve your problem would be to execute the
>>>>> unwind block in another process...
>>>>> Quite technical and costly !
>>>>
>>>> It's our problem. Just look at the senders of #ensure: and imagine what
>>>> will
>>>> happen if the termination block is not evaluated.
>>>> I think there's another way (though it might be my lack of knowledge
>>>> again).
>>>> After suspending the process which is about to be terminated we can
>>>> check if
>>>> it's executing a termination block. It it's not, we are safe to continue
>>>> the
>>>> termination, otherwise we can do something else which ensures that the
>>>> termination block is evaluated.
>>>>
>>>
>>> Maybe...
>>> Unfortunately, you did not tell how you will distinguish well behaved
>>> unwind-blocks from Igor's example...
>>>
>>
>> Yes, then what prevents me from writing:
>>
>> [ [ ] ensure: [ self doCrazyThings ] ] fork.
>
> What prevents you from writing: Object superclass: Object. ?
> Nothing, but you don't do that, do you?
>
So, why at all, you care about using #ensure: then? If you putting
everything up to the hands of developer,
then obviously you won't need to use this message, because you always
know that you're running a reliable code which
will always let you to run your things in the end. :)

>>
>> and now given assumption that any code which placed inside ensure
>> block should always run to the end, without chances being terminated,
>> will have ill side effects.
>
> You can terminate it (maybe not the usual way).
>
that's the point. Why do we need two (or more) ways to terminate a process?

>> The #ensure: means, that interpreter will have a chance to enter that
>> block eventually, but it should not mean that it will keep running the
>> code there until normal or non-local return from that block.
>
> Then it doesn't ensure anything at all, so it should be called
> #tryToEvaluateThisToo:.
>
in fact, this is the actual behavior :)
If i press the power button on your PC, or plug out the power cord,
any #ensure: blocks will have no chances to run either way.
So, relying on something, which is not reliable is a fallacy :)

>
> Levente
>
>> Othewise, you losing a way to terminate unwanted, ill-behaved process.
>>
>>> Nicolas
>>>
>>>>
>>>> Levente
>>>>
>>>>>
>>>>> Nicolas
>>>>>
>>>>>>>
>>>>>>> Cheers,
>>>>>>>  - Andreas
>>>>>>>
>>
>> --
>> Best regards,
>> Igor Stasenko AKA sig.
>>
>
>
>
>



--
Best regards,
Igor Stasenko AKA sig.

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Gary Chambers-4
In reply to this post by Nicolas Cellier
Certainly a tangled web... of course we want all ensures to happen,
like, I think, any use of terminate to happen too. A conflict of
interest, especially in the case of errors within ensures...

No easy answer I think. I favour having terminate complete if errors in
ensures, otherwise that ensures complete before eventual termination...

Regards, Gary


On Thu, 2010-03-04 at 00:56 +0100, Nicolas Cellier wrote:

> 2010/3/4 Levente Uzonyi <[hidden email]>:
> > On Thu, 4 Mar 2010, Nicolas Cellier wrote:
> >
> >> 2010/3/3 Levente Uzonyi <[hidden email]>:
> >>>
> >>> On Wed, 3 Mar 2010, Andreas Raab wrote:
> >>>
> >>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
> >>>>>
> >>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
> >>>>>>
> >>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
> >>>>>> evaluated no matter what happens.' ?
> >>>>>> But now you saying that it may not be executed in some conditions
> >>>>>> (when user pressing abandon button, causing process to be terminated).
> >>>>>
> >>>>> It's simple: don't terminate process X from another process if process
> >>>>> X
> >>>>> is executing a termiation block (aka #ensure: block). Or if you
> >>>>> terminate it, make sure that the execution of the block will continue
> >>>>> somehow (I don't care how).
> >>>>
> >>>> You're missing Igors point which is that in his example the halt /
> >>>> Transcript *was* in the ensure block and as a result you're
> >>>> contradicting
> >>>> yourself here. Let's go back to Igor's example:
> >>>>
> >>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
> >>>>
> >>>> The halt is inside the ensure block. If you terminate the process from
> >>>> the
> >>>> debugger, it would be logical from your statement that the Transcript
> >>>> message would be executed - after all it's " executing a termiation
> >>>> block
> >>>> (aka #ensure: block)" and so it can't be terminated by your reasoning.
> >>>> However, when Igor was pointing this out you replied with "I didn't say
> >>>> that. I said evaluate it the same way as normal code." which is
> >>>> inconsistent
> >>>> with the other statement.
> >>>
> >>> That shows my lack of knowledge about how the debugger works.
> >>>
> >>>>
> >>>>> I think every user of #ensure: expects that the termination blocks are
> >>>>> executed even if the process which is executing the receiver of
> >>>>> #ensure:
> >>>>> is terminated. And it actually happens in all but this case.
> >>>>
> >>>> The question of terminating processes is always tricky. I don't think
> >>>> that
> >>>> your proposal would actually work in practice - it could easily result
> >>>> in
> >>>> processes that cannot be terminated due to a simple bug in an ensure
> >>>> block.
> >>>> Personally, I'd rather say that the more useful behavior would be
> >>>> something
> >>>> along the lines of saying that process termination either skips the
> >>>> current
> >>>> ensure block (assuming there's a bug and it should get the heck out of
> >>>> it
> >>>> but try to evaluate the remaining ones) or that there need to be two
> >>>> terminations - one that is 'soft' and won't allow ensure blocks to be
> >>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all the
> >>>> ensure blocks.
> >>>
> >>> I'm only saying that normal usage (aka #terminate) shouldn't do
> >>> unexpected
> >>> things like this.
> >>> If you read the comment of Process >> #terminate, you may assume that
> >>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
> >>> #terminate, but that's not true.
> >>>
> >>> "Stop the process that the receiver represents forever.  Unwind to
> >>> execute
> >>> pending ensure:/ifCurtailed: blocks before terminating."
> >>>
> >>>
> >>> Levente
> >>>
> >>
> >> The only way I see to solve your problem would be to execute the
> >> unwind block in another process...
> >> Quite technical and costly !
> >
> > It's our problem. Just look at the senders of #ensure: and imagine what will
> > happen if the termination block is not evaluated.
> > I think there's another way (though it might be my lack of knowledge again).
> > After suspending the process which is about to be terminated we can check if
> > it's executing a termination block. It it's not, we are safe to continue the
> > termination, otherwise we can do something else which ensures that the
> > termination block is evaluated.
> >
>
> Maybe...
> Unfortunately, you did not tell how you will distinguish well behaved
> unwind-blocks from Igor's example...
>
> Nicolas
>
> >
> > Levente
> >
> >>
> >> Nicolas
> >>
> >>>>
> >>>> Cheers,
> >>>>  - Andreas
> >>>>
> >>>>
> >>>
> >>>
> >>
> >
> >
> >
> >
>



Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Andreas.Raab
In reply to this post by Eliot Miranda-2
On 3/3/2010 5:23 PM, Eliot Miranda wrote:

>
>
> On Wed, Mar 3, 2010 at 5:07 PM, Andreas Raab <[hidden email]
> <mailto:[hidden email]>> 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.

I said "short of changing the overall termination semantics" :-)

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
>
>
>
>
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Gary Chambers-4
In reply to this post by Igor Stasenko
That's the dilemma. Ensures are there to make sure things happen (to
avoid inconsistent state, release of OS resources etc.). Also terminates
are there to stop things...

Perhaps we should discourage terminates as part of "normal" processing
in code and recommend "proper ending"... usually terminates are used for
"endless" processes. Maybe those processes should have a deliberate end
condition...

Terminate is still valid under direct user action, of course, along with
the understanding that ensure may not actually happen...

Just more thoughts...

Regards, Gary.

On Thu, 2010-03-04 at 03:26 +0200, Igor Stasenko wrote:

> 2010/3/4 Levente Uzonyi <[hidden email]>:
> > On Thu, 4 Mar 2010, Igor Stasenko wrote:
> >
> >> On 4 March 2010 01:56, Nicolas Cellier
> >> <[hidden email]> wrote:
> >>>
> >>> 2010/3/4 Levente Uzonyi <[hidden email]>:
> >>>>
> >>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
> >>>>
> >>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
> >>>>>>
> >>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
> >>>>>>
> >>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
> >>>>>>>>
> >>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
> >>>>>>>>>
> >>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
> >>>>>>>>> evaluated no matter what happens.' ?
> >>>>>>>>> But now you saying that it may not be executed in some conditions
> >>>>>>>>> (when user pressing abandon button, causing process to be
> >>>>>>>>> terminated).
> >>>>>>>>
> >>>>>>>> It's simple: don't terminate process X from another process if
> >>>>>>>> process
> >>>>>>>> X
> >>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
> >>>>>>>> terminate it, make sure that the execution of the block will
> >>>>>>>> continue
> >>>>>>>> somehow (I don't care how).
> >>>>>>>
> >>>>>>> You're missing Igors point which is that in his example the halt /
> >>>>>>> Transcript *was* in the ensure block and as a result you're
> >>>>>>> contradicting
> >>>>>>> yourself here. Let's go back to Igor's example:
> >>>>>>>
> >>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
> >>>>>>>
> >>>>>>> The halt is inside the ensure block. If you terminate the process
> >>>>>>> from
> >>>>>>> the
> >>>>>>> debugger, it would be logical from your statement that the Transcript
> >>>>>>> message would be executed - after all it's " executing a termiation
> >>>>>>> block
> >>>>>>> (aka #ensure: block)" and so it can't be terminated by your
> >>>>>>> reasoning.
> >>>>>>> However, when Igor was pointing this out you replied with "I didn't
> >>>>>>> say
> >>>>>>> that. I said evaluate it the same way as normal code." which is
> >>>>>>> inconsistent
> >>>>>>> with the other statement.
> >>>>>>
> >>>>>> That shows my lack of knowledge about how the debugger works.
> >>>>>>
> >>>>>>>
> >>>>>>>> I think every user of #ensure: expects that the termination blocks
> >>>>>>>> are
> >>>>>>>> executed even if the process which is executing the receiver of
> >>>>>>>> #ensure:
> >>>>>>>> is terminated. And it actually happens in all but this case.
> >>>>>>>
> >>>>>>> The question of terminating processes is always tricky. I don't think
> >>>>>>> that
> >>>>>>> your proposal would actually work in practice - it could easily
> >>>>>>> result
> >>>>>>> in
> >>>>>>> processes that cannot be terminated due to a simple bug in an ensure
> >>>>>>> block.
> >>>>>>> Personally, I'd rather say that the more useful behavior would be
> >>>>>>> something
> >>>>>>> along the lines of saying that process termination either skips the
> >>>>>>> current
> >>>>>>> ensure block (assuming there's a bug and it should get the heck out
> >>>>>>> of
> >>>>>>> it
> >>>>>>> but try to evaluate the remaining ones) or that there need to be two
> >>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to be
> >>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all
> >>>>>>> the
> >>>>>>> ensure blocks.
> >>>>>>
> >>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
> >>>>>> unexpected
> >>>>>> things like this.
> >>>>>> If you read the comment of Process >> #terminate, you may assume that
> >>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
> >>>>>> #terminate, but that's not true.
> >>>>>>
> >>>>>> "Stop the process that the receiver represents forever.  Unwind to
> >>>>>> execute
> >>>>>> pending ensure:/ifCurtailed: blocks before terminating."
> >>>>>>
> >>>>>>
> >>>>>> Levente
> >>>>>>
> >>>>>
> >>>>> The only way I see to solve your problem would be to execute the
> >>>>> unwind block in another process...
> >>>>> Quite technical and costly !
> >>>>
> >>>> It's our problem. Just look at the senders of #ensure: and imagine what
> >>>> will
> >>>> happen if the termination block is not evaluated.
> >>>> I think there's another way (though it might be my lack of knowledge
> >>>> again).
> >>>> After suspending the process which is about to be terminated we can
> >>>> check if
> >>>> it's executing a termination block. It it's not, we are safe to continue
> >>>> the
> >>>> termination, otherwise we can do something else which ensures that the
> >>>> termination block is evaluated.
> >>>>
> >>>
> >>> Maybe...
> >>> Unfortunately, you did not tell how you will distinguish well behaved
> >>> unwind-blocks from Igor's example...
> >>>
> >>
> >> Yes, then what prevents me from writing:
> >>
> >> [ [ ] ensure: [ self doCrazyThings ] ] fork.
> >
> > What prevents you from writing: Object superclass: Object. ?
> > Nothing, but you don't do that, do you?
> >
> So, why at all, you care about using #ensure: then? If you putting
> everything up to the hands of developer,
> then obviously you won't need to use this message, because you always
> know that you're running a reliable code which
> will always let you to run your things in the end. :)
>
> >>
> >> and now given assumption that any code which placed inside ensure
> >> block should always run to the end, without chances being terminated,
> >> will have ill side effects.
> >
> > You can terminate it (maybe not the usual way).
> >
> that's the point. Why do we need two (or more) ways to terminate a process?
>
> >> The #ensure: means, that interpreter will have a chance to enter that
> >> block eventually, but it should not mean that it will keep running the
> >> code there until normal or non-local return from that block.
> >
> > Then it doesn't ensure anything at all, so it should be called
> > #tryToEvaluateThisToo:.
> >
> in fact, this is the actual behavior :)
> If i press the power button on your PC, or plug out the power cord,
> any #ensure: blocks will have no chances to run either way.
> So, relying on something, which is not reliable is a fallacy :)
>
> >
> > Levente
> >
> >> Othewise, you losing a way to terminate unwanted, ill-behaved process.
> >>
> >>> Nicolas
> >>>
> >>>>
> >>>> Levente
> >>>>
> >>>>>
> >>>>> Nicolas
> >>>>>
> >>>>>>>
> >>>>>>> Cheers,
> >>>>>>>  - Andreas
> >>>>>>>
> >>
> >> --
> >> Best regards,
> >> Igor Stasenko AKA sig.
> >>
> >
> >
> >
> >
>
>
>



Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Levente Uzonyi-2
In reply to this post by Igor Stasenko
On Thu, 4 Mar 2010, Igor Stasenko wrote:

> 2010/3/4 Levente Uzonyi <[hidden email]>:
>> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>>
>>> On 4 March 2010 01:56, Nicolas Cellier
>>> <[hidden email]> wrote:
>>>>
>>>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>>>
>>>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>>>
>>>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>>>>>>>
>>>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>>>>>>>
>>>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>>>>>>>
>>>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>>>>>>>
>>>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>>>>>>>> evaluated no matter what happens.' ?
>>>>>>>>>> But now you saying that it may not be executed in some conditions
>>>>>>>>>> (when user pressing abandon button, causing process to be
>>>>>>>>>> terminated).
>>>>>>>>>
>>>>>>>>> It's simple: don't terminate process X from another process if
>>>>>>>>> process
>>>>>>>>> X
>>>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>>>>>>>>> terminate it, make sure that the execution of the block will
>>>>>>>>> continue
>>>>>>>>> somehow (I don't care how).
>>>>>>>>
>>>>>>>> You're missing Igors point which is that in his example the halt /
>>>>>>>> Transcript *was* in the ensure block and as a result you're
>>>>>>>> contradicting
>>>>>>>> yourself here. Let's go back to Igor's example:
>>>>>>>>
>>>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>>>>>>>
>>>>>>>> The halt is inside the ensure block. If you terminate the process
>>>>>>>> from
>>>>>>>> the
>>>>>>>> debugger, it would be logical from your statement that the Transcript
>>>>>>>> message would be executed - after all it's " executing a termiation
>>>>>>>> block
>>>>>>>> (aka #ensure: block)" and so it can't be terminated by your
>>>>>>>> reasoning.
>>>>>>>> However, when Igor was pointing this out you replied with "I didn't
>>>>>>>> say
>>>>>>>> that. I said evaluate it the same way as normal code." which is
>>>>>>>> inconsistent
>>>>>>>> with the other statement.
>>>>>>>
>>>>>>> That shows my lack of knowledge about how the debugger works.
>>>>>>>
>>>>>>>>
>>>>>>>>> I think every user of #ensure: expects that the termination blocks
>>>>>>>>> are
>>>>>>>>> executed even if the process which is executing the receiver of
>>>>>>>>> #ensure:
>>>>>>>>> is terminated. And it actually happens in all but this case.
>>>>>>>>
>>>>>>>> The question of terminating processes is always tricky. I don't think
>>>>>>>> that
>>>>>>>> your proposal would actually work in practice - it could easily
>>>>>>>> result
>>>>>>>> in
>>>>>>>> processes that cannot be terminated due to a simple bug in an ensure
>>>>>>>> block.
>>>>>>>> Personally, I'd rather say that the more useful behavior would be
>>>>>>>> something
>>>>>>>> along the lines of saying that process termination either skips the
>>>>>>>> current
>>>>>>>> ensure block (assuming there's a bug and it should get the heck out
>>>>>>>> of
>>>>>>>> it
>>>>>>>> but try to evaluate the remaining ones) or that there need to be two
>>>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to be
>>>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all
>>>>>>>> the
>>>>>>>> ensure blocks.
>>>>>>>
>>>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>>>>>>> unexpected
>>>>>>> things like this.
>>>>>>> If you read the comment of Process >> #terminate, you may assume that
>>>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>>>>>>> #terminate, but that's not true.
>>>>>>>
>>>>>>> "Stop the process that the receiver represents forever.  Unwind to
>>>>>>> execute
>>>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>>>>>>>
>>>>>>>
>>>>>>> Levente
>>>>>>>
>>>>>>
>>>>>> The only way I see to solve your problem would be to execute the
>>>>>> unwind block in another process...
>>>>>> Quite technical and costly !
>>>>>
>>>>> It's our problem. Just look at the senders of #ensure: and imagine what
>>>>> will
>>>>> happen if the termination block is not evaluated.
>>>>> I think there's another way (though it might be my lack of knowledge
>>>>> again).
>>>>> After suspending the process which is about to be terminated we can
>>>>> check if
>>>>> it's executing a termination block. It it's not, we are safe to continue
>>>>> the
>>>>> termination, otherwise we can do something else which ensures that the
>>>>> termination block is evaluated.
>>>>>
>>>>
>>>> Maybe...
>>>> Unfortunately, you did not tell how you will distinguish well behaved
>>>> unwind-blocks from Igor's example...
>>>>
>>>
>>> Yes, then what prevents me from writing:
>>>
>>> [ [ ] ensure: [ self doCrazyThings ] ] fork.
>>
>> What prevents you from writing: Object superclass: Object. ?
>> Nothing, but you don't do that, do you?
>>
> So, why at all, you care about using #ensure: then? If you putting
> everything up to the hands of developer,
> then obviously you won't need to use this message, because you always
> know that you're running a reliable code which
> will always let you to run your things in the end. :)
>
>>>
>>> and now given assumption that any code which placed inside ensure
>>> block should always run to the end, without chances being terminated,
>>> will have ill side effects.
>>
>> You can terminate it (maybe not the usual way).
>>
> that's the point. Why do we need two (or more) ways to terminate a process?
>
>>> The #ensure: means, that interpreter will have a chance to enter that
>>> block eventually, but it should not mean that it will keep running the
>>> code there until normal or non-local return from that block.
>>
>> Then it doesn't ensure anything at all, so it should be called
>> #tryToEvaluateThisToo:.
>>
> in fact, this is the actual behavior :)
> If i press the power button on your PC, or plug out the power cord,
> any #ensure: blocks will have no chances to run either way.
> So, relying on something, which is not reliable is a fallacy :)
Here's a simple example (replace file with any external resource):
My process opened a file, a termination block will close it if it's
evaluated. If I send #terminate to the process I expect my file to be
closed, so I won't leak a file descriptor.
If the file can't be closed (aka the termination block raised an
error) then there's a serious problem. It doesn't really matter what
happens then.

But I don't have to try to convince you anymore, because Andreas is about
to solve the issue.


Levente

>
>>
>> Levente
>>
>>> Othewise, you losing a way to terminate unwanted, ill-behaved process.
>>>
>>>> Nicolas
>>>>
>>>>>
>>>>> Levente
>>>>>
>>>>>>
>>>>>> Nicolas
>>>>>>
>>>>>>>>
>>>>>>>> Cheers,
>>>>>>>>  - Andreas
>>>>>>>>
>>>
>>> --
>>> Best regards,
>>> Igor Stasenko AKA sig.
>>>
>>
>>
>>
>>
>
>
>
> --
> Best regards,
> Igor Stasenko AKA sig.
>
>

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Eliot Miranda-2
In reply to this post by Gary Chambers-4


On Wed, Mar 3, 2010 at 5:30 PM, Gary Chambers <[hidden email]> wrote:
Certainly a tangled web... of course we want all ensures to happen,
like, I think, any use of terminate to happen too. A conflict of
interest, especially in the case of errors within ensures...

No easy answer I think. I favour having terminate complete if errors in
ensures, otherwise that ensures complete before eventual termination...

I think it's vital that an error during termination is reported not silenced.  How are you going to write something correct if errors during termination are hidden?
 

Regards, Gary


On Thu, 2010-03-04 at 00:56 +0100, Nicolas Cellier wrote:
> 2010/3/4 Levente Uzonyi <[hidden email]>:
> > On Thu, 4 Mar 2010, Nicolas Cellier wrote:
> >
> >> 2010/3/3 Levente Uzonyi <[hidden email]>:
> >>>
> >>> On Wed, 3 Mar 2010, Andreas Raab wrote:
> >>>
> >>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
> >>>>>
> >>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
> >>>>>>
> >>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
> >>>>>> evaluated no matter what happens.' ?
> >>>>>> But now you saying that it may not be executed in some conditions
> >>>>>> (when user pressing abandon button, causing process to be terminated).
> >>>>>
> >>>>> It's simple: don't terminate process X from another process if process
> >>>>> X
> >>>>> is executing a termiation block (aka #ensure: block). Or if you
> >>>>> terminate it, make sure that the execution of the block will continue
> >>>>> somehow (I don't care how).
> >>>>
> >>>> You're missing Igors point which is that in his example the halt /
> >>>> Transcript *was* in the ensure block and as a result you're
> >>>> contradicting
> >>>> yourself here. Let's go back to Igor's example:
> >>>>
> >>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
> >>>>
> >>>> The halt is inside the ensure block. If you terminate the process from
> >>>> the
> >>>> debugger, it would be logical from your statement that the Transcript
> >>>> message would be executed - after all it's " executing a termiation
> >>>> block
> >>>> (aka #ensure: block)" and so it can't be terminated by your reasoning.
> >>>> However, when Igor was pointing this out you replied with "I didn't say
> >>>> that. I said evaluate it the same way as normal code." which is
> >>>> inconsistent
> >>>> with the other statement.
> >>>
> >>> That shows my lack of knowledge about how the debugger works.
> >>>
> >>>>
> >>>>> I think every user of #ensure: expects that the termination blocks are
> >>>>> executed even if the process which is executing the receiver of
> >>>>> #ensure:
> >>>>> is terminated. And it actually happens in all but this case.
> >>>>
> >>>> The question of terminating processes is always tricky. I don't think
> >>>> that
> >>>> your proposal would actually work in practice - it could easily result
> >>>> in
> >>>> processes that cannot be terminated due to a simple bug in an ensure
> >>>> block.
> >>>> Personally, I'd rather say that the more useful behavior would be
> >>>> something
> >>>> along the lines of saying that process termination either skips the
> >>>> current
> >>>> ensure block (assuming there's a bug and it should get the heck out of
> >>>> it
> >>>> but try to evaluate the remaining ones) or that there need to be two
> >>>> terminations - one that is 'soft' and won't allow ensure blocks to be
> >>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all the
> >>>> ensure blocks.
> >>>
> >>> I'm only saying that normal usage (aka #terminate) shouldn't do
> >>> unexpected
> >>> things like this.
> >>> If you read the comment of Process >> #terminate, you may assume that
> >>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
> >>> #terminate, but that's not true.
> >>>
> >>> "Stop the process that the receiver represents forever.  Unwind to
> >>> execute
> >>> pending ensure:/ifCurtailed: blocks before terminating."
> >>>
> >>>
> >>> Levente
> >>>
> >>
> >> The only way I see to solve your problem would be to execute the
> >> unwind block in another process...
> >> Quite technical and costly !
> >
> > It's our problem. Just look at the senders of #ensure: and imagine what will
> > happen if the termination block is not evaluated.
> > I think there's another way (though it might be my lack of knowledge again).
> > After suspending the process which is about to be terminated we can check if
> > it's executing a termination block. It it's not, we are safe to continue the
> > termination, otherwise we can do something else which ensures that the
> > termination block is evaluated.
> >
>
> Maybe...
> Unfortunately, you did not tell how you will distinguish well behaved
> unwind-blocks from Igor's example...
>
> Nicolas
>
> >
> > Levente
> >
> >>
> >> Nicolas
> >>
> >>>>
> >>>> Cheers,
> >>>>  - Andreas
> >>>>
> >>>>
> >>>
> >>>
> >>
> >
> >
> >
> >
>






Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Eliot Miranda-2
In reply to this post by Andreas.Raab


On Wed, Mar 3, 2010 at 5:35 PM, Andreas Raab <[hidden email]> wrote:
On 3/3/2010 5:23 PM, Eliot Miranda wrote:


On Wed, Mar 3, 2010 at 5:07 PM, Andreas Raab <[hidden email]
<mailto:[hidden email]>> 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.

I said "short of changing the overall termination semantics" :-)

But running unwinds in anything other than the owning process is /broken/ 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













Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Andreas.Raab
In reply to this post by Levente Uzonyi-2
On 3/3/2010 5:44 PM, Levente Uzonyi wrote:
> Here's a simple example (replace file with any external resource):
> My process opened a file, a termination block will close it if it's
> evaluated. If I send #terminate to the process I expect my file to be
> closed, so I won't leak a file descriptor.
> If the file can't be closed (aka the termination block raised an error)
> then there's a serious problem. It doesn't really matter what happens then.
>
> But I don't have to try to convince you anymore, because Andreas is
> about to solve the issue.

Just FYI, the reason why you're not getting through to Igor is because
you're arguing the case from the perspective of a non-well-behaved
termination block which is a lost cause. There simply are no assurances
for that, Igor is correct on that. But that shouldn't stop us trying to
support well-behaved blocks properly. Because if we don't then ensure
becomes entirely random. Or can anyone here explain why it would be
reasonable to expect that this version works:

p := [[Processor yield] ensure:[Transcript show: 'finished']] fork.
Processor yield.
p terminate.

but this one doesn't:

p := [[] ensure:[Processor yield. Transcript show: 'finished']] fork.
Processor yield.
p terminate.

(we're using yield here to denote any kind of process switch) If the
only thing that determines whether an ensure block is executed is a
process switch, then we basically have random, non-predictable unwind
handling. Which is a problem that is entirely unrelated to whether the
unwind block is itself well-behaved or not.

Cheers,
   - Andreas

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Levente Uzonyi-2
On Wed, 3 Mar 2010, Andreas Raab wrote:

> On 3/3/2010 5:44 PM, Levente Uzonyi wrote:
>> Here's a simple example (replace file with any external resource):
>> My process opened a file, a termination block will close it if it's
>> evaluated. If I send #terminate to the process I expect my file to be
>> closed, so I won't leak a file descriptor.
>> If the file can't be closed (aka the termination block raised an error)
>> then there's a serious problem. It doesn't really matter what happens then.
>>
>> But I don't have to try to convince you anymore, because Andreas is
>> about to solve the issue.
>
> Just FYI, the reason why you're not getting through to Igor is because you're
> arguing the case from the perspective of a non-well-behaved termination block
> which is a lost cause. There simply are no assurances for that, Igor is

It must be my bad english, because I don't really care about
non-well-behaved termination blocks.

> correct on that. But that shouldn't stop us trying to support well-behaved
> blocks properly. Because if we don't then ensure becomes entirely random. Or

That's exactly what I'd like to happen.


Levente

> can anyone here explain why it would be reasonable to expect that this
> version works:
>
> p := [[Processor yield] ensure:[Transcript show: 'finished']] fork.
> Processor yield.
> p terminate.
>
> but this one doesn't:
>
> p := [[] ensure:[Processor yield. Transcript show: 'finished']] fork.
> Processor yield.
> p terminate.
>
> (we're using yield here to denote any kind of process switch) If the only
> thing that determines whether an ensure block is executed is a process
> switch, then we basically have random, non-predictable unwind handling. Which
> is a problem that is entirely unrelated to whether the unwind block is itself
> well-behaved or not.
>
> Cheers,
>  - Andreas
>
>

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Igor Stasenko
In reply to this post by Levente Uzonyi-2
2010/3/4 Levente Uzonyi <[hidden email]>:

> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>
>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>
>>> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>>>
>>>> On 4 March 2010 01:56, Nicolas Cellier
>>>> <[hidden email]> wrote:
>>>>>
>>>>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>>>>
>>>>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>>>>
>>>>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>>>>>>>>
>>>>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>>>>>>>>
>>>>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>>>>>>>>
>>>>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>>>>>>>>
>>>>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>>>>>>>>> evaluated no matter what happens.' ?
>>>>>>>>>>> But now you saying that it may not be executed in some conditions
>>>>>>>>>>> (when user pressing abandon button, causing process to be
>>>>>>>>>>> terminated).
>>>>>>>>>>
>>>>>>>>>> It's simple: don't terminate process X from another process if
>>>>>>>>>> process
>>>>>>>>>> X
>>>>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>>>>>>>>>> terminate it, make sure that the execution of the block will
>>>>>>>>>> continue
>>>>>>>>>> somehow (I don't care how).
>>>>>>>>>
>>>>>>>>> You're missing Igors point which is that in his example the halt /
>>>>>>>>> Transcript *was* in the ensure block and as a result you're
>>>>>>>>> contradicting
>>>>>>>>> yourself here. Let's go back to Igor's example:
>>>>>>>>>
>>>>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>>>>>>>>
>>>>>>>>> The halt is inside the ensure block. If you terminate the process
>>>>>>>>> from
>>>>>>>>> the
>>>>>>>>> debugger, it would be logical from your statement that the
>>>>>>>>> Transcript
>>>>>>>>> message would be executed - after all it's " executing a termiation
>>>>>>>>> block
>>>>>>>>> (aka #ensure: block)" and so it can't be terminated by your
>>>>>>>>> reasoning.
>>>>>>>>> However, when Igor was pointing this out you replied with "I didn't
>>>>>>>>> say
>>>>>>>>> that. I said evaluate it the same way as normal code." which is
>>>>>>>>> inconsistent
>>>>>>>>> with the other statement.
>>>>>>>>
>>>>>>>> That shows my lack of knowledge about how the debugger works.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> I think every user of #ensure: expects that the termination blocks
>>>>>>>>>> are
>>>>>>>>>> executed even if the process which is executing the receiver of
>>>>>>>>>> #ensure:
>>>>>>>>>> is terminated. And it actually happens in all but this case.
>>>>>>>>>
>>>>>>>>> The question of terminating processes is always tricky. I don't
>>>>>>>>> think
>>>>>>>>> that
>>>>>>>>> your proposal would actually work in practice - it could easily
>>>>>>>>> result
>>>>>>>>> in
>>>>>>>>> processes that cannot be terminated due to a simple bug in an
>>>>>>>>> ensure
>>>>>>>>> block.
>>>>>>>>> Personally, I'd rather say that the more useful behavior would be
>>>>>>>>> something
>>>>>>>>> along the lines of saying that process termination either skips the
>>>>>>>>> current
>>>>>>>>> ensure block (assuming there's a bug and it should get the heck out
>>>>>>>>> of
>>>>>>>>> it
>>>>>>>>> but try to evaluate the remaining ones) or that there need to be
>>>>>>>>> two
>>>>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to
>>>>>>>>> be
>>>>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all
>>>>>>>>> the
>>>>>>>>> ensure blocks.
>>>>>>>>
>>>>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>>>>>>>> unexpected
>>>>>>>> things like this.
>>>>>>>> If you read the comment of Process >> #terminate, you may assume
>>>>>>>> that
>>>>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>>>>>>>> #terminate, but that's not true.
>>>>>>>>
>>>>>>>> "Stop the process that the receiver represents forever.  Unwind to
>>>>>>>> execute
>>>>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>>>>>>>>
>>>>>>>>
>>>>>>>> Levente
>>>>>>>>
>>>>>>>
>>>>>>> The only way I see to solve your problem would be to execute the
>>>>>>> unwind block in another process...
>>>>>>> Quite technical and costly !
>>>>>>
>>>>>> It's our problem. Just look at the senders of #ensure: and imagine
>>>>>> what
>>>>>> will
>>>>>> happen if the termination block is not evaluated.
>>>>>> I think there's another way (though it might be my lack of knowledge
>>>>>> again).
>>>>>> After suspending the process which is about to be terminated we can
>>>>>> check if
>>>>>> it's executing a termination block. It it's not, we are safe to
>>>>>> continue
>>>>>> the
>>>>>> termination, otherwise we can do something else which ensures that the
>>>>>> termination block is evaluated.
>>>>>>
>>>>>
>>>>> Maybe...
>>>>> Unfortunately, you did not tell how you will distinguish well behaved
>>>>> unwind-blocks from Igor's example...
>>>>>
>>>>
>>>> Yes, then what prevents me from writing:
>>>>
>>>> [ [ ] ensure: [ self doCrazyThings ] ] fork.
>>>
>>> What prevents you from writing: Object superclass: Object. ?
>>> Nothing, but you don't do that, do you?
>>>
>> So, why at all, you care about using #ensure: then? If you putting
>> everything up to the hands of developer,
>> then obviously you won't need to use this message, because you always
>> know that you're running a reliable code which
>> will always let you to run your things in the end. :)
>>
>>>>
>>>> and now given assumption that any code which placed inside ensure
>>>> block should always run to the end, without chances being terminated,
>>>> will have ill side effects.
>>>
>>> You can terminate it (maybe not the usual way).
>>>
>> that's the point. Why do we need two (or more) ways to terminate a
>> process?
>>
>>>> The #ensure: means, that interpreter will have a chance to enter that
>>>> block eventually, but it should not mean that it will keep running the
>>>> code there until normal or non-local return from that block.
>>>
>>> Then it doesn't ensure anything at all, so it should be called
>>> #tryToEvaluateThisToo:.
>>>
>> in fact, this is the actual behavior :)
>> If i press the power button on your PC, or plug out the power cord,
>> any #ensure: blocks will have no chances to run either way.
>> So, relying on something, which is not reliable is a fallacy :)
>
> Here's a simple example (replace file with any external resource):
> My process opened a file, a termination block will close it if it's
> evaluated. If I send #terminate to the process I expect my file to be
> closed, so I won't leak a file descriptor.
> If the file can't be closed (aka the termination block raised an error) then
> there's a serious problem. It doesn't really matter what happens then.
>
> But I don't have to try to convince you anymore, because Andreas is about to
> solve the issue.
>

I'm not trying to convince anyone, i just wanted to show you that
there is no good solution in that plane.
More workarounds means more code to run (and makes things more complex, btw).
But you will be still unsafe.You will be safe, once you stop relying
on #ensure: in your code and use different approach.

As for your example: use weak finalizer to close your file.
This will make sure that no matter what were happen, you wont leave
the file open. Working with external resources is a pain. But lets try
to not poison ourselves with manual resource management, which comes
from C world.

>
> Levente
>
>>
>>>
>>> Levente
>>>
>>>> Othewise, you losing a way to terminate unwanted, ill-behaved process.
>>>>
>>>>> Nicolas
>>>>>
>>>>>>
>>>>>> Levente
>>>>>>
>>>>>>>
>>>>>>> Nicolas
>>>>>>>
>>>>>>>>>
>>>>>>>>> Cheers,
>>>>>>>>>  - Andreas
>>>>>>>>>
>>>>
>>>> --
>>>> Best regards,
>>>> Igor Stasenko AKA sig.
>>>>
>>>
>>>
>>>
>>>
>>
>>
>>
>> --
>> Best regards,
>> Igor Stasenko AKA sig.
>>
>
>
>
>



--
Best regards,
Igor Stasenko AKA sig.

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Andreas.Raab
In reply to this post by Eliot Miranda-2
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. 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"].

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.

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

Cheers,
   - Andreas


> 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
>
>
>
>
>
>
>
>
>
>
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Eliot Miranda-2


On Wed, Mar 3, 2010 at 6:13 PM, Andreas Raab <[hidden email]> 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



















Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Eliot Miranda-2
In reply to this post by Igor Stasenko


On Wed, Mar 3, 2010 at 6:10 PM, Igor Stasenko <[hidden email]> wrote:
2010/3/4 Levente Uzonyi <[hidden email]>:
> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>
>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>
>>> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>>>
>>>> On 4 March 2010 01:56, Nicolas Cellier
>>>> <[hidden email]> wrote:
>>>>>
>>>>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>>>>>>
>>>>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>>>>>>
>>>>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>>>>>>>>
>>>>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>>>>>>>>
>>>>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>>>>>>>>>>
>>>>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>>>>>>>>>>>
>>>>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to be
>>>>>>>>>>> evaluated no matter what happens.' ?
>>>>>>>>>>> But now you saying that it may not be executed in some conditions
>>>>>>>>>>> (when user pressing abandon button, causing process to be
>>>>>>>>>>> terminated).
>>>>>>>>>>
>>>>>>>>>> It's simple: don't terminate process X from another process if
>>>>>>>>>> process
>>>>>>>>>> X
>>>>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>>>>>>>>>> terminate it, make sure that the execution of the block will
>>>>>>>>>> continue
>>>>>>>>>> somehow (I don't care how).
>>>>>>>>>
>>>>>>>>> You're missing Igors point which is that in his example the halt /
>>>>>>>>> Transcript *was* in the ensure block and as a result you're
>>>>>>>>> contradicting
>>>>>>>>> yourself here. Let's go back to Igor's example:
>>>>>>>>>
>>>>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>>>>>>>>>
>>>>>>>>> The halt is inside the ensure block. If you terminate the process
>>>>>>>>> from
>>>>>>>>> the
>>>>>>>>> debugger, it would be logical from your statement that the
>>>>>>>>> Transcript
>>>>>>>>> message would be executed - after all it's " executing a termiation
>>>>>>>>> block
>>>>>>>>> (aka #ensure: block)" and so it can't be terminated by your
>>>>>>>>> reasoning.
>>>>>>>>> However, when Igor was pointing this out you replied with "I didn't
>>>>>>>>> say
>>>>>>>>> that. I said evaluate it the same way as normal code." which is
>>>>>>>>> inconsistent
>>>>>>>>> with the other statement.
>>>>>>>>
>>>>>>>> That shows my lack of knowledge about how the debugger works.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> I think every user of #ensure: expects that the termination blocks
>>>>>>>>>> are
>>>>>>>>>> executed even if the process which is executing the receiver of
>>>>>>>>>> #ensure:
>>>>>>>>>> is terminated. And it actually happens in all but this case.
>>>>>>>>>
>>>>>>>>> The question of terminating processes is always tricky. I don't
>>>>>>>>> think
>>>>>>>>> that
>>>>>>>>> your proposal would actually work in practice - it could easily
>>>>>>>>> result
>>>>>>>>> in
>>>>>>>>> processes that cannot be terminated due to a simple bug in an
>>>>>>>>> ensure
>>>>>>>>> block.
>>>>>>>>> Personally, I'd rather say that the more useful behavior would be
>>>>>>>>> something
>>>>>>>>> along the lines of saying that process termination either skips the
>>>>>>>>> current
>>>>>>>>> ensure block (assuming there's a bug and it should get the heck out
>>>>>>>>> of
>>>>>>>>> it
>>>>>>>>> but try to evaluate the remaining ones) or that there need to be
>>>>>>>>> two
>>>>>>>>> terminations - one that is 'soft' and won't allow ensure blocks to
>>>>>>>>> be
>>>>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores all
>>>>>>>>> the
>>>>>>>>> ensure blocks.
>>>>>>>>
>>>>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>>>>>>>> unexpected
>>>>>>>> things like this.
>>>>>>>> If you read the comment of Process >> #terminate, you may assume
>>>>>>>> that
>>>>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>>>>>>>> #terminate, but that's not true.
>>>>>>>>
>>>>>>>> "Stop the process that the receiver represents forever.  Unwind to
>>>>>>>> execute
>>>>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>>>>>>>>
>>>>>>>>
>>>>>>>> Levente
>>>>>>>>
>>>>>>>
>>>>>>> The only way I see to solve your problem would be to execute the
>>>>>>> unwind block in another process...
>>>>>>> Quite technical and costly !
>>>>>>
>>>>>> It's our problem. Just look at the senders of #ensure: and imagine
>>>>>> what
>>>>>> will
>>>>>> happen if the termination block is not evaluated.
>>>>>> I think there's another way (though it might be my lack of knowledge
>>>>>> again).
>>>>>> After suspending the process which is about to be terminated we can
>>>>>> check if
>>>>>> it's executing a termination block. It it's not, we are safe to
>>>>>> continue
>>>>>> the
>>>>>> termination, otherwise we can do something else which ensures that the
>>>>>> termination block is evaluated.
>>>>>>
>>>>>
>>>>> Maybe...
>>>>> Unfortunately, you did not tell how you will distinguish well behaved
>>>>> unwind-blocks from Igor's example...
>>>>>
>>>>
>>>> Yes, then what prevents me from writing:
>>>>
>>>> [ [ ] ensure: [ self doCrazyThings ] ] fork.
>>>
>>> What prevents you from writing: Object superclass: Object. ?
>>> Nothing, but you don't do that, do you?
>>>
>> So, why at all, you care about using #ensure: then? If you putting
>> everything up to the hands of developer,
>> then obviously you won't need to use this message, because you always
>> know that you're running a reliable code which
>> will always let you to run your things in the end. :)
>>
>>>>
>>>> and now given assumption that any code which placed inside ensure
>>>> block should always run to the end, without chances being terminated,
>>>> will have ill side effects.
>>>
>>> You can terminate it (maybe not the usual way).
>>>
>> that's the point. Why do we need two (or more) ways to terminate a
>> process?
>>
>>>> The #ensure: means, that interpreter will have a chance to enter that
>>>> block eventually, but it should not mean that it will keep running the
>>>> code there until normal or non-local return from that block.
>>>
>>> Then it doesn't ensure anything at all, so it should be called
>>> #tryToEvaluateThisToo:.
>>>
>> in fact, this is the actual behavior :)
>> If i press the power button on your PC, or plug out the power cord,
>> any #ensure: blocks will have no chances to run either way.
>> So, relying on something, which is not reliable is a fallacy :)
>
> Here's a simple example (replace file with any external resource):
> My process opened a file, a termination block will close it if it's
> evaluated. If I send #terminate to the process I expect my file to be
> closed, so I won't leak a file descriptor.
> If the file can't be closed (aka the termination block raised an error) then
> there's a serious problem. It doesn't really matter what happens then.
>
> But I don't have to try to convince you anymore, because Andreas is about to
> solve the issue.
>

I'm not trying to convince anyone, i just wanted to show you that
there is no good solution in that plane.
More workarounds means more code to run (and makes things more complex, btw).
But you will be still unsafe.You will be safe, once you stop relying
on #ensure: in your code and use different approach.

As for your example: use weak finalizer to close your file.
This will make sure that no matter what were happen, you wont leave
the file open. Working with external resources is a pain. But lets try
to not poison ourselves with manual resource management, which comes
from C world.

For example the weak finalizer won't help. If the system quits without doing a GC (which could happen for a number of internal or external reasons) the finalizer won't get run.  In general in the presence of errors all bets are off.  In fact, the finalizer is worse because there are no timely guarantees as to the finalizer running and therefore errors could go unreported.  At least with an unwind-protect it is easy to arrange that if there is an you'll get notified about it.

need to remember to say "best" instead of rattling off replies :)
Eliot


>
> Levente
>
>>
>>>
>>> Levente
>>>
>>>> Othewise, you losing a way to terminate unwanted, ill-behaved process.
>>>>
>>>>> Nicolas
>>>>>
>>>>>>
>>>>>> Levente
>>>>>>
>>>>>>>
>>>>>>> Nicolas
>>>>>>>
>>>>>>>>>
>>>>>>>>> Cheers,
>>>>>>>>>  - Andreas
>>>>>>>>>
>>>>
>>>> --
>>>> Best regards,
>>>> Igor Stasenko AKA sig.
>>>>
>>>
>>>
>>>
>>>
>>
>>
>>
>> --
>> Best regards,
>> Igor Stasenko AKA sig.
>>
>
>
>
>



--
Best regards,
Igor Stasenko AKA sig.




Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Igor Stasenko
On 4 March 2010 04:34, Eliot Miranda <[hidden email]> wrote:

>
>
> On Wed, Mar 3, 2010 at 6:10 PM, Igor Stasenko <[hidden email]> wrote:
>>
>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>> > On Thu, 4 Mar 2010, Igor Stasenko wrote:
>> >
>> >> 2010/3/4 Levente Uzonyi <[hidden email]>:
>> >>>
>> >>> On Thu, 4 Mar 2010, Igor Stasenko wrote:
>> >>>
>> >>>> On 4 March 2010 01:56, Nicolas Cellier
>> >>>> <[hidden email]> wrote:
>> >>>>>
>> >>>>> 2010/3/4 Levente Uzonyi <[hidden email]>:
>> >>>>>>
>> >>>>>> On Thu, 4 Mar 2010, Nicolas Cellier wrote:
>> >>>>>>
>> >>>>>>> 2010/3/3 Levente Uzonyi <[hidden email]>:
>> >>>>>>>>
>> >>>>>>>> On Wed, 3 Mar 2010, Andreas Raab wrote:
>> >>>>>>>>
>> >>>>>>>>> On 3/3/2010 2:07 PM, Levente Uzonyi wrote:
>> >>>>>>>>>>
>> >>>>>>>>>> On Wed, 3 Mar 2010, Igor Stasenko wrote:
>> >>>>>>>>>>>
>> >>>>>>>>>>> i don't get it. Just before that, you said: ' I'd expect it to
>> >>>>>>>>>>> be
>> >>>>>>>>>>> evaluated no matter what happens.' ?
>> >>>>>>>>>>> But now you saying that it may not be executed in some
>> >>>>>>>>>>> conditions
>> >>>>>>>>>>> (when user pressing abandon button, causing process to be
>> >>>>>>>>>>> terminated).
>> >>>>>>>>>>
>> >>>>>>>>>> It's simple: don't terminate process X from another process if
>> >>>>>>>>>> process
>> >>>>>>>>>> X
>> >>>>>>>>>> is executing a termiation block (aka #ensure: block). Or if you
>> >>>>>>>>>> terminate it, make sure that the execution of the block will
>> >>>>>>>>>> continue
>> >>>>>>>>>> somehow (I don't care how).
>> >>>>>>>>>
>> >>>>>>>>> You're missing Igors point which is that in his example the halt
>> >>>>>>>>> /
>> >>>>>>>>> Transcript *was* in the ensure block and as a result you're
>> >>>>>>>>> contradicting
>> >>>>>>>>> yourself here. Let's go back to Igor's example:
>> >>>>>>>>>
>> >>>>>>>>> [self boom ] ensure: [ self halt. Transcript show: 'boom']
>> >>>>>>>>>
>> >>>>>>>>> The halt is inside the ensure block. If you terminate the
>> >>>>>>>>> process
>> >>>>>>>>> from
>> >>>>>>>>> the
>> >>>>>>>>> debugger, it would be logical from your statement that the
>> >>>>>>>>> Transcript
>> >>>>>>>>> message would be executed - after all it's " executing a
>> >>>>>>>>> termiation
>> >>>>>>>>> block
>> >>>>>>>>> (aka #ensure: block)" and so it can't be terminated by your
>> >>>>>>>>> reasoning.
>> >>>>>>>>> However, when Igor was pointing this out you replied with "I
>> >>>>>>>>> didn't
>> >>>>>>>>> say
>> >>>>>>>>> that. I said evaluate it the same way as normal code." which is
>> >>>>>>>>> inconsistent
>> >>>>>>>>> with the other statement.
>> >>>>>>>>
>> >>>>>>>> That shows my lack of knowledge about how the debugger works.
>> >>>>>>>>
>> >>>>>>>>>
>> >>>>>>>>>> I think every user of #ensure: expects that the termination
>> >>>>>>>>>> blocks
>> >>>>>>>>>> are
>> >>>>>>>>>> executed even if the process which is executing the receiver of
>> >>>>>>>>>> #ensure:
>> >>>>>>>>>> is terminated. And it actually happens in all but this case.
>> >>>>>>>>>
>> >>>>>>>>> The question of terminating processes is always tricky. I don't
>> >>>>>>>>> think
>> >>>>>>>>> that
>> >>>>>>>>> your proposal would actually work in practice - it could easily
>> >>>>>>>>> result
>> >>>>>>>>> in
>> >>>>>>>>> processes that cannot be terminated due to a simple bug in an
>> >>>>>>>>> ensure
>> >>>>>>>>> block.
>> >>>>>>>>> Personally, I'd rather say that the more useful behavior would
>> >>>>>>>>> be
>> >>>>>>>>> something
>> >>>>>>>>> along the lines of saying that process termination either skips
>> >>>>>>>>> the
>> >>>>>>>>> current
>> >>>>>>>>> ensure block (assuming there's a bug and it should get the heck
>> >>>>>>>>> out
>> >>>>>>>>> of
>> >>>>>>>>> it
>> >>>>>>>>> but try to evaluate the remaining ones) or that there need to be
>> >>>>>>>>> two
>> >>>>>>>>> terminations - one that is 'soft' and won't allow ensure blocks
>> >>>>>>>>> to
>> >>>>>>>>> be
>> >>>>>>>>> skipped and one that is 'hard' (kill -9 hard) and just ignores
>> >>>>>>>>> all
>> >>>>>>>>> the
>> >>>>>>>>> ensure blocks.
>> >>>>>>>>
>> >>>>>>>> I'm only saying that normal usage (aka #terminate) shouldn't do
>> >>>>>>>> unexpected
>> >>>>>>>> things like this.
>> >>>>>>>> If you read the comment of Process >> #terminate, you may assume
>> >>>>>>>> that
>> >>>>>>>> #ensure: and #ifCurtailed: blocks will be excuted even if you use
>> >>>>>>>> #terminate, but that's not true.
>> >>>>>>>>
>> >>>>>>>> "Stop the process that the receiver represents forever.  Unwind
>> >>>>>>>> to
>> >>>>>>>> execute
>> >>>>>>>> pending ensure:/ifCurtailed: blocks before terminating."
>> >>>>>>>>
>> >>>>>>>>
>> >>>>>>>> Levente
>> >>>>>>>>
>> >>>>>>>
>> >>>>>>> The only way I see to solve your problem would be to execute the
>> >>>>>>> unwind block in another process...
>> >>>>>>> Quite technical and costly !
>> >>>>>>
>> >>>>>> It's our problem. Just look at the senders of #ensure: and imagine
>> >>>>>> what
>> >>>>>> will
>> >>>>>> happen if the termination block is not evaluated.
>> >>>>>> I think there's another way (though it might be my lack of
>> >>>>>> knowledge
>> >>>>>> again).
>> >>>>>> After suspending the process which is about to be terminated we can
>> >>>>>> check if
>> >>>>>> it's executing a termination block. It it's not, we are safe to
>> >>>>>> continue
>> >>>>>> the
>> >>>>>> termination, otherwise we can do something else which ensures that
>> >>>>>> the
>> >>>>>> termination block is evaluated.
>> >>>>>>
>> >>>>>
>> >>>>> Maybe...
>> >>>>> Unfortunately, you did not tell how you will distinguish well
>> >>>>> behaved
>> >>>>> unwind-blocks from Igor's example...
>> >>>>>
>> >>>>
>> >>>> Yes, then what prevents me from writing:
>> >>>>
>> >>>> [ [ ] ensure: [ self doCrazyThings ] ] fork.
>> >>>
>> >>> What prevents you from writing: Object superclass: Object. ?
>> >>> Nothing, but you don't do that, do you?
>> >>>
>> >> So, why at all, you care about using #ensure: then? If you putting
>> >> everything up to the hands of developer,
>> >> then obviously you won't need to use this message, because you always
>> >> know that you're running a reliable code which
>> >> will always let you to run your things in the end. :)
>> >>
>> >>>>
>> >>>> and now given assumption that any code which placed inside ensure
>> >>>> block should always run to the end, without chances being terminated,
>> >>>> will have ill side effects.
>> >>>
>> >>> You can terminate it (maybe not the usual way).
>> >>>
>> >> that's the point. Why do we need two (or more) ways to terminate a
>> >> process?
>> >>
>> >>>> The #ensure: means, that interpreter will have a chance to enter that
>> >>>> block eventually, but it should not mean that it will keep running
>> >>>> the
>> >>>> code there until normal or non-local return from that block.
>> >>>
>> >>> Then it doesn't ensure anything at all, so it should be called
>> >>> #tryToEvaluateThisToo:.
>> >>>
>> >> in fact, this is the actual behavior :)
>> >> If i press the power button on your PC, or plug out the power cord,
>> >> any #ensure: blocks will have no chances to run either way.
>> >> So, relying on something, which is not reliable is a fallacy :)
>> >
>> > Here's a simple example (replace file with any external resource):
>> > My process opened a file, a termination block will close it if it's
>> > evaluated. If I send #terminate to the process I expect my file to be
>> > closed, so I won't leak a file descriptor.
>> > If the file can't be closed (aka the termination block raised an error)
>> > then
>> > there's a serious problem. It doesn't really matter what happens then.
>> >
>> > But I don't have to try to convince you anymore, because Andreas is
>> > about to
>> > solve the issue.
>> >
>>
>> I'm not trying to convince anyone, i just wanted to show you that
>> there is no good solution in that plane.
>> More workarounds means more code to run (and makes things more complex,
>> btw).
>> But you will be still unsafe.You will be safe, once you stop relying
>> on #ensure: in your code and use different approach.
>>
>> As for your example: use weak finalizer to close your file.
>> This will make sure that no matter what were happen, you wont leave
>> the file open. Working with external resources is a pain. But lets try
>> to not poison ourselves with manual resource management, which comes
>> from C world.
>
> For example the weak finalizer won't help. If the system quits without doing
> a GC (which could happen for a number of internal or external reasons) the
> finalizer won't get run.  In general in the presence of errors all bets are
> off.  In fact, the finalizer is worse because there are no timely guarantees
> as to the finalizer running and therefore errors could go unreported.  At
> least with an unwind-protect it is easy to arrange that if there is an
> you'll get notified about it.
> need to remember to say "best" instead of rattling off replies :)

You're right. Lets not bet on anything, unless the whole system using
an automatic resource management.
Then you won't have an open/closed state, but just objects and garbage
to be collected.

As for error reporting. Well, same thing, what if an error reporting
relies on a file-system which is broken somehow?
Then you won't be able to report any errors and none of unwind-protect
blocks will make it any safer. :)
So, where you think ends an unreliable code, and starts a reliable?
The problem, i trying to articulate is, that we are trying to build a
castle on a swamp to protect ourselves from swamp monsters, but at
same time we not paying attention that castle can be submerged at any
moment, and then the swamp monsters will be the least hazard we'll
need to worry about.


--
Best regards,
Igor Stasenko AKA sig.

Reply | Threaded
Open this post in threaded view
|

Re: [Pharo-project] #ensure: issues

Andreas.Raab
In reply to this post by Eliot Miranda-2
On 3/3/2010 6:32 PM, Eliot Miranda wrote:

> 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.

Yes, but my claim was that this a feature, not a bug, for a value of
"feature" which means that the terminator (<- I really wanted to use
that :-) is capable of controlling the exception context of the ensure
block.

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

The reason why I'm not convinced of that, period or not, repetitions or
not, is because we've seen no evidence of it ever being a problem. If
it's not a problem, neither in our production environments that have
triggered all the problems and the fixes for process, semaphore, mutex,
termination etc. handling over the last years, nor in messages or bug
reports that I've seen on this or other lists, I'm just not buying the
strong claim you are making.

Where is the *evidence* to support your claim? Not the theoretical
discussion of what could possibly go wrong, but rather the actual,
day-to-day things that people really get bitten by.

> 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.

Well, not really. In a realistic situation you'd probably want to handle
multiple conditions, timeouts etc. and stitching the stack together for
that is likely way beyond your average Squeaker :-)

[BTW, I don't get why you call this "better"; it would seem that if you
call this "better" because it's using in-process termination that your
reasoning is cyclical]

>     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]
> ?

It would work fine, why? Did you not notice that I used
#evaluate:onBehalfOf: in the above for precisely this reason?

> 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 ;)

I don't buy this, and claiming it repeatedly doesn't make it any more
real unless you put up or shut up. I think you're vastly underestimating
the resulting fallout (does that sound like something I've said before? ;-)

For example, here is an interesting one (and please use VW for reference
here if you'd like; I'd actually be interested in finding a *small*
download myself to try some of this stuff): One of the issues in
Levente's example of terminating a process halfways through an unwind
block is that if the unwind block is ill-behaved you can really be in
for trouble. Let's say you have this:

[
   "just to have something to interrupt"
   [(Delay forMilliseconds: 100) wait. true] whileTrue.
] ensure:[
   "this is the actual killer"
   [(Delay forMilliseconds: 100) wait. true] whileTrue.
].

If you wish to support completing a well-behaved ensure block that has
been started, it seems tricky to see how this process would actually be
terminated, and how you'd find out that it didn't. In Squeak it's simple
- the fact that the process sending #terminate is still in this code
tells you it's not finished so one can (for example) associate a timeout
with #terminate and catch that. Or, one can just interrupt that process
and close the debugger. Both are quite reasonable options to deal with
the invariant.

Under the assumption that you'd like to support the well-behaved case,
what do you do when using in-process termination in a non-well-behaved
case? How do you find out that a termination request didn't finish, and
what do you do in practice to kill it?

Also, what are the priority implications of the different schemes? One
of the more interesting issues here is that in cases like on our servers
I could see how one of the lower priority processes times out due to
starvation, is then terminated but this may never complete if it's run
from the priority it's at instead of the terminator process. Does for
example VW bump the priority of the terminated process? How does that
interact when the terminated process isn't well-behaved and runs wild?

I think there are numerous interesting aspects that we have somewhat
reasonable answers in Squeak today that I don't even know what the
implications would be if you'd go for in-process termination.

Cheers,
   - Andreas

123