Hi all,
I just found a side-effect of Process>>terminate call, leading to evaluate all #ensure: and #ifCurtailed: blocks in process before its really got terminated. I just wonder, how many of you wrote #ensure: thinking that it will be evaluated only during normal execution flow and never when process is terminated in the middle of execution. I just had conversation with Goran on IRC, and he was surprised by not knowing this too. It needless to say that writing a code which uses #ensure: without knowing such details could lead to errors which is hard to catch and reproduce. I thought that #terminate behavior was just stop process at point where its currently running and never try to evaluate anything else in its context. A terminating in current way could be considered as 'soft terminate' or gracious terminate. But then we lack of 'hard' or unconditional terminate, which just stops process without trying to unwind stack. And the poll question is: how many of you thought that #terminate is 'hard' terminate, not 'soft'. :) -- Best regards, Igor Stasenko AKA sig. |
> I just found a side-effect of Process>>terminate call, leading to
> evaluate all #ensure: and #ifCurtailed: blocks in process before its > really got terminated. This is absolutely the right behavior. When I say "ensure", I mean "ensure no matter what". This includes deleting the debug window or terminating the process some other way. In particular, you want all semaphores in critical regions to be unlocked. Why is this a problem? Perhaps there is a bug in the ensure block, and so you don't want to run it. I use ensure blocks to close files, and if I kill the process, I still want files to be closed. -Ralph |
In reply to this post by Igor Stasenko
> And the poll question is: how many of you thought that #terminate is
> 'hard' terminate, not 'soft'. :) I do expect that #ensure: blocks are evaluated, but ... There are many more gotchas in Process>>#terminate. In fact this is one of the darkest places of Squeak, in my opinion. - #ensure: blocks are evaluated in the wrong process, that is the process that calls #terminate not the one that runs the process. This can have very strange effects, if your code depends on the current process. - #terminate can badly harm system if two processes concurrently try to terminate a third process. More? Lukas -- Lukas Renggli http://www.lukas-renggli.ch |
In reply to this post by Igor Stasenko
"Igor Stasenko" <[hidden email]> wrote:
> Hi all, > > I just found a side-effect of Process>>terminate call, leading to > evaluate all #ensure: and #ifCurtailed: blocks in process before its > really got terminated. > > I just wonder, how many of you wrote #ensure: thinking that it will be > evaluated only during normal execution flow and never when process is > terminated in the middle of execution. > > I just had conversation with Goran on IRC, and he was surprised by not > knowing this too. > It needless to say that writing a code which uses #ensure: without > knowing such details could lead to errors which is hard to catch and > reproduce. The problem is, in my opinion, that #ensure: and #ifCurtailed: are often explained as exception handlers. That is wrong. The methods #ensure: and #ifCurtailed: are designed to provide cleanup actions for stack unwinding. The stack is unwound when * pending method calls are dropped during exception handling (methods Exception>return and Exception>return: are used to do that) * pending method calls are left with a jump (To do that, you pass a block with a return statement (e.g. ^self) as argument to another method) * a process is terminated So these are the situations that cause the evaluation of an ensure/ifCurtailed block. I think it is perhaps better to call #ensure: and #ifCurtailed: finalizers.Unfortunately, that word has also a lot of slightly different meanings in different programming languages. > I thought that #terminate behavior was just stop process at point > where its currently running and never try to evaluate anything else in > its context. > > A terminating in current way could be considered as 'soft terminate' > or gracious terminate. But then we lack of 'hard' or unconditional > terminate, which just stops process without trying to unwind stack. > > And the poll question is: how many of you thought that #terminate is > 'hard' terminate, not 'soft'. :) and Smalltalk in general, I learned all these details. Greetings, Boris |
In reply to this post by Ralph Johnson
Hi!
>> I just found a side-effect of Process>>terminate call, leading to >> evaluate all #ensure: and #ifCurtailed: blocks in process before its >> really got terminated. > > This is absolutely the right behavior. When I say "ensure", I mean > "ensure no matter what". This includes deleting the debug window or > terminating the process some other way. In particular, you want all > semaphores in critical regions to be unlocked. Well, I must *personally* say that I always associated #ensure: and friends with the exception framework - thus, yes, I also want my ensure: blocks to run - when there are *exceptions* thrown - but also when the Process is *terminated*? Igor asked me on IRC and I admit I wouldn't have guessed them to be executed then. And I presume Igor wants to see how many of us have the "wrong" perception here - and if we are many - then *that* is in any circumstance a problem. > Why is this a problem? Perhaps there is a bug in the ensure block, > and so you don't want to run it. I use ensure blocks to close files, > and if I kill the process, I still want files to be closed. > > -Ralph I am unsure, but I think Igor noticed some oddish behavior in Magma that caught his attention to this. regards, Göran |
In reply to this post by Lukas Renggli
> Well, I must *personally* say that I always associated #ensure: and
> friends with the exception framework - thus, yes, I also want my ensure: > blocks to run - when there are *exceptions* thrown - but also when the > Process is *terminated*? That's the source of the misunderstanding. #ensure: is completely detached from the exception framework. In fact, you can implement exceptions only with thisContext, but it's really hard to implement #ensure: correctly without support in the virtual machine. #ensure: and #ifCurtailed: blocks, for example, are run also when a block performs a non-local return, as in ^[ ^#outer ] ensure: [ Transcript show: 'executed' ] ^[ ^#outer ] ifCurtailed: [ Transcript show: 'executed' ] Note that #ifCurtailed: and #ensure: are equally powerful. Here is #ifCurtailed: implemented with #ensure: and vice versa: BlockClosure>>ifCurtailed: curtailedBlock | result | curtailed := true. ^[ result := aBlock value. curtailed := false. result ] ensure: [ curtailed ifTrue: [ curtailedBlock value ] ] BlockClosure>>ensure: ensureBlock | result | [ result := aBlock value ] ifCurtailed: [ ensureBlock value. ^result ]. "Don't reenter the ensureBlock if it is curtailed!" ensureBlock value. ^result > - #ensure: blocks are evaluated in the wrong process, that is the > process that calls #terminate not the one that runs the process. This > can have very strange effects, if your code depends on the current > process. This can be solved easily. You define a ProcessBeingTerminated exception and wrap the process block aBlock into something like this: BlockClosure>>newProcess ^Process forContext: self processBlock priority: Processor userSchedulingPriority BlockClosure>>processBlock ^[[aBlock on: ProcessBeingTerminated do: [:sig | "If we terminate in the handler, the 'ensure' blocks are not evaluated. Instead, if the handler returns, the unwinding is done properly." sig return]] ensure: [self primTerminate]] where #primTerminate is the "hard terminate" that Igor was mentioning. Process>>primTerminate self isActiveProcess ifFalse: [ self error: 'booh' ]. thisContext terminateTo: nil. self suspend and termination becomes a matter of signaling the exception: ProcessorScheduler>>isProcessReady: aProcess ^aProcess suspendingList == (quiescentProcessLists at: aProcess priority) Process>>isReady ^Processor isProcessReady: self Process>>terminate | pbt | "A while ago I explained how to use this to properly implement Semaphore>>#critical:." pbt := ProcessBeingTerminated new. self isReady ifFalse: [ pbt semaphore: myList ]. self signalException: pbt. You can do whatever you want with this code, it is not taken from GNU Smalltalk. Paolo |
Lukas Renggli:
- #terminate can badly harm system if two processes concurrently try to terminate a third process. Yes, and that means that #terminate must be enclosed by semaphore to ensure that termination code evaluated only once. And moreover, things must be harvested to see, what is happening when process which causes other process to terminate terminating too .. like: proc := [ ... ] fork. [ proc terminate ] fork terminate. On 15/10/2007, Paolo Bonzini <[hidden email]> wrote: > > Well, I must *personally* say that I always associated #ensure: and > > friends with the exception framework - thus, yes, I also want my ensure: > > blocks to run - when there are *exceptions* thrown - but also when the > > Process is *terminated*? > > That's the source of the misunderstanding. #ensure: is completely > detached from the exception framework. In fact, you can implement > exceptions only with thisContext, but it's really hard to implement > #ensure: correctly without support in the virtual machine. #ensure: and > #ifCurtailed: blocks, for example, are run also when a block performs a > non-local return, as in > > ^[ ^#outer ] ensure: [ Transcript show: 'executed' ] > ^[ ^#outer ] ifCurtailed: [ Transcript show: 'executed' ] > > Note that #ifCurtailed: and #ensure: are equally powerful. Here is > #ifCurtailed: implemented with #ensure: and vice versa: > > BlockClosure>>ifCurtailed: curtailedBlock > | result | > curtailed := true. > ^[ result := aBlock value. curtailed := false. result ] > ensure: [ curtailed ifTrue: [ curtailedBlock value ] ] > > BlockClosure>>ensure: ensureBlock > | result | > [ result := aBlock value ] ifCurtailed: [ > ensureBlock value. > ^result ]. > "Don't reenter the ensureBlock if it is curtailed!" > ensureBlock value. > ^result > > > - #ensure: blocks are evaluated in the wrong process, that is the > > process that calls #terminate not the one that runs the process. This > > can have very strange effects, if your code depends on the current > > process. > > This can be solved easily. You define a ProcessBeingTerminated > exception and wrap the process block aBlock into something like this: > > BlockClosure>>newProcess > ^Process > forContext: self processBlock > priority: Processor userSchedulingPriority > > BlockClosure>>processBlock > ^[[aBlock > on: ProcessBeingTerminated > do: [:sig | > "If we terminate in the handler, the 'ensure' blocks > are not evaluated. Instead, if the handler returns, the > unwinding is done properly." > sig return]] > ensure: [self primTerminate]] > > > where #primTerminate is the "hard terminate" that Igor was mentioning. > > Process>>primTerminate > self isActiveProcess ifFalse: [ self error: 'booh' ]. > thisContext terminateTo: nil. > self suspend > > > and termination becomes a matter of signaling the exception: > > ProcessorScheduler>>isProcessReady: aProcess > ^aProcess suspendingList == > (quiescentProcessLists at: aProcess priority) > > Process>>isReady > ^Processor isProcessReady: self > > Process>>terminate > | pbt | > "A while ago I explained how to use this to properly implement > Semaphore>>#critical:." > pbt := ProcessBeingTerminated new. > self isReady ifFalse: [ pbt semaphore: myList ]. > self signalException: pbt. > > > You can do whatever you want with this code, it is not taken from GNU > Smalltalk. > > Paolo > For cases, when my process is terminated and i need to execute some code before(or at the moment) when this happens, better, i think is to provide a #onTerminated: method for Process, where you can put a hadler which will be executed whenever process is going to be terminated. In normal conditions, if process finished w/o explicit call to #terminate, this code should not be invoked. So, you can just write something like that: [ ... ] fork onTerminate: [ :p | Transcript show: 'Ouch, i terminated by..' , p name ] or even #addOnTerminate: block , which will add a block to a list of blocks which will be evaluated when process is terminating. Also, i not agree that terminate must be connected with exceptions. Process provides a ways how to control its execution flow, and its not an exceptional behavior, its a part of its interface. And i think that exceptions framework must not be involved here. Of course, its just my humble opinion :) -- Best regards, Igor Stasenko AKA sig. |
That seems like the correct solution to me.
> -----Original Message----- > From: [hidden email] > [mailto:[hidden email]]On Behalf Of Igor > Stasenko > Sent: 15 October 2007 7:55 PM > To: The general-purpose Squeak developers list > Subject: Re: [POLL] Process>>terminate > > > Lukas Renggli: > > - #terminate can badly harm system if two processes concurrently try > to terminate a third process. > > Yes, and that means that #terminate must be enclosed by semaphore to > ensure that termination code evaluated only once. > And moreover, things must be harvested to see, what is happening when > process which causes other process to terminate terminating too .. > like: > > proc := [ ... ] fork. > [ proc terminate ] fork terminate. > > > On 15/10/2007, Paolo Bonzini <[hidden email]> wrote: > > > Well, I must *personally* say that I always associated #ensure: and > > > friends with the exception framework - thus, yes, I also want > my ensure: > > > blocks to run - when there are *exceptions* thrown - but also when the > > > Process is *terminated*? > > > > That's the source of the misunderstanding. #ensure: is completely > > detached from the exception framework. In fact, you can implement > > exceptions only with thisContext, but it's really hard to implement > > #ensure: correctly without support in the virtual machine. #ensure: and > > #ifCurtailed: blocks, for example, are run also when a block performs a > > non-local return, as in > > > > ^[ ^#outer ] ensure: [ Transcript show: 'executed' ] > > ^[ ^#outer ] ifCurtailed: [ Transcript show: 'executed' ] > > > > Note that #ifCurtailed: and #ensure: are equally powerful. Here is > > #ifCurtailed: implemented with #ensure: and vice versa: > > > > BlockClosure>>ifCurtailed: curtailedBlock > > | result | > > curtailed := true. > > ^[ result := aBlock value. curtailed := false. result ] > > ensure: [ curtailed ifTrue: [ curtailedBlock value ] ] > > > > BlockClosure>>ensure: ensureBlock > > | result | > > [ result := aBlock value ] ifCurtailed: [ > > ensureBlock value. > > ^result ]. > > "Don't reenter the ensureBlock if it is curtailed!" > > ensureBlock value. > > ^result > > > > > - #ensure: blocks are evaluated in the wrong process, that is the > > > process that calls #terminate not the one that runs the process. This > > > can have very strange effects, if your code depends on the current > > > process. > > > > This can be solved easily. You define a ProcessBeingTerminated > > exception and wrap the process block aBlock into something like this: > > > > BlockClosure>>newProcess > > ^Process > > forContext: self processBlock > > priority: Processor userSchedulingPriority > > > > BlockClosure>>processBlock > > ^[[aBlock > > on: ProcessBeingTerminated > > do: [:sig | > > "If we terminate in the handler, the 'ensure' blocks > > are not evaluated. Instead, if the handler returns, the > > unwinding is done properly." > > sig return]] > > ensure: [self primTerminate]] > > > > > > where #primTerminate is the "hard terminate" that Igor was mentioning. > > > > Process>>primTerminate > > self isActiveProcess ifFalse: [ self error: 'booh' ]. > > thisContext terminateTo: nil. > > self suspend > > > > > > and termination becomes a matter of signaling the exception: > > > > ProcessorScheduler>>isProcessReady: aProcess > > ^aProcess suspendingList == > > (quiescentProcessLists at: aProcess priority) > > > > Process>>isReady > > ^Processor isProcessReady: self > > > > Process>>terminate > > | pbt | > > "A while ago I explained how to use this to properly implement > > Semaphore>>#critical:." > > pbt := ProcessBeingTerminated new. > > self isReady ifFalse: [ pbt semaphore: myList ]. > > self signalException: pbt. > > > > > > You can do whatever you want with this code, it is not taken from GNU > > Smalltalk. > > > > Paolo > > > > For cases, when my process is terminated and i need to execute some > code before(or at the moment) when this happens, better, i think is to > provide a #onTerminated: method for Process, where you can put a > hadler which will be executed whenever process is going to be > terminated. In normal conditions, if process finished w/o explicit > call to #terminate, this code should not be invoked. > > So, you can just write something like that: > > [ ... ] fork onTerminate: [ :p | Transcript show: 'Ouch, i terminated > by..' , p name ] > > or even #addOnTerminate: block , which will add a block to a list of > blocks which will be evaluated when process is terminating. > > Also, i not agree that terminate must be connected with exceptions. > Process provides a ways how to control its execution flow, and its not > an exceptional behavior, its a part of its interface. And i think that > exceptions framework must not be involved here. > Of course, its just my humble opinion :) > > -- > Best regards, > Igor Stasenko AKA sig. > |
Also note, that because we have only the 'soft' terminate, this leads
to appearance of some evil code like: block := [ [ true] whileTrue: [ ... ] ]. [ block value ] ensure: [ block value ]. |
Igor Stasenko wrote:
> Also note, that because we have only the 'soft' terminate, this leads > to appearance of some evil code like: > > block := [ [ true] whileTrue: [ ... ] ]. where? > or even #addOnTerminate: block , which will add a block to a list of > blocks which will be evaluated when process is terminating. > > Also, i not agree that terminate must be connected with exceptions. Also in Java termination trigger an exception. Here, the point is that abrupt termination of a process is a nasty thing, something that's going to cause bugs unless you take special measures (witness the mess that is done in Delay to avoid half-updating the data structures), and the process must know about it! A hard termination must only be used as a last measure, really. If you use these termination blocks heavily, you will soon want to remove them. And sooner or later you realize that the best way to remove them is to store their information somewhere on the context chain which is where #ensure: and exceptions put it. The fact that process execution flow *can* be controlled by an exception is also hinted by a method such as Process>>#signalException:, which will even wake up a process temporarily for the sake of signalling that exception. (Other Smalltalks, such as VA and GNU, have a more generic #queueInterrupt: method that takes a block, but Squeak's solution is also very elegant). Paolo |
On 16/10/2007, Paolo Bonzini <[hidden email]> wrote:
> Igor Stasenko wrote: > > Also note, that because we have only the 'soft' terminate, this leads > > to appearance of some evil code like: > > > > block := [ [ true] whileTrue: [ ... ] ]. > > where? > > > or even #addOnTerminate: block , which will add a block to a list of > > blocks which will be evaluated when process is terminating. > > > > Also, i not agree that terminate must be connected with exceptions. > > Also in Java termination trigger an exception. Here, the point is that > abrupt termination of a process is a nasty thing, something that's going > to cause bugs unless you take special measures (witness the mess that is > done in Delay to avoid half-updating the data structures), and the > process must know about it! A hard termination must only be used as a > last measure, really. > > If you use these termination blocks heavily, you will soon want to > remove them. And sooner or later you realize that the best way to > remove them is to store their information somewhere on the context chain > which is where #ensure: and exceptions put it. > > The fact that process execution flow *can* be controlled by an exception > is also hinted by a method such as Process>>#signalException:, which > will even wake up a process temporarily for the sake of signalling that > exception. (Other Smalltalks, such as VA and GNU, have a more generic > #queueInterrupt: method that takes a block, but Squeak's solution is > also very elegant). > > Paolo One or other way, we need to have control on how to react to process termination, so developer can put a finalization code for such case (not for general case as with #ensure:), because difference, as illustrated with Semaphore>>critical:/wait is great. But even with your case there are pitfalls, i can put the exception catching code and omit 'sig pass', breaking the safe termination flow. [ ] on: ProcessBeingTerminated do: [:sig | do something. " sig pass" ]. I don't know, maybe we can come up with more safe scheme, which provides same semantics and also way to control termination, but be simple at same time and not requiring from developer to know much of details (like putting "sig pass")? Also, i don't know much details on how squeak handles exceptions. Suppose i signaled exception, triggering stack unwinding, but somewhere in the middle i put something in #ensure: block which causes new exception. What happens in this case? Does process stack continue unwinding using exception generated first, or it will be replaced by new one? And for me, still is unclear, how to guard a process which terminates other process to not be terminated until it finishes termination of other process, which is critical to fulfill the 'soft termination' semantics? -- Best regards, Igor Stasenko AKA sig. |
Free forum by Nabble | Edit this page |