Hi Eliot,
Many thanks for your comments; it'll take me some time to respond properly and with reasonable arguments but let me just briefly touch some points I think you may be mistaken (in the text below): best, ~~~ ^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-12-16T14:20:01-08:00, eliot.miranda@gmail.com wrote:
Hi Jaromir,
I'm responding to this message because you introduce useful
categorisation of different kinds of terminate. I'll respond in context below.
One more naming suggestion: (iii) terminate (~run all unwind blocks) (ii) terminateLessSafely (~skip unwind blocks in progress, run all not-yet-started ones) (i) terminateUnsafely (~kill, run no unwinds)
OK, this is really useful. It provides the straight-forward options we have for process termination. Now let's try and be categorical about unwind blocks.
Unwind blocks are used to maintain invariants. The classic example is Semaphore>>critical:
critical: mutuallyExcludedBlock "Evaluate mutuallyExcludedBlock only if the receiver is not currently in the process of running the critical: message. If the receiver is, evaluate mutuallyExcludedBlock after the other critical: message is finished." <criticalSection> self wait. ^mutuallyExcludedBlock ensure: [self signal]
Given that unwind blocks are used to maintain invariants, any terminate which does (iii), run all unwind blocks, including those that have already been run, is completely broken.
I think this is a misunderstanding: it was never intended to run an unwind block twice; the point was to *finish* unwind blocks that were in the middle of their execution; never re-run.
We can therefore ignore (i). It would run unwinds more than once. The correct terminate is (ii). Skip unwind blocks already in progress. Presumably something very wrong has happened in an unwind block that has not completed. But all unwinds further away from top of stack should and must be run normally. Note that in the normal case there will be no such partial unwinds; it will not occur in correct code. Note further that its extremely unlikely that there will be an unwind within an unwind.
But there's an assumption here. In normal termination, unwind blocks will just run, and the process will terminate cleanly. If it terminates itself then there is no issue other than potential bugs in unwinds (likely during development, rather than normal running). An application that wants to terminate other processes should be written to terminate those processes it needs to only when those processes are at some safe point. I would not spend effort trying to make terminate safe when sent from other processes at arbitrary points. That counts as trying to shoot oneself in the foot.
Fair point, I did it as a learning exercise :)
So terminate boils down to two or three cases, depending on how you look at it. The first case, or pair of cases, is a process that terminates itself or is terminated by another process while at a safe point. In this case any and all unwinds *must* be run. The final case is a process that is stuck in termination, presumably due to a bug in an unwind. This is the only point where we have the choice between ii and i.
So IMO the choice should be between terminate (ii) and terminateUnsafely (i). [I believe that in VisualWorks I called (i) terminateWithExtremePrejudice, after Apocalypse Now].
Current VisualWorks use all three termination modes and unfortunately renamed them to boring: (iii) terminate (ii) terminateUnsafely (i) terminateUnsafelyNow
The semantics is very similar to what's being proposed here though, i.e. #terminate tries to finish all *unfinished* unwind blocks in progress (even nested). #terminateUnsafely unwinds only blocks that haven't started yet and is used as a debugger Abandon action and #terminateUnsafelyNow just kills.
When a process starts to terminate, it is marked somehow as being terminated, running unwind blocks. If the normal terminate is sent to the process while in this state that terminate send should raise an error; attempting to terminate a process twice is an error.
Yes, this another bug I found in current #terminate; it allows teminating again a process being currently terminated and also allows to resume a process that is being terminated. I'll be sending some tests and suggestion how to fix it.
We cannot automatically decide if a process in this state is correctly running unwinds; that would be equivalent to solving the halting problem. Therefore we leave it up to the programmer to decide if, on discovering a terminating process stuck somewhere in running unwinds, they terminateUnsafely.
A key attitude here is to make the common case work well. Don't let the perfect be the enemy of the good. In this case there is no perfect solution; arbitrary errors *can* occur, *potentially*, in termination, but in practice are extremely rare. Keeping the system simple, comprehensible and maintainable is a prime directive. If a programmer can, for example, use the debugger to manually run undins that have not yet run because of a stuck unwind, then there is no absolute need for the system to handle this situation gracefully. A developer that gets themselves in hot water creating errors iu unwinds can get themselves out using the debugger.
So for me, KISS.
Agreed, absolutely... The only BUT from me is to fix bugs making learning a miserable experience :)
Just support something that guards against multiple termination,
As I said, I'll send tests and a solution for your consideration.
and in the debugger, reveals clearly where a process is in the course of running unwinds.
And I really *don'* like the idea of running terminate in a separate process.
I'm sorry to hear that indeed :D
This has bad performance implications. Most terminations can be done in the process itself; that should be the expectation.
Marcel wrote:
How would you summarize the differences between #terminate,
#terminateAggressively, and #kill, each with 1-2 sentences? :-)
Inspired by VW:
(iii) "Terminate the process and allow all unwind blocks to run, even if they are currently in progress." This is the behavior we expect when terminating a healthy process.
(ii) "Terminate the process and run all not-yet-started unwind blocks; if the process is in the middle of an unwind block, then that unwind block will not be completed, but subsequent unwind blocks will be run." This is the behavior we expect when we close the debugger in a situation when the unwind block may not be able to complete, either because of an error, or because it is hung. In most other circumstances, we would want to use #terminate, which allows unwind blocks in progress to complete.
(i) "Terminate the process without attempting to run any of its unwind blocks." This is the behavior we might need in very rare circumstances when modes (iii) and (ii) fail.
Changeset:
Please find the attached changeset in which I have changed the names
accordingly and also have removed the kill/destroy operation from the UI as per request of Marcel. I aggree with his argument that destroy is a kind of private operation that affects the interna of the process and should be only used for troubleshooting, so we should not give this hack too much visibility.
Nice! I liked your Abandon button with options more but this is a start. Kill option: do you mean no access to the kill option from the Process browser either? Process browser is a very troubleshooting tool :)
And finally, If I may - I'd slightly prefer not adding another #terminate: to the Process class only to serve a single sender but rather inline it and address the two termination modes directly in the ProcessBrowser class:
ProcessBrowser class >> #terminateProcess: aProcess aggressively: aggressive
aProcess ifNil: [^ self]. self suspendedProcesses removeKey: aProcess ifAbsent: [].
aProcess terminate: aggressive.
aggressive ifFalse: [aProcess terminate] ifTrue: [aProcess terminateAggressively]
Concerning your #unwindTo: issue - not sure if this actually related to
this thread? Actually I've lost the overview which terminate mode - (ii) or (iii) - you are referring to, maybe we should discuss this in a separate conversation. :-)
Yes, it's a bug and deserves its own thread.
=== Summary:
TLDR: Concerning our next steps - consider this a small to-do list:
- [ ] We want to implement three modi of process termination:
- [x] (i) is not yet implemented but should be easy to implement
- [x] (ii) is in my proposal from [2], maybe needs further refinement
- [ ] (iii) is pretty much what we have in Process >> #terminate in
the current Trunk. Is there anything still missing yet?
- [ ] Decide on names for (ii) and (iii) - see above
- [ ] Decide on UI integration into debugger - see above
Add this one?
- [ ] Decide on UI integration into Process Browser
Regarding Process >> #terminate - the current Trunk version is not final; the final version is the Inbox's Kernel-jar.1414.
More or less done! :)
Thanks,
Jaromir
On 2021-08-30T12:51:21+02:00, christoph.thiede at student.hpi.uni-potsdam.de wrote:
Hi Marcel, hi all,
How would you summarize the differences between #terminate,
#terminateAggressively, and #kill, each with 1-2 sentences? :-)
Let's keep using the numbers from above instead, as the names are only
my imperfect proposals:
(i) run no unwind contexts (harshest possible way; currently only
achievable by doing "suspendedContext privSender: nil" prior to terminating). You would need this very rarely, if at all, for troubleshooting.
(ii) run not-yet started unwind contexts (this is what I proposed in
fix-Process-terminate.1.cs [1]). This is the behavior we expect from the Abandon button in a debugger. I think?
(iii) run all unwind contexts, including those that already have been
started (this is the most friendly way that you implemented in #terminate recently). This is the behavior we expect when terminating a healthy process.
Here are some ideas for better names:
(i) #destroy (as opposed to #kill, this sounds more like the cheap and
unreactable operation it is)
(ii) #terminateAggressively, which redirects to #terminate: true (iii) #terminate, which redirects to #terminate: false
Please find the attached changeset in which I have changed the names
accordingly and also have removed the kill/destroy operation from the UI as per request of Marcel. I aggree with his argument that destroy is a kind of private operation that affects the interna of the process and should be only used for troubleshooting, so we should not give this hack too much visibility.
Best, Christoph
Sent from Squeak Inbox Talk
On 2021-08-23T13:59:04+02:00, marcel.taeumel at hpi.de wrote:
Hi Christoph --
How would you summarize the differences between #terminate,
#terminateAggressively, and #kill, each with 1-2 sentences? :-)
Best, Marcel Am 23.08.2021 13:18:40 schrieb christoph.thiede at
student.hpi.uni-potsdam.de <christoph.thiede at student.hpi.uni-potsdam.de
:
Hi all, hi Jaromir,
based on the to-do list from my previous message, I have created a
changeset that adds Process >> #terminateAggressively (ii) and Process >> #kill (i) and integrates them into the process browser and the debugger. Most importantly, the following snippet will inform "nil" now instead of "ZeroDevision" after abandoning the error:
����[[x1 := nil] ensure: [x1 := (2 / 0)]] ��������ensure: [self inform: x1 asString].
With this changeset, I would be fully happy again with the
functionality of the debugger. :-) What remains to do is the following:
- [ ] We want to implement three modi of process termination:
- [x] (i) - see #kill
- [X] (ii) - see #terminateAggressively
- [ ] (iii) is pretty much what we have in Process >> #terminate in
the current Trunk. Is there anything still missing yet?
- [ ] Decide on names for (ii) and (iii) - see my previous message
- [ ] Decide on UI integration into debugger - proposal exists in
Process-terminateAggressively.1, but open for criticism
- [ ] Test #terminateAggressively (actualy, I don't have a good of
overview of all your new #terminate tests - probably it would be easiest to wait until they have arrived in the Trunk and then create copies of them to test #terminateAggressively analogously :D)
Best, Christoph
Sent from Squeak Inbox Talk [
https://github.com/hpi-swa-lab/squeak-inbox-talk]
On 2021-08-22T16:07:54+02:00, christoph.thiede at
student.hpi.uni-potsdam.de wrote:
Hi Jaromir,
back from vacation, *finally* I have found some time to return back
to this pleasant problem! :-)
Great, it sounds like we're already agreeing pretty much, just let
me address some small points:
Debugger - Abandon could be the lightweight version you proposed.
Why not
have a proper Abandon button for it? ����The right click menu on a context could offer the Kill option
(next to
'peel to first like this'); no button necessary. Now the question is what should be under the "window close"
red-circle-x -
heavyweight terminate? I'm thinking this scenario: if the debugger
returns
after closing the window you start thinking what happened and use
Abandon;
if it still doesn't help you go right-click and kill it?
My usual scenario is (limited experience however): look at
something in the
debugger (on a healthy process) and close the window (i.e. full
termination
is appropriate and I'd even say preferable). If something goes
wrong - then
I'd welcome a hint there are options - thus the proper Abandon
button - what
do you think?
Hm, I would rather say that closing the window should have the same
effect as pressing "abandon" - personally, I am very much used to this and we do not really have good options to communicate a different behavior of the close button. I think presenting the new behaviors Kill (i) and (iii) in some kind of menu would be less confusing.
A kill item in the stack trace menu sounds fine for now (even though
killing is not actually related to the selected context). In the long term, I would like to introduce menu buttons in the fashion of [1] in the Morphic UI (Marcel is currently reviewing my proposal for this), then we could move Kill into the Abandon menu, but for now, your proposal sounds perfectly fine to me. :-)
look at something in the debugger (on a healthy process) and close
the window (i.e. full termination is appropriate and I'd even say preferable).
Can you help me please, if the process is healthy (so no unwind
context is lying on the stack), would there be any difference between the different terminate modi (ii) and (iii)?
> But I don't have any good idea for version (ii) yet. Call it
#abandon like
> in the debugger? Then again, #abandon is rather a verb from the
Morphic
> language. Further possible vocables (according to my synonym
thesaurus)
> include #end, #stop, #finish, #unwind, #abort, #exit. Please
help... :-)
I'd probably go with something like #terminateLight because it's a
proper
process termination including unwinds except the ones currently in
progress
- so it is a light version of #terminate :) I've checked
VisualWorks: they
chose #terminateUnsafely for this type of termination which I
don't like
much, it sounds too negative; the real meaning is rather #terminateAsSafelyAsPossibleGivenTheCircumstances ;).
Hm, "light" reminds me of "friendly" and (iii) would certainly be
more friendly (giving the process more freedom for cleaning things up). Difficult ...
What about #terminateNow for (ii) and #terminate for (iii), slightly
related to #updateStyle and #updateStyleNow? Since (iii) might continue with the execution of the current halfway-executed context, (ii) is more urgent.
Another option would be "terminate: aggressive", analogously to
"cleanup: aggressive" on Behavior where #terminate would fall back to "terminate: false". But this would make the new mode a little bit worse accessible.
Concerning your #unwindTo: issue - not sure if this actually related
to this thread? Actually I've lost the overview which terminate mode - (ii) or (iii) - you are referring to, maybe we should discuss this in a separate conversation. :-)
TLDR: Concerning our next steps - consider this a small to-do list:
- [ ] We want to implement three modi of process termination:
����- [ ] (i) is not yet implemented but should be easy to implement ����- [ ] (ii) is in my proposal from [2], maybe needs further
refinement
����- [ ] (iii) is pretty much what we have in Process >> #terminate
in the current Trunk. Is there anything still missing yet?
- [ ] Decide on names for (ii) and (iii) - see above
- [ ] Decide on UI integration into debugger - see above
Further answers following soon.
Best, Christoph
[1]
https://user-images.githubusercontent.com/48567372/60924070-4472fe80-a26e-11...
[2]
http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-May/215608.html (fix-Process-terminate.cs)
Sent from Squeak Inbox Talk
On 2021-05-29T14:31:13-05:00, m at jaromir.net wrote:
Hi Christoph,
> Jaromir, your proposal to provide multiple selectors for
modeling separate
> modes of termination sounds like a very good idea to me. But how
many
> different modes do we actually need? So far I can count three
modes:
> > (i) run no unwind contexts (harshest possible way; currently only > achievable by doing "suspendedContext privSender: nil" prior to > terminating) > (ii) run not-yet started unwind contexts (this is what I
proposed in
> fix-Process-terminate.1.cs [1]) > (iii) run all unwind contexts, including those that already have
been
> started (this is the most friendly way that you implemented in
#terminate
> recently)
I think this is it.
Litereally minutes ago had to use privSender: nil to get rid of a
debugger
:) Fully terminate really is too strong to recover from fatal
errors.
> ... my point here is: Proceeding from an error almost always
doesn't seem
> "right". :-) It is always a decision by the debugging programmer
to
> override the default control flow and switch to the "next
plausible
> alternative control flow", i.e., resume as if the error would
have never
> been raised.
yes - I'd add: even an error may quite often be completely benign,
like
'Transcript show: 1/0' - possibly a typo so you just may want to
Proceed or
fully terminate. In case the error damages a whole subsequent
chain of
events, you're absolutely right a full termination seems a silly
option and
a light version of terminate may be the most appropriate.
So I fully agree the decision which termination mode it is stays
with the
user - so I'm all for giving the user the choices you suggested.
> \1. Which mode should we use in which situations? > > I think this debate could benefit from a few more concrete usage > scenarios. I'm just collecting some here (thinking aloud): > > - Process Browser: We can provide multiple options in the
process menu.
> - Debugger: I agree with you that Abandon should always run
not-yet
> started unwind contexts but never resume halfway-executed unwind
contexts.
> So this maps to to mode (ii) from above. > - Skimming through most senders of #terminate in the image,
they often
> orchestrate helper processes, deal with unhandled errors or
timeouts, or
> do similar stuff - usually they should be very fine with the
friendly
> version of #terminate, i.e. mode (iii) from above. I think. > - Regarding option (1), I think you would need it extremely
seldom but
> maybe in situations like when your stack contains a loop, your
unwind
> contexts will cause a recursion/new error, or you deliberately
want to
> prevent any unwind context from running. No objections against
adding a
> small but decent button for this in the debugger. :-) > > Would you agree with these behaviors? Maybe you can add further
examples
> to the list?
Yes
Process Browser - the right click menu could provide all options
Debugger - Abandon could be the lightweight version you proposed.
Why not
have a proper Abandon button for it? ����The right click menu on a context could offer the Kill option
(next to
'peel to first like this'); no button necessary. ����Now the question is what should be under the "window close"
red-circle-x -
heavyweight terminate? I'm thinking this scenario: if the debugger
returns
after closing the window you start thinking what happened and use
Abandon;
if it still doesn't help you go right-click and kill it?
My usual scenario is (limited experience however): look at
something in the
debugger (on a healthy process) and close the window (i.e. full
termination
is appropriate and I'd even say preferable). If something goes
wrong - then
I'd welcome a hint there are options - thus the proper Abandon
button - what
do you think?
> \2. How should we name them? > > Direct proposal: (i) #kill and (iii) #terminate. > After looking up the original behavior of #terminate in Squeak
5.3, I
> think it would be consistent to resume all halfway-executed
unwind
> contexts in this method. So yes, I also withdraw my criticism
about
> #testNestedUnwind. :-) > > But I don't have any good idea for version (ii) yet. Call it
#abandon like
> in the debugger? Then again, #abandon is rather a verb from the
Morphic
> language. Further possible vocables (according to my synonym
thesaurus)
> include #end, #stop, #finish, #unwind, #abort, #exit. Please
help... :-)
I'd probably go with something like #terminateLight because it's a
proper
process termination including unwinds except the ones currently in
progress
- so it is a light version of #terminate :) I've checked
VisualWorks: they
chose #terminateUnsafely for this type of termination which I
don't like
much, it sounds too negative; the real meaning is rather #terminateAsSafelyAsPossibleGivenTheCircumstances ;).
I'm wondering whether #unwindTo: (used ony by Generator) is bugged
(with
regard to dealing with non-local returns), and could be
fixed/unified with
your approach. Look at these examples:
p := [[Processor activeProcess suspend] valueUninterruptably] fork. Processor yield. p suspendedContext unwindTo: nil
or
p := [[:exit | [Processor activeProcess suspend] ensure: [exit
value]]
valueWithExit] fork. Processor yield. p suspendedContext unwindTo: nil
If you do `p terminate` instead of `p suspendedContext unwindTo:
nil`, it
works fine, but #unwindTo causes a block cannot return error - I
think it's
the same bug all over again :) #value evaluates the non-local
return on the
wrong stack...
Regarding our cannot return discussion - I have to think about it
and I'll
post my reply later in [1] to keep it separate :)
Thanks again and regards,
[1]
http://forum.world.st/The-Inbox-Kernel-ct-1405-mcz-td5129706.html#a5130114
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html