The Inbox: Kernel-jar.1376.mcz

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

The Inbox: Kernel-jar.1376.mcz

commits-2
A new version of Kernel was added to project The Inbox:
http://source.squeak.org/inbox/Kernel-jar.1376.mcz

==================== Summary ====================

Name: Kernel-jar.1376
Author: jar
Time: 28 February 2021, 7:58:42.007459 pm
UUID: d063e740-7836-a94a-9796-488741d2b2b6
Ancestors: Kernel-codefrau.1374

Fix Process >> #isTerminated inconsistent behavior - for discussion

=============== Diff against Kernel-codefrau.1374 ===============

Item was changed:
  ----- Method: Process>>isTerminated (in category 'testing') -----
  isTerminated
  "Answer if the receiver is terminated, or at least terminating."
  self isActiveProcess ifTrue: [^ false].
  ^suspendedContext isNil
+  or: ["If the suspendedContext is the bottomContext and the pc is at the endPC,
+ then there is nothing more to do."
-  or: ["If the suspendedContext is the bottomContext it is the block in Process>>newProcess.
-   If so, and the pc is at the endPC, the block has already sent and returned
-   from value and there is nothing more to do."
  suspendedContext isBottomContext
+ and: [suspendedContext pc >= suspendedContext endPC
+ or: [suspendedContext closure
- and: [suspendedContext closure
  ifNil: [suspendedContext methodClass == Process
  and: [suspendedContext selector == #terminate]]
+ ifNotNil: [false]]]]!
- ifNotNil: [suspendedContext pc >= suspendedContext closure endPC]]]!


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Kernel-jar.1376.mcz

Jaromir Matas
Hi, the following example shows a slight inconsistency in the process
terminantion logic:

   p := Process forContext: ([] asContextWithSender: thisContext) priority:
40

   p isTerminated   --> false

   p terminate

   p isTerminated   --> false


I'm aware the example is nonsensical but I guess the terminate =>
isTerminated logic should be followed anyway.

I propose the following change in the #isTerminated condition. The idea is
if suspendedContext is the bottomContext and pc >= endPC then it should be
considered terminated regardless of whether there is or isn't a closure. The
rest of the condition remains intact. All tests green.

I also suggest a new wording of the comment (the bottomContext block doesn't
necessarily need to be the block in BlockClosure>>newProcess so I removed
the note).

The suggested version:

isTerminated
        "Answer if the receiver is terminated, or at least terminating."
        self isActiveProcess ifTrue: [^ false].
        ^suspendedContext isNil
          or: ["If the suspendedContext is the bottomContext and the pc is at the
endPC,
                then there is nothing more to do."
                suspendedContext isBottomContext
                and: [suspendedContext pc >= suspendedContext endPC
                                or: [suspendedContext closure
                                                ifNil: [suspendedContext methodClass == Process
                                                        and: [suspendedContext selector == #terminate]]
                                                ifNotNil: [false]]]]




-----
^[^ Jaromir
--
Sent from: http://forum.world.st/Squeak-Dev-f45488.html

^[^ Jaromir
Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Kernel-jar.1376.mcz

Eliot Miranda-2
Hi Jaromir,

On Sun, Feb 28, 2021 at 11:08 AM Jaromir Matas <[hidden email]> wrote:
Hi, the following example shows a slight inconsistency in the process
terminantion logic:

   p := Process forContext: ([] asContextWithSender: thisContext) priority:
40

   p isTerminated   --> false

   p terminate

   p isTerminated   --> false


I'm aware the example is nonsensical but I guess the terminate =>
isTerminated logic should be followed anyway.

I propose the following change in the #isTerminated condition. The idea is
if suspendedContext is the bottomContext and pc >= endPC then it should be
considered terminated regardless of whether there is or isn't a closure. The
rest of the condition remains intact. All tests green.

I also suggest a new wording of the comment (the bottomContext block doesn't
necessarily need to be the block in BlockClosure>>newProcess so I removed
the note).

The suggested version:

isTerminated
        "Answer if the receiver is terminated, or at least terminating."
        self isActiveProcess ifTrue: [^ false].
        ^suspendedContext isNil
          or: ["If the suspendedContext is the bottomContext and the pc is at the endPC,
                then there is nothing more to do."
                suspendedContext isBottomContext
                and: [suspendedContext pc >= suspendedContext endPC
                                or: [suspendedContext closure
                                                ifNil: [suspendedContext methodClass == Process
                                                        and: [suspendedContext selector == #terminate]]
                                                ifNotNil: [false]]]]



I like this.  But isn't this a little bit better?

isTerminated
"Answer if the receiver is terminated, or at least terminating."
self isActiveProcess ifTrue: [^ false].
^suspendedContext isNil
 or: ["If the suspendedContext is the bottomContext and the pc is at the endPC, 
then there is nothing more to do."
suspendedContext isBottomContext
and: [suspendedContext pc >= suspendedContext endPC 
or: [suspendedContext closure isNil
and: [suspendedContext methodClass == Process
and: [suspendedContext selector == #terminate]]]]]
I'm also tempted to state in a comment why being in other than a block in Process>>#terminate implies the methods is essentially done terminating.  And there in lies the rub, to quote Shakespeare.  Would a hack like adding a first temporary in Process>>#terminate called e.g. terminationStatus and having Process>>terminate assign to it when termination is essentially complete be better?

e.g.

isTerminated
"Answer if the receiver is terminated, or at least terminating."
self isActiveProcess ifTrue: [^ false].
^suspendedContext isNil
  or: ["If the suspendedContext is the bottomContext and the pc is at the endPC, 
then there is nothing more to do."
suspendedContext isBottomContext
and: [suspendedContext pc >= suspendedContext endPC 
or: [suspendedContext closure isNil
and: [suspendedContext methodClass == Process
and: [suspendedContext selector == #terminate
and: [(suspendedContext localAt: 1) == #terminated]]]]]]


terminate 
"Stop the process that the receiver represents forever.
Unwind to execute pending ensure:/ifCurtailed: blocks before terminating.
If the process is in the middle of a critical: critical section, release it properly.
 N.B. terminationStatus is for the benefit of Process>>#isTerminated."

| terminationStatus ctxt unwindBlock oldList |
self isActiveProcess ifTrue:
[ctxt := thisContext.
[ctxt := ctxt findNextUnwindContextUpTo: nil.
 ctxt ~~ nil] whileTrue:
[(ctxt tempAt: 2) ifNil:
["N.B. Unlike Context>>unwindTo: we do not set complete (tempAt: 2) to true."
unwindBlock := ctxt tempAt: 1.
thisContext terminateTo: ctxt.
unwindBlock value]].
>>> terminationStatus := #terminated.
thisContext terminateTo: nil.
self suspend.
"If the process is resumed this will provoke a cannotReturn: error.
Would self debug: thisContext title: 'Resuming a terminated process' be better?"
^self].

"Always suspend the process first so it doesn't accidentally get woken up.
N.B. If oldList is a LinkedList then the process is runnable. If it is a Semaphore/Mutex et al
then the process is blocked, and if it is nil then the process is already suspended."
oldList := self suspend.
suspendedContext ifNotNil:
["Release any method marked with the <criticalSection> pragma.
 The argument is whether the process is runnable."
self releaseCriticalSection: (oldList isNil or: [oldList class == LinkedList]).

"If terminating a process halfways through an unwind, try to complete that unwind block first."
(suspendedContext findNextUnwindContextUpTo: nil) ifNotNil:
[:outer|
(suspendedContext findContextSuchThat:[:c| c closure == (outer tempAt: 1)]) ifNotNil:
[:inner| "This is an unwind block currently under evaluation"
suspendedContext runUntilErrorOrReturnFrom: inner]].

>>> terminationStatus := #terminated.
ctxt := self popTo: suspendedContext bottomContext.
ctxt == suspendedContext bottomContext ifFalse:
[self debug: ctxt title: 'Unwind error during termination'].
"Set the context to its endPC for the benefit of isTerminated."
ctxt pc: ctxt endPC]

-----
^[^ Jaromir
--
Sent from: http://forum.world.st/Squeak-Dev-f45488.html

_,,,^..^,,,_
best, Eliot


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Kernel-jar.1376.mcz

Jaromir Matas
Hi Eliot,

>
> I like this.  But isn't this a little bit better?
>
> isTerminated
>      "Answer if the receiver is terminated, or at least terminating."
>       self isActiveProcess ifTrue: [^ false].
>      ^suspendedContext isNil
>        or: ["If the suspendedContext is the bottomContext and the pc is at
> the endPC,
>              then there is nothing more to do."
>              suspendedContext isBottomContext
>              and: [suspendedContext pc >= suspendedContext endPC
>                      or: [suspendedContext closure isNil
>                              and: [suspendedContext methodClass == Process
>                              and: [suspendedContext selector ==
> #terminate]]]]]

delightful! Thanks ;)


Just for my curiosity - would this be considered a bad style (or is a math
like logical expression better):


isTerminated
        "Answer if the receiver is terminated, or at least terminating.
        If the suspendedContext is the bottomContext and the pc is at the endPC,
        then there is nothing more to do.
        If an active process is terminated, it means it was suspended inside the
        #terminate method leaving pc < endPC, so it needs to be caught separately"
       
        self isActiveProcess ifTrue: [^false].
        suspendedContext ifNil: [^true].
        suspendedContext isBottomContext ifFalse: [^false].
        suspendedContext pc >= suspendedContext endPC ifTrue: [^true].
        ^suspendedContext closure isNil
                and: [suspendedContext methodClass == Process
                and: [suspendedContext selector == #terminate]]


> I'm also tempted to state in a comment why being in other than a block in
> Process>>#terminate implies the methods is essentially done terminating.
> And there in lies the rub, to quote Shakespeare.  


My attempted explanation in the comment above but I'm not sure...


> Would a hack like adding a first temporary in Process>>#terminate called
> e.g. terminationStatus and having Process>>terminate assign to it when
> termination is essentially complete be better?
>
> e.g.
>
> isTerminated
>      "Answer if the receiver is terminated, or at least terminating."
>       self isActiveProcess ifTrue: [^ false].
>      ^suspendedContext isNil
>        or: ["If the suspendedContext is the bottomContext and the pc is at
> the endPC,
>              then there is nothing more to do."
>              suspendedContext isBottomContext
>              and: [suspendedContext pc >= suspendedContext endPC
>                      or: [suspendedContext closure isNil
>                              and: [suspendedContext methodClass == Process
>                              and: [suspendedContext selector == #terminate
>                              and: [(suspendedContext localAt: 1) ==
> #terminated]]]]]]

>

I tried to figure out whether you just wanted to detect termination status
as early as possible or also something else...

I guess the condition should be (not tested):

isTerminated
        "Answer if the receiver is terminated, or at least terminating."
        self isActiveProcess ifTrue: [^ false].
        ^suspendedContext isNil
          or: ["If the suspendedContext is the bottomContext and the pc is at the
endPC,
                then there is nothing more to do."
                suspendedContext isBottomContext
                and: [suspendedContext pc >= suspendedContext endPC
                        or: [(suspendedContext localAt: 1) == #terminated]]]

... because `suspendedContext closure isNil` etc. only catches terminating a
suspended process. So to also cover terminating the active process we can, I
guess,  safely remove this condition. I'm not sure though about the localAt:
1 - what if other non-terminating process had it with the same #terminated -
is it ok?


Also - #isSuspended suffers with the same problem as #isTerminated; I'll
take a look at it next.

Thanks,

Best regards









-----
^[^ Jaromir
--
Sent from: http://forum.world.st/Squeak-Dev-f45488.html

^[^ Jaromir
Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Kernel-jar.1376.mcz

Jaromir Matas
Hi Eliot,

To clarify some confusion in my last message I tested your idea: it works as
expected for an active process termination. In case of suspended process
termination the suspendedContext is actually not the one of
Process>>terminate so it doesn't do anything except the debugger doesn't
like it and gets stuck before sending #bottomContext following the
terminationStatus assignment (weird).

I also had to change the isTerminated condition because the context becomes
bottom one step after assigning the terminationStatus (all tests are green
but...):


isTerminated
        "Answer if the receiver is terminated, or at least terminating.
        If an active process has terminated, it means it was suspended inside the
        #terminate method and this situation needs to be caught separately.
        If the suspendedContext is the bottomContext and the pc is at the endPC,
        then there is nothing more to do."

        self isActiveProcess ifTrue: [^false].
        ^suspendedContext isNil
        or: [suspendedContext pc isNil]
        or: [suspendedContext methodClass == Process
                and: [suspendedContext selector == #terminate
                and: [(suspendedContext tempAt: 1) == #terminated]]]
        or: [suspendedContext isBottomContext
                and: [suspendedContext pc >= suspendedContext endPC]]


Thanks,



-----
^[^ Jaromir
--
Sent from: http://forum.world.st/Squeak-Dev-f45488.html

^[^ Jaromir
Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Kernel-jar.1376.mcz

Jaromir Matas
For consideration:

isSuspended
        "A process is suspended if it has been suspended with the suspend
primitive.
        It is distinguishable from the active process and a terminated process by
        having a non-nil suspendedContext that is either not the bottom context
        or has not reached its endPC."
        ^myList isNil
                and: [suspendedContext notNil
                and: [self isTerminated not]]

>>> new suggested comment:

        "A process is suspended if it has non-nil suspended context (i.e. is new or
        previously suspended with the suspend primitive), is not terminated and
        not waiting in a queue (Processor or Semaphore/Mutex/etc., i.e. is not
        runnable or blocked)."


The current #isSuspended suffers from the same inconsistency, i.e. answers
true even after sending terminate (in rare cases). The fix is either modify
the current condition mirroring the changes in #isTerminated or rephrase it
using #isTerminated. I'd prefer the latter but I can't foresee all
consequences. Advice welcome :)

regards,




-----
^[^ Jaromir
--
Sent from: http://forum.world.st/Squeak-Dev-f45488.html

^[^ Jaromir