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. 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. > > |
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 >>>>>>>> >>>>>>>> >>>>>>> >>>>>>> >>>>>> >>>>> >>>>> >>>>> >>>>> >>>> >>> >>> >>> >>> >> > > > > |
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 |
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. |
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. 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
|
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? > 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. |
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 > >>>> > >>>> > >>> > >>> > >> > > > > > > > > > |
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 > > > > > > > > |
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. > >> > > > > > > > > > > > |
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 :) 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. > > |
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, 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?
|
In reply to this post by Andreas.Raab
On Wed, Mar 3, 2010 at 5:35 PM, Andreas Raab <[hidden email]> wrote:
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.
|
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 |
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 > > |
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. |
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 > > > > > > > > > > > > > > |
On Wed, Mar 3, 2010 at 6:13 PM, Andreas Raab <[hidden email]> 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.
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: 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.
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 ;)
best, Eliot
|
In reply to this post by Igor Stasenko
On Wed, Mar 3, 2010 at 6:10 PM, Igor Stasenko <[hidden email]> wrote:
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
|
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. |
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 |
Free forum by Nabble | Edit this page |