[squeak-dev] Bug in Process>>#terminate | Returning from unwind contexts

Jaromir Matas m at jaromir.net
Thu Mar 25 08:15:58 UTC 2021


> Again from a rather conceptual point of view: How useful is it to terminate
> a process "remotely", i.e. simulating it from the outside? 
>
> Why can't we just activate the relevant return context
> on the process (see #popTo:) and then resume it again? 
>
> But conceptionally, wouldn't such
> an approach be closer to the conceptual meaning of terminating a process?
>
> Just thinking aloud ... I'm looking forward to your opinion! :-)


Hi Christoph,

thanks! yes, I've been thinking along the same lines :) 

I think #runUntilErrorOrReturnFrom is not the cause of the problem though;
#popTo is! It uses #evaluate:onBehalf:, the indirect way to unwind the
terminated process. I still don't understand why we should want to unwind
indirectly instead of just making the suspended process active and have it
unwound itself... I created a new top context for it effectively sending it
back to #terminate as an active process with the #activePriority, and
replaced #popTo (and the remainder of #terminate) with:

		suspendedContext := Context
			sender: suspendedContext 
			receiver: self 
			method: (Process>>#terminate) 
			arguments: {}.
		self priority: Processor activePriority.
		self resume.
		Processor yield

See the whole #terminate method further below. 

All Process tests are green. It seems to resolve the 'easy' issues. 

Your non-local example seems to work as well with one caveat: It leaves the
image with two UI processes, both in the prio 40 queue, alternating control.

A question is: what is the semantics of the non-local return in

	[self error] ensure: [^2]
or
	[self error] valueUninterruptably

And I think it is exactly what it does now - it escapes the termination! I
think it's called "cheating death"... A powerful and meaningful feature ;)
#valueUninterruptably says: don't let *anything* interrupt me, not even a
debugger Abandon.

More precisesly: `self error` causes an error window is opened and a new UI
process is spawned. Abandon then causes the new UI process sends #terminate
to the previous UI process running the do-it. #terminate then makes the old
UI execute #terminate on itself but during the unwind the non-local return
(^2) causes both the unwind loop and #terminate are jumped over back to the
original do-it and the old UI continues as if nothing happened :) The new
UI, in the meantime, has finished closing the Error (debugger) window and
continues it's everyday UI cycles. So we end up with two normal healthy UIs. 

Now my question is: how to remove the extra UI?

One idea I haven't explored yet is why run a do-it withing the UI and not as
a separate process? In this scenario the non-local return would continue or
return from the do-it instead of returning inside the UI itself causing it
survive the expected termination.

But that's a big change. Would there be anything simpler?

As for #runUntilErrorOrReturnFrom: I haven't studied it thorougly yet but my
understanding is it's supposed to deal with unwind blocks caught in the
middle of their unwinding - e.g. when abandoning the debugger (but I'd like
to see some real examples first and I have to think about your remarks).


As for #terminate: I had to do one modification the original unwind code:
according to the N.B. note we do not set `complete` variable to true - but I
can't see why: was a necessity for something or just saving a line? (I hope
the latter but can get around the former too)

>>> Why???		["N.B. Unlike Context>>unwindTo: we do not set complete (tempAt:
>>> 2) to true."
>>> 			 ctxt tempAt: 2 put: true.        "added, otherwise Abandon doesn't
>>> work"

Without it the debugger Abandon in this example

	[self error: 'e1'] ensure: [self error: 'e2']

wouldn't work: without the `complete` mark the Abandon (#windowIsClosing)
sends #terminate, the process runs unwind, opens a debugger, Abandon sends
#terminate etc.


Process >> 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."

	| ctxt unwindBlock oldList |
	self isActiveProcess ifTrue:
		[ctxt := thisContext.
		 [ctxt := ctxt findNextUnwindContextUpTo: nil.
		  ctxt ~~ nil] whileTrue:
			[(ctxt tempAt: 2) ifNil:
>>> Why???		["N.B. Unlike Context>>unwindTo: we do not set complete (tempAt:
>>> 2) to true."
>>> 			 ctxt tempAt: 2 put: true.        "this is added otherwise Abandon
>>> doesn't work"
				 unwindBlock := ctxt tempAt: 1.
				 thisContext terminateTo: ctxt.
				 unwindBlock value]].
		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]].

>>>		suspendedContext := Context      "now top context"
			sender: suspendedContext 
			receiver: self 
			method: (Process>>#terminate) 
			arguments: {}.
		self priority: Processor activePriority.
		self resume.
>>>		Processor yield          "important when using activePriority"
	]



>
> > This scenario, however uncovers a weakness in the #isTerminated,
> reporting
> > the currently executing process with effectiveProcess assigned, as
> > terminated and as a result preventing Process Browser to reveal such a
> > pathological situation (which is precisely when you need to see it).
>
> I don't think so ... The violated invariant in this situation is that the
> effectiveProcess variable has not been reset. In general, Process >>
> #isTerminated should indeed consider the effectiveProcess. Debugging
> "Processor activeProcess isTerminated" should always work exactly like
> executing it outside the debugger. :-)
>

Yes, 100% agreed, but that's not the point I'm making :) I'm saying such a
situation (where the effective process has not been reset) is rare but
dangerous and we need to be aware of it rather sooner than later - i.e. for
instance we need to see in the Process Browser there's one more process left
- but now it's hiding as invisible because #isTerminated reports it as
terminated.

To demonstrate how dangerous the scenario is, do-it:

	[self error] ensure: [^2]

and Abandon the ensuing Error window. Now the image becomes 'corrupted' or
'unstable' in the sense that the following do-it will hang the image:

	Semaphore new waitTimeoutMSecs: 50

If you try Test Runner the following Process and Semaphore tests fail and
may even crash the image:

	testWaitTimeoutMSecs
	testProcessFaithfulSimulation
	testProcessFaithfulRunning
	testEvaluateOnBehalfOf

The reason is after abandoning the error the following expressions are no
longer true and some tests get confused:

	Processor activeProcess = Project current uiProcess  "-> FALSE"
	Processor activeProcess = (Processor instVarNamed: 'genuineProcess')  "->
FALSE"

We can observe the anomaly indirectly by testing these expressions but what
I'm suggesting is the Process Browser should be the place to reveal such a
situation.

To summarize: I admit such situations are rare so it's not a pressing issue. 


I look forward to your opinion about the #terminate experiment and the twin
UI processes :)






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


More information about the Squeak-dev mailing list