[POLL] Process>>terminate

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

[POLL] Process>>terminate

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

Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

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

Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

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

Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

Boris.Gaertner
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.
Agreed, but that is a question of documentation.
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'. :)
Some years ago, when I wrote a tutorial about the MVC user interface
and Smalltalk in general, I learned all these details.

Greetings, Boris

Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

Göran Krampe
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


Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

Paolo Bonzini-2
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


Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

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

Reply | Threaded
Open this post in threaded view
|

RE: [POLL] Process>>terminate

Gary Chambers-4
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.
>


Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

Igor Stasenko
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 ].

Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

Paolo Bonzini-2
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


Reply | Threaded
Open this post in threaded view
|

Re: [POLL] Process>>terminate

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