[squeak-dev] The Trunk: Tools-jar.1161.mcz

Thiede, Christoph Christoph.Thiede at student.hpi.uni-potsdam.de
Mon Jun 13 19:06:30 UTC 2022


Hi Marcel,


thank you for the reply!



> We could not think of a serious issue to use a helper process for termination.


Let's construct a simple example:


m := Morph new.
[m halt]
ensure: [m delete].

"From the debugger, abandon the process."


In the past, first, the debugged process was unwound and after that, the UI was resumed.

With the new change, both will happen concurrently. As Morphic is in general not concurrency-safe, this clearly violates the execution semantics from the debugger IMO.


> It's not quite different to what the debugger itself does and we also use helper processes to, for example, handle CMD+Dot.

I don't see any concurrent UI operations during the opening and closing of the Morphic debugger itself. Also, everything which happens concurrently here is under our explicit control, whereas we cannot know what any domain-specific logic in any ensure block of the debugger process might do.


> > [...] this was visible in the past because the UI was blocked. Now it's no longer possible to see this directly. [...]
>
> The Process Browser will show this very easily later without disturbing the user's flow for the moment. :-)

But that is very much more indirect and often unseen feedback.
In 99% of all cases, unwinding will happen without any noticeable delay.
In the remaining 1%, I argue that there is a high chance that something exceptional and interesting is happening - for instance, some very expensive clean-up code, a deadlock, or some modal operation (see also the examples in the appendix). It seems natural to me that "UI is blocked after pressing terminate" indicates that "termination has not yet completed". I think that users will benefit more from that direct feedback than that they will be glad about "continuing their work a tiny fraction of a second earlier".
We have been very hesitant to use concurrency in general in Squeak until yesterday, and I think this has preserved us from another dimension of complexity and a large number of hard-to-debug issues. (The only exception that comes to my mind are things like Shout background styling with a very small scope). Especially, we have never started to distribute user code to multiple processes, which we are effectively doing with this change. I insist that this will open a can of worms and will lead us into something like "concurrency hell".


Coming from the other side, what is the greatest advantage that this fork could do for us? I could not find any pointer on this in the referenced thread (The Trunk: ToolsTests-ct.107.mcz). The only argument seems to be in the version message:

> Replace termination of the debugged process with an indirect termination via a forked process (otherwise the debugger waits until the debugged process is terminated but if another error is raised during unwind the termination is halted and the assertion fail).

So are we talking about something like this?

[self halt] ensure: [self error].

"Terminate from halt, then proceed from error"

Indeed this requires a user interrupt before seeing the second debugger if we don't fork in #windowIsClosing. However, as this does not happen when you #abandon instead of #terminate, I would rather suspect the new implementation of #terminate here (which I have not yet found the time to study). If this is the only reason for the #fork, I would consider the #fork a workaround for an issue in #terminate. From the perspective of getting the release done, I don't think that this special case (which is anyway kind of hidden via the debugger's window menu) should be prioritized higher than the negative consequences I mentioned above. Also, we don't support this case very well at all at the moment: if you abandon the second debugger, a MessageNotUnderstood: UndefinedObject>>stepToCallee will be raised from Context>>runUntilErrorOrReturnFrom:. Something else to investigate. :-)
(PS: Note to myself: We should have one or two explicit DebuggerTest for this situation.)

Also, note that the new implementation of #windowIsClosing leads to two dangling processes from running all DebuggerTests (nil MNU for interruptedProcess in #windowIsClosing) despite all tests are passing.


tl;dr: I strongly vote for removing that #fork for two reasons: unexpected/unmanageable concurrency in user code, and a bad trade-off between beauty and informativeness.

But I'm always open to your arguments. :-)

Best,
Christoph


Appendix 1 - further observations for terminate/abandon from debugger and comments:


  *   [self halt] ensure: [1 second wait]. "Let the user know that something is happening"
  *   [self halt] ensure: [self inform: #foo]. "! When should the debugger window disappear?
If we had a proper hook for this in SystemWindow, I think that the termination should take place even BEFORE the window is actually deleted. This would make the aforementioned feedback even easier to grasp.
(Asked differently, why is #windowIsClosing sent when the window has been deleted already? Shouldn't it be rather named #windowHasClosed then?)"
  *   [self halt] ensure: [[Project current world doOneCycle.  Processor yield ] repeat]. "Do it, abandon, interrupt, abandon, then watch the process browser. Not a regression, but we have a stale process at [] in Context>>resume: here."
  *   [self halt] ensure: [Transcript showln: Processor activeProcess = Project uiProcess]. "Outputs false after abandon. Not a regression, but this looks suspicious to me. I think we should temporarily set Project >> #uiProcess: back to the interruptedProcess in #windowIsClosing. Unfortunately, this only works in Morphic."

________________________________
Von: Squeak-dev <squeak-dev-bounces at lists.squeakfoundation.org> im Auftrag von Taeumel, Marcel
Gesendet: Montag, 13. Juni 2022 14:50 Uhr
An: squeak-dev
Betreff: Re: [squeak-dev] The Trunk: Tools-jar.1161.mcz

That said, if we discover a serious issue during the next days, we can always make process termination in the debugger synchronous again.

Best,
Marcel

Am 13.06.2022 14:49:05 schrieb Marcel Taeumel <marcel.taeumel at hpi.de>:

> [...] this was visible in the past because the UI was blocked. Now it's no longer possible to see this directly. [...]

The Process Browser will show this very easily later without disturbing the user's flow for the moment. :-)

Best,
Marcel

Am 13.06.2022 14:48:01 schrieb Marcel Taeumel <marcel.taeumel at hpi.de>:

Hi Christoph --

I double-checked with Tom (tobe) on this. We could not think of a serious issue to use a helper process for termination. It's not quite different to what the debugger itself does and we also use helper processes to, for example, handle CMD+Dot.

Best,
Marcel

Am 13.06.2022 14:03:13 schrieb Christoph Thiede <christoph.thiede at student.hpi.uni-potsdam.de>:

Hi Jaromir,

sorry for not responding earlier!

> Replace termination of the debugged process with an indirect termination via a forked process (otherwise the debugger waits until the debugged process is terminated but if another error is raised during unwind the termination is halted and the assertion fail).

Could you elaborate further on that? I have a bad feeling with it. If the termination behaves suspiciously and takes a longer time, or the process never ends (e.g. via a non-local return), this was visible in the past because the UI was blocked. Now it's no longer possible to see this directly. Furthermore, anything might happen during the termination which now happens in a separate process, opening a potential can of worm of concurrency issues (see also "async hell"). Couldn't we simply keep the termination running synchronously? User interrupt will still work if something goes wrong with the termination, but at least the user will notice.

Best,
Christoph


Am 13.06.2022 09:42 schrieb commits at source.squeak.org:

Marcel Taeumel uploaded a new version of Tools to project The Trunk:
http://source.squeak.org/trunk/Tools-jar.1161.mcz

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

Name: Tools-jar.1161
Author: jar
Time: 9 June 2022, 1:57:46.549529 pm
UUID: f3921b2b-5628-104d-98a9-1e99671344a2
Ancestors: Tools-ct.1160

Fix an inconsistent Debugger behavior WRT the new alternative abandon/terminate/destroy modes introduced by Kernel-ct.1434 and Tools-ct.1092.

The suggestion is to make all three termination methods equal in terms of how the chosen termination mode is propagated to #windowIsClosing. Current asymmetrical approach leads to an incorrect debugger termination via #terminate mode.

Replace termination of the debugged process with an indirect termination via a forked process (otherwise the debugger waits until the debugged process is terminated but if another error is raised during unwind the termination is halted and the assertion fail).

Following up a conversation in http://lists.squeakfoundation.org/pipermail/squeak-dev/2022-May/220675.html

Complemented by ToolsTests-jar.111 fixing the failing/incorrects tests in ToolsTests-ct.107

Please review as I'm no debugger expert and this is just my suggestion.

Superseeds (or replaces) Tools-jar.1159 (please kindly remove from Inbox)

=============== Diff against Tools-ct.1160 ===============

Item was changed:
  CodeHolder subclass: #Debugger
+ instanceVariableNames: 'interruptedProcess contextStack contextStackIndex contextStackList receiverInspector receiverInspectorState contextVariablesInspector contextVariablesInspectorState externalInterrupt proceedValue selectingPC savedCursor isolationHead failedProject labelString message untilExpression terminationMethod'
- instanceVariableNames: 'interruptedProcess contextStack contextStackIndex contextStackList receiverInspector receiverInspectorState contextVariablesInspector contextVariablesInspectorState externalInterrupt proceedValue selectingPC savedCursor isolationHead failedProject labelString message untilExpression'
  classVariableNames: 'ContextStackKeystrokes ErrorReportServer FullStackSize InterruptUIProcessIfBlockedOnErrorInBackgroundProcess NotifierStackSize SavedExtent StackSizeLimit WantsAnnotationPane'
  poolDictionaries: ''
  category: 'Tools-Debugger'!

  !Debugger commentStamp: 'mt 12/17/2019 12:19' prior: 0!
  I represent the machine state at the time of an interrupted process. I also represent a query path into the state of the process. The debugger is typically viewed through a window that views the stack of suspended contexts, the code for, and execution point in, the currently selected message, and inspectors on both the receiver of the currently selected message, and the variables in the current context.

  Special note on recursive errors:
  Some errors affect Squeak's ability to present a debugger.  This is normally an unrecoverable situation.  However, if such an error occurs in an isolation layer, Squeak will attempt to exit from the isolation layer and then present a debugger.  Here is the chain of events in such a recovery.

  * A recursive error is detected.
  * The current project is queried for an isolationHead
  * Changes in the isolationHead are revoked
  * The parent project of isolated project is returned to
  * The debugger is opened there and execution resumes.

  If the user closes that debugger, execution continues in the outer project and layer.  If, after repairing some damage, the user proceeds from the debugger, then the isolationHead is re-invoked, the failed project is re-entered, and execution resumes in that world.

  ---

  In September 2019, we added MorphicDebugger and MVCDebugger to untangle framework-specific features in our debugger infrastructure. However, this is just an intermediate step. The overall goal would be to remove those two subclasses again while preserving their functionality. Mostly, MVC and Morphic differ in their GUI-process management. This means that "proceed" and "close" work differently depending on the process that is being debugged. --- One idea is to attach that framework-specific information to the process objects. See Process >> #environmentAt: and #environmentAt:put:. Also see ToolSet's #handle* and #debug* methods.!

Item was changed:
  ----- Method: Debugger>>abandon (in category 'context stack menu') -----
  abandon
+ "close the debugger and terminate the debugged process"
- "abandon the debugger from its pre-debug notifier"

+ terminationMethod := #terminateAggressively.
+ self close
+ !
- self close.!

Item was changed:
  ----- Method: Debugger>>initialize (in category 'initialize') -----
  initialize

  super initialize.

  Smalltalk at: #MessageTally ifPresentAndInMemory: [ :tally |
  tally terminateTimerProcess].

  externalInterrupt := false.
  selectingPC := true.

+ contextStackIndex := 0.
+
+ terminationMethod := #terminateAggressively "debugger's default termination method"!
- contextStackIndex := 0.!

Item was changed:
  ----- Method: Debugger>>terminateProcess (in category 'context stack menu') -----
  terminateProcess
+ "close the debugger and terminate the debugged process"

+ terminationMethod := #terminate.
+ self close
+ !
- interruptedProcess terminate.
- interruptedProcess := nil.
- self close.!

Item was changed:
  ----- Method: Debugger>>windowIsClosing (in category 'initialize') -----
  windowIsClosing
  "My window is being closed; clean up. Restart the low space watcher."

  contextStack := nil.
  receiverInspector := nil.
  contextVariablesInspector := nil.

  interruptedProcess == nil ifTrue: [^ self].
+ [interruptedProcess perform: terminationMethod.
+ interruptedProcess := nil] fork.
- interruptedProcess terminateAggressively.
- interruptedProcess := nil.

  Smalltalk installLowSpaceWatcher.  "restart low space handler"!

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220613/f9c273cf/attachment-0001.html>


More information about the Squeak-dev mailing list