Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Computation has been terminated" when terminating a process while resuming from an exception #112

Open
LinqLover opened this issue Jan 15, 2024 · 12 comments
Labels
base system [SCOPE] Squeak's basic (language) concerns such as Kernel, Collections, Graphics, Network bug [WHAT] Something isn't working as expected. Automated tests beneficial. :-

Comments

@LinqLover
Copy link
Contributor

Hi @isCzech, all,

I've discovered another situation while I get a "Computation has terminated" error from Process>>#terminate. The entire situation is pretty complex, depends on some experimental code that I have not yet commited to third-party packages, and occurs only very sporadically (that terminate is maybe getting sent thousands of times per day and fails every second day...) - so it won't be possible to clearly reproduce right now. Nevertheless, I'm trying to collect as many information about the bug here as possible and hope it helps:

First debugger:

image

Bug report
15 January 2024 5:43:58.314083 pm

VM: Win32 - Smalltalk
Image: Squeak6.1alpha [latest update: #22943]

SecurityManager state:
Restricted: false
FileAccess: true
SocketAccess: true
Working Dir C:\Users\Christoph\OneDrive\Dokumente\Squeak
Trusted Dir C:\Users\Christoph\OneDrive\Dokumente\Squeak\Christoph
Untrusted Dir C:\Users\Christoph\OneDrive\Dokumente\My Squeak

Context(Object)>>error:
Receiver: FullBlockClosure(BlockClosure)>>ensure:
Arguments and temporary variables:
aString: 'Computation has been terminated!'
Receiver's instance variables:
sender: [] in Context>>handleSignal:
pc: nil
stackp: 4
method: (BlockClosure>>#ensure: "a CompiledMethod(1093181)")
closureOrNil: nil
receiver: [closure] in Context>>handleSignal:

Context>>cannotReturn:
Receiver: FullBlockClosure(BlockClosure)>>ensure:
Arguments and temporary variables:
result: FullBlockClosure(BlockClosure)>>on:do:
Receiver's instance variables:
sender: [] in Context>>handleSignal:
pc: nil
stackp: 4
method: (BlockClosure>>#ensure: "a CompiledMethod(1093181)")
closureOrNil: nil
receiver: [closure] in Context>>handleSignal:

FullBlockClosure(BlockClosure)>>ensure:
Receiver: [closure] in Context>>handleSignal:
Arguments and temporary variables:
aBlock: [closure] in Context>>handleSignal:
complete: true
returnValue: nil
Receiver's instance variables:
outerContext: Context>>handleSignal:
startpcOrMethod: ([] in Context>>#handleSignal: "a CompiledBlock(2105497)")
numArgs: 0
receiver: FullBlockClosure(BlockClosure)>>on:do:

[] in Context>>handleSignal:
Receiver: FullBlockClosure(BlockClosure)>>on:do:
Arguments and temporary variables:
exception: UndeclaredVariableNotification:
val: nil
Receiver's instance variables:
sender: [] in FullBlockClosure(BlockClosure)>>on:do:on:do:
pc: 34
stackp: 4
method: (BlockClosure>>#on:do: "a CompiledMethod(1946039)")
closureOrNil: nil
receiver: [closure] in ECGlobalEntry(ECEntry)>>tryPredictResultWith:andDo:

FullBlockClosure(BlockClosure)>>ensure:
Receiver: [closure] in Context class>>contextEnsure:
Arguments and temporary variables:
aBlock: [closure] in FullBlockClosure(BlockClosure)>>valueAndWaitWhileUnwinding:...etc...
complete: true
returnValue: nil
Receiver's instance variables:
outerContext: Context class>>contextEnsure:
startpcOrMethod: ([] in Context class>>#contextEnsure: "a CompiledBlock(2240957...etc...
numArgs: 0
receiver: Context

--- The full stack ---
Context(Object)>>error:
Context>>cannotReturn:
FullBlockClosure(BlockClosure)>>ensure:
[] in Context>>handleSignal:
FullBlockClosure(BlockClosure)>>ensure:

At the same time, the UI hangs, and I need to press Cmd-dot to continue. The interrupt reveals where the UI process was stuck:

image

Bug report
15 January 2024 5:45:40.243083 pm

VM: Win32 - Smalltalk
Image: Squeak6.1alpha [latest update: #22943]

SecurityManager state:
Restricted: false
FileAccess: true
SocketAccess: true
Working Dir C:\Users\Christoph\OneDrive\Dokumente\Squeak
Trusted Dir C:\Users\Christoph\OneDrive\Dokumente\Squeak\Christoph
Untrusted Dir C:\Users\Christoph\OneDrive\Dokumente\My Squeak

FullBlockClosure(BlockClosure)>>valueAndWaitWhileUnwinding:
Receiver: [closure] in [] in Process>>terminate
Arguments and temporary variables:
contextToUnwind: Context>>pop
semaphore: a Semaphore()
newBottom: FullBlockClosure(BlockClosure)>>ensure:
Receiver's instance variables:
outerContext: [] in Process>>terminate
startpcOrMethod: ([] in Process>>#terminate "a CompiledBlock(3245925)")
numArgs: 1
receiver: a Process(50758) in [] in Context>>unwindAndStop:

[] in Process>>terminate
Receiver: a Process(50758) in [] in Context>>unwindAndStop:
Arguments and temporary variables:

Receiver's instance variables: 
	nextLink: 	nil
	suspendedContext: 	[] in Context>>unwindAndStop:
	priority: 	40
	myList: 	nil
	threadAffinity: 	nil
	effectiveProcess: 	nil
	name: 	nil
	island: 	nil
	env: 	a Dictionary()

FullBlockClosure(BlockClosure)>>ensure:
Receiver: [closure] in Process>>terminate
Arguments and temporary variables:
aBlock: [closure] in Process>>terminate
complete: true
returnValue: nil
Receiver's instance variables:
outerContext: Process>>terminate
startpcOrMethod: ([] in Process>>#terminate "a CompiledBlock(1088437)")
numArgs: 0
receiver: a Process(50758) in [] in Context>>unwindAndStop:

Process>>terminate
Receiver: a Process(50758) in [] in Context>>unwindAndStop:
Arguments and temporary variables:

Receiver's instance variables: 
	nextLink: 	nil
	suspendedContext: 	[] in Context>>unwindAndStop:
	priority: 	40
	myList: 	nil
	threadAffinity: 	nil
	effectiveProcess: 	nil
	name: 	nil
	island: 	nil
	env: 	a Dictionary()

[] in ECMenuMorph>>postNarrow
Receiver: an ECMenuMorph(3220153)
Arguments and temporary variables:
p: a Process(50758) in [] in Context>>unwindAndStop:
Receiver's instance variables:
bounds: 759@582 corner: 1036@1017
owner: nil
submorphs: #()
fullBounds: 759@582 corner: 1036@1017
color: (Color r: 0.9 g: 0.9 b: 0.9)
extension: a MorphExtension (772249) [other: (borderStyle -> a SimpleBorder) (...etc...
selected: 1
firstVisible: 1
titleStringMorph: nil
controller: an ECBrowserController
context: an ECContext
pageHeight: 26
itemHeight: 16
detailMorph: nil
detailPosition: 1035@582
lastInteraction: 2024-01-15T17:30:29.192083+01:00
alpha: 0.0
processes: an OrderedCollection()

OrderedCollection>>removeAllSuchThat:
Receiver: an OrderedCollection()
Arguments and temporary variables:
aBlock: [closure] in ECMenuMorph>>postNarrow
n: 1
index: 192
element: a Process(50758) in [] in Context>>unwindAndStop:
Receiver's instance variables:
array: #(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil ni...etc...
firstIndex: 1
lastIndex: 0

ECMenuMorph>>postNarrow
Receiver: an ECMenuMorph(3220153)
Arguments and temporary variables:

Receiver's instance variables: 
	bounds: 	759@582 corner: 1036@1017
	owner: 	nil
	submorphs: 	#()
	fullBounds: 	759@582 corner: 1036@1017
	color: 	(Color r: 0.9 g: 0.9 b: 0.9)
	extension: 	a MorphExtension (772249) [other:  (borderStyle -> a SimpleBorder) (...etc...
	selected: 	1
	firstVisible: 	1
	titleStringMorph: 	nil
	controller: 	an ECBrowserController
	context: 	an ECContext
	pageHeight: 	26
	itemHeight: 	16
	detailMorph: 	nil
	detailPosition: 	1035@582
	lastInteraction: 	2024-01-15T17:30:29.192083+01:00
	alpha: 	0.0
	processes: 	an OrderedCollection()

ECBrowserController(ECController)>>handleKeystrokeAfter:editor:
Receiver: an ECBrowserController
Arguments and temporary variables:
aKeyboardEvent: [170@179 keystroke 'n' (110) 138401125]
anEditor: a SmalltalkEditor
Receiver's instance variables:
model: a WeakArray(a TreeBrowser)
menuMorph: nil
editor: a WeakArray(a SmalltalkEditor)
context: nil
oppositeChar: nil
caret: nil
inverseMapping: nil

ECToolSet class>>codeCompletionAround:textMorph:keyStroke:
Receiver: ECToolSet
Arguments and temporary variables:
aBlock: [closure] in TextMorphForEditView(TextMorph)>>keyStroke:
aTextMorph: a TextMorphForEditView(491455)
evt: [170@179 keystroke 'n' (110) 138401125]
completionAllowed: true
editor: a SmalltalkEditor
stringHolder: a TreeBrowser
controller: an ECBrowserController
Receiver's instance variables:
superclass: StandardToolSet
methodDict: a MethodDictionary()
format: 0
instanceVariables: nil
organization: ('as yet unclassified')

	subclasses: 	nil
	name: 	#ECToolSet
	classPool: 	nil
	sharedPools: 	nil
	environment: 	Smalltalk
	category: 	#'Autocompletion-SqueakCompatibility'

ToolSet class>>codeCompletionAround:textMorph:keyStroke:
Receiver: ToolSet
Arguments and temporary variables:
aBlock: [closure] in TextMorphForEditView(TextMorph)>>keyStroke:
aTextMorph: a TextMorphForEditView(491455)
evt: [170@179 keystroke 'n' (110) 138401125]
Receiver's instance variables:
superclass: AppRegistry
methodDict: a MethodDictionary()
format: 0
instanceVariables: nil
organization: ('as yet unclassified')

	subclasses: 	nil
	name: 	#ToolSet
	classPool: 	nil
	sharedPools: 	nil
	environment: 	Smalltalk
	category: 	#'System-Applications'
	registeredClasses: 	an OrderedCollection(StandardToolSet CommandLineToolSet ECToolSet...etc...
	default: 	ECToolSet

TextMorphForEditView(TextMorph)>>keyStroke:
Receiver: a TextMorphForEditView(491455)
Arguments and temporary variables:
evt: [170@179 keystroke 'n' (110) 138401125]
Receiver's instance variables:
bounds: 0@0 corner: 795@260
owner: a TransformMorph(1299609)
submorphs: #()
fullBounds: 0@0 corner: 795@260
color: Color black
extension: a MorphExtension (4182057) [other: (unfocusedSelectionColor -> (Col...etc...
borderWidth: 0
borderColor: Color black
textStyle: a TextStyle(6) Bitmap DejaVu Sans 10.5pt 96ppi 14px Normal
text: a Text for 'contentWithStyledProbabilities

| confidentColor unconfident...etc...
	wrapFlag: 	true
	paragraph: 	a NewParagraph
	editor: 	a SmalltalkEditor
	container: 	nil
	predecessor: 	nil
	successor: 	nil
	backgroundColor: 	nil
	margins: 	3@2 corner: 3@2
	readOnly: 	false
	autoFit: 	true
	plainTextOnly: 	false
	numCharactersPerLine: 	nil
	editView: 	a PluggableTextMorphPlus(3139481)
	acceptOnCR: 	false

TextMorphForEditView>>keyStroke:
Receiver: a TextMorphForEditView(491455)
Arguments and temporary variables:
evt: [170@179 keystroke 'n' (110) 138401125]
view: a PluggableTextMorphPlus(3139481)
Receiver's instance variables:
bounds: 0@0 corner: 795@260
owner: a TransformMorph(1299609)
submorphs: #()
fullBounds: 0@0 corner: 795@260
color: Color black
extension: a MorphExtension (4182057) [other: (unfocusedSelectionColor -> (Col...etc...
borderWidth: 0
borderColor: Color black
textStyle: a TextStyle(6) Bitmap DejaVu Sans 10.5pt 96ppi 14px Normal
text: a Text for 'contentWithStyledProbabilities

| confidentColor unconfident...etc...
	wrapFlag: 	true
	paragraph: 	a NewParagraph
	editor: 	a SmalltalkEditor
	container: 	nil
	predecessor: 	nil
	successor: 	nil
	backgroundColor: 	nil
	margins: 	3@2 corner: 3@2
	readOnly: 	false
	autoFit: 	true
	plainTextOnly: 	false
	numCharactersPerLine: 	nil
	editView: 	a PluggableTextMorphPlus(3139481)
	acceptOnCR: 	false

TextMorphForEditView(Morph)>>handleKeystroke:
Receiver: a TextMorphForEditView(491455)
Arguments and temporary variables:
anEvent: [170@179 keystroke 'n' (110) 138401125]
handler: a TextMorphForEditView(491455)
Receiver's instance variables:
bounds: 0@0 corner: 795@260
owner: a TransformMorph(1299609)
submorphs: #()
fullBounds: 0@0 corner: 795@260
color: Color black
extension: a MorphExtension (4182057) [other: (unfocusedSelectionColor -> (Col...etc...
borderWidth: 0
borderColor: Color black
textStyle: a TextStyle(6) Bitmap DejaVu Sans 10.5pt 96ppi 14px Normal
text: a Text for 'contentWithStyledProbabilities

| confidentColor unconfident...etc...
	wrapFlag: 	true
	paragraph: 	a NewParagraph
	editor: 	a SmalltalkEditor
	container: 	nil
	predecessor: 	nil
	successor: 	nil
	backgroundColor: 	nil
	margins: 	3@2 corner: 3@2
	readOnly: 	false
	autoFit: 	true
	plainTextOnly: 	false
	numCharactersPerLine: 	nil
	editView: 	a PluggableTextMorphPlus(3139481)
	acceptOnCR: 	false

TextMorphForEditView(TextMorph)>>handleKeystroke:
Receiver: a TextMorphForEditView(491455)
Arguments and temporary variables:
anEvent: [170@179 keystroke 'n' (110) 138401125]
pasteUp: nil
Receiver's instance variables:
bounds: 0@0 corner: 795@260
owner: a TransformMorph(1299609)
submorphs: #()
fullBounds: 0@0 corner: 795@260
color: Color black
extension: a MorphExtension (4182057) [other: (unfocusedSelectionColor -> (Col...etc...
borderWidth: 0
borderColor: Color black
textStyle: a TextStyle(6) Bitmap DejaVu Sans 10.5pt 96ppi 14px Normal
text: a Text for 'contentWithStyledProbabilities

| confidentColor unconfident...etc...
	wrapFlag: 	true
	paragraph: 	a NewParagraph
	editor: 	a SmalltalkEditor
	container: 	nil
	predecessor: 	nil
	successor: 	nil
	backgroundColor: 	nil
	margins: 	3@2 corner: 3@2
	readOnly: 	false
	autoFit: 	true
	plainTextOnly: 	false
	numCharactersPerLine: 	nil
	editView: 	a PluggableTextMorphPlus(3139481)
	acceptOnCR: 	false

KeyboardEvent>>sentTo:
Receiver: [170@179 keystroke 'n' (110) 138401125]
Arguments and temporary variables:
anObject: a TextMorphForEditView(491455)
Receiver's instance variables:
timeStamp: 138401125
source: a HandMorph(333670)
type: #keystroke
buttons: 0
position: 170@179
handler: a TextMorphForEditView(491455)
wasHandled: true
wasIgnored: false
keyValue: 110
keyCode: 78

TextMorphForEditView(Morph)>>handleEvent:
Receiver: a TextMorphForEditView(491455)
Arguments and temporary variables:
anEvent: [170@179 keystroke 'n' (110) 138401125]
filteredEvent: [170@179 keystroke 'n' (110) 138401125]
Receiver's instance variables:
bounds: 0@0 corner: 795@260
owner: a TransformMorph(1299609)
submorphs: #()
fullBounds: 0@0 corner: 795@260
color: Color black
extension: a MorphExtension (4182057) [other: (unfocusedSelectionColor -> (Col...etc...
borderWidth: 0
borderColor: Color black
textStyle: a TextStyle(6) Bitmap DejaVu Sans 10.5pt 96ppi 14px Normal
text: a Text for 'contentWithStyledProbabilities

| confidentColor unconfident...etc...
	wrapFlag: 	true
	paragraph: 	a NewParagraph
	editor: 	a SmalltalkEditor
	container: 	nil
	predecessor: 	nil
	successor: 	nil
	backgroundColor: 	nil
	margins: 	3@2 corner: 3@2
	readOnly: 	false
	autoFit: 	true
	plainTextOnly: 	false
	numCharactersPerLine: 	nil
	editView: 	a PluggableTextMorphPlus(3139481)
	acceptOnCR: 	false

TextMorphForEditView(Morph)>>handleFocusEvent:
Receiver: a TextMorphForEditView(491455)
Arguments and temporary variables:
anEvent: [170@179 keystroke 'n' (110) 138401125]
Receiver's instance variables:
bounds: 0@0 corner: 795@260
owner: a TransformMorph(1299609)
submorphs: #()
fullBounds: 0@0 corner: 795@260
color: Color black
extension: a MorphExtension (4182057) [other: (unfocusedSelectionColor -> (Col...etc...
borderWidth: 0
borderColor: Color black
textStyle: a TextStyle(6) Bitmap DejaVu Sans 10.5pt 96ppi 14px Normal
text: a Text for 'contentWithStyledProbabilities

| confidentColor unconfident...etc...
	wrapFlag: 	true
	paragraph: 	a NewParagraph
	editor: 	a SmalltalkEditor
	container: 	nil
	predecessor: 	nil
	successor: 	nil
	backgroundColor: 	nil
	margins: 	3@2 corner: 3@2
	readOnly: 	false
	autoFit: 	true
	plainTextOnly: 	false
	numCharactersPerLine: 	nil
	editView: 	a PluggableTextMorphPlus(3139481)
	acceptOnCR: 	false

MorphicEventDispatcher>>doHandlingForFocusEvent:with:
Receiver: a MorphicEventDispatcher
Arguments and temporary variables:
currentEvent: [532@567 keystroke 'n' (110) 138401125]
focusMorph: a TextMorphForEditView(491455)
localEvent: [170@179 keystroke 'n' (110) 138401125]
filteredEvent: nil
Receiver's instance variables:
lastType: nil
lastDispatch: nil

MorphicEventDispatcher>>dispatchFocusEvent:with:
Receiver: a MorphicEventDispatcher
Arguments and temporary variables:
anEventWithGlobalPosition: [532@567 keystroke 'n' (110) 138401125]
focusMorph: a TextMorphForEditView(491455)
currentEvent: [532@567 keystroke 'n' (110) 138401125]
Receiver's instance variables:
lastType: nil
lastDispatch: nil

--- The full stack ---
FullBlockClosure(BlockClosure)>>valueAndWaitWhileUnwinding:
[] in Process>>terminate
FullBlockClosure(BlockClosure)>>ensure:
Process>>terminate
[] in ECMenuMorph>>postNarrow
OrderedCollection>>removeAllSuchThat:
ECMenuMorph>>postNarrow
ECBrowserController(ECController)>>handleKeystrokeAfter:editor:
ECToolSet class>>codeCompletionAround:textMorph:keyStroke:
ToolSet class>>codeCompletionAround:textMorph:keyStroke:
TextMorphForEditView(TextMorph)>>keyStroke:
TextMorphForEditView>>keyStroke:
TextMorphForEditView(Morph)>>handleKeystroke:
TextMorphForEditView(TextMorph)>>handleKeystroke:
KeyboardEvent>>sentTo:
TextMorphForEditView(Morph)>>handleEvent:
TextMorphForEditView(Morph)>>handleFocusEvent:
MorphicEventDispatcher>>doHandlingForFocusEvent:with:
MorphicEventDispatcher>>dispatchFocusEvent:with:


TextMorphForEditView(Morph)>>processFocusEvent:using:
TextMorphForEditView(Morph)>>processFocusEvent:
[] in [] in [] in HandMorph>>sendFocusEvent:to:clear:
[] in ActiveEventVariable class(DynamicVariable class)>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveEventVariable class(DynamicVariable class)>>value:during:
[] in ActiveEventVariable class>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveEventVariable class>>value:during:
KeyboardEvent(MorphicEvent)>>becomeActiveDuring:
[] in [] in HandMorph>>sendFocusEvent:to:clear:
[] in ActiveHandVariable class(DynamicVariable class)>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveHandVariable class(DynamicVariable class)>>value:during:
[] in ActiveHandVariable class>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveHandVariable class>>value:during:
HandMorph>>becomeActiveDuring:
[] in HandMorph>>sendFocusEvent:to:clear:
[] in ActiveWorldVariable class(DynamicVariable class)>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveWorldVariable class(DynamicVariable class)>>value:during:
[] in ActiveWorldVariable class>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveWorldVariable class>>value:during:
PasteUpMorph>>becomeActiveDuring:
HandMorph>>sendFocusEvent:to:clear:
HandMorph>>sendEvent:focus:clear:
HandMorph>>sendKeyboardEvent:
HandMorph>>handleEvent:
HandMorph>>processEvents
[] in [] in WorldState>>doOneCycleNowFor:
[] in ActiveHandVariable class(DynamicVariable class)>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveHandVariable class(DynamicVariable class)>>value:during:
[] in ActiveHandVariable class>>value:during:
FullBlockClosure(BlockClosure)>>ensure:
ActiveHandVariable class>>value:during:
HandMorph>>becomeActiveDuring:
[] in WorldState>>doOneCycleNowFor:
Array(SequenceableCollection)>>do:
WorldState>>handsDo:
-- and more not shown --

Exploring the receiver of the selected context in the first debugger (FullBlockClosure(BlockClosure)>>on:do:) reveals the following stack (it is cyclic/infinite, I used self stackOfSize: 100):

image

Exploring the contextToUnwind from the interrupted context of the second debugger reveals the following (note the print-it displays the full stack of the interrupted context's receiver):

image

These are the methods from my package that are relevant to the bug (just look at the <-- pointer):

ECMenuMorph>>postNarrow

	self selected: 0.
	firstVisible := 1.
	self model notEmpty ifTrue: [ self selected: 1 ].
	(self model entries size < 4 and: [ self model expanded not])
		ifTrue: ["Automatically expand if there are too few entries" self expand].
	processes ifNotNil: [processes removeAllSuchThat: [:p | p terminate. true]]. "<-- here the process should be terminated"
	processes ifNil: [processes := OrderedCollection new].
	self model entries do: [:entry |
		processes add: ([entry tryStorePredictedResultWith: context. self future changed] forkAt: Processor userBackgroundPriority)].
	self show.
	^ true
tryPredictResultWith: anECContext andDo: resultBlock

	| controller editor code method result |
	controller := anECContext instVarNamed: 'controller'.
	editor := controller editor.
	editor ifNil: [^ nil].
	code := ((editor text first: editor startIndex - anECContext completionToken size - 1) , self completion) lines last.
	method := [controller model doItContext class compilerClass new
		compiledMethodFor: code
		in: controller model doItContext
		to: controller model doItReceiver
		notifying: nil
		ifFail: [^ nil]]
			on: UndeclaredVariableNotification do: [:ex | ex resume] "prevent Transcript output" "<-- here an exception is resumed during parsing"
			on: SyntaxErrorNotification do: [:ex | ^ nil].
	result := [Sandbox evaluate:
		[[(method
			valueWithReceiver: controller model doItReceiver
			arguments:
				(controller model doItContext ifNil: [#()] ifNotNil: [:context | {context}]))
					printString]
						on: Error , Warning , Halt do: [:ex | ex]]]
							valueWithin: 5 seconds onTimeout: [].
	^ result ifNotNil: [resultBlock value: result]

To me, this looks as if the process was attempted to terminate while the UndeclaredVariableNotification was on the stack (maybe while it was already being handled/resumed from), and something in the stack manipulation logic has prevented the termination from working correctly. Maybe this is related to the fact that the context stack is temporarily invalidated during Context class>>#contextEnsure: et al. (cf. stepping into all the details of cut: during thisContext insertSender: (Context contextEnsure: []))?

I wish I could reproduce this issue, but I cannot for now. I have a vague hope that this information might be enough to suggest any ideas of what might be wrong, and maybe create a simpler example to reproduce ...

@LinqLover LinqLover added base system [SCOPE] Squeak's basic (language) concerns such as Kernel, Collections, Graphics, Network bug [WHAT] Something isn&#39;t working as expected. Automated tests beneficial. :- labels Jan 15, 2024
@LinqLover
Copy link
Contributor Author

I was able to reproduce!

block := [[Compiler evaluate: 'x'] on: UndeclaredVariableNotification do: [:ex | ex resume]].

"context := block asContext.
steps := 0.
[context willReturn and: [context sender isNil]] whileFalse:
	[steps := steps + 1.
	context := context step].

steps. ""11145""

0 to: 11145"#(6402) do: [:n |
	Transcript showln: n.
	context := block asContext.
	n timesRepeat: [context := context step].
	process := Process forContext: context priority: 40.
	process terminate.
	self assert: process isTerminated].

As the out-commented code shows, terminate should not fail at whatever point the process is interrupted ...

@isCzech
Copy link

isCzech commented Jan 16, 2024

Hi Christoph,
This is an amazing way to test an operation (unwinding/termination in this case) at every step! You should make it a standard testing procedure (method?) ;)

My guess what is happening at step 6402 is the process is in the middle of unwinding the ex resume and you initiate termination, i.e. another unwind on top of the previous one - and the outside unwind kind of slips through some crack in the inner one and the procedure fails with the BCR (see the two ensure contexts inserted at the bottom of the contextToUnwind at your last screenshot). Although I tried to make unwind resilient against termination at any arbitrary point (see tests covering termination of a terminating process etc.) I clearly failed to cover all such situations :)

I'll investigate more thoroughly next week. Thanks for this lovely problem!
Jaromir

PS: have you tried it in 5.3 ?

@isCzech
Copy link

isCzech commented Jan 16, 2024

Hi Christoph,
try Kernel-jar.1552 please.

I've described the root cause and quickly drafted a solution. If you find it going in the right direction we can polish the final form.

It turns out the problem lies in the way the ensure guard contexts are being generated by contextEnsure. I'm curious whether the suggested solution will fix all your observed problems or whether some more surface.

@isCzech
Copy link

isCzech commented Jan 18, 2024

Hi Christoph (@LinqLover),

I've modified #contextEnsure a bit - see Kernel-jar.1553. I guess this prevents the situation you observed. Please check and merge if you like it. Let me know of any other issues, thanks.

@isCzech
Copy link

isCzech commented Jan 23, 2024

Hi Christoph,

Have you had a chance to check Kernel-jar.1553 yet? I wonder if it solves the bug you observed?

I assume you found another occurrence of the BCR at a later step in your amazing example: this time it's a completely unrelated issue where two nested unwinds don't cooperate correctly. I've improved the "granularity" of the unwind to distinguish unwind blocks that started but not finished their execution and those that are really finished - see Kernel-jar.1554 - description of the bug and the fix.

Please let me know of this helps.

Please either merge if you approve or otherwise I could bundle all my recent changes into a single package... Let me know what you think.

@LinqLover
Copy link
Contributor Author

Hi @isCzech, so sorry for not replying earlier! Having too many different things to do right now diving into the depths of simulation and unwinding is never something I can handle in a couple of minutes. :( Thank you for looking into this!

This is an amazing way to test an operation (unwinding/termination in this case) at every step! You should make it a standard testing procedure (method?) ;)

Good idea! The original example was pretty slow but I have just added ProcessTest>>#testTerminateEverywhere (KernelTests-ct.451) with a more minified block, which runs in under 1 second. If you think it makes sense, we can adapt the same pattern for testing "termination of a terminating process" etc. as well. :-)

I confirm that Kernel-jar.1553 fixes the original issue and together with Kernel-jar.1554 makes the new test entirely pass, so I am tempted to merge both of them. :-) Nevertheless, I do not yet fully understand why this issue is specific to Context class>>contextEnsure:, why would the following example not trigger the original bug?

Once I have understood that, I will merge both versions. I guess Kernel-jar.1552 can be moved to treated, or do you have any further plans for it? One disadvantage with this solution is that you hardcode expectations about the bytecodes of ensure: there. I have already merged both versions into my multiprocessing-extensive image and will observe it further. :-)

block := [[| c |
	c := thisContext swapSender: nil.
	thisContext swapSender: c.
	42] ensure: []].

@isCzech
Copy link

isCzech commented Jan 31, 2024

Hi Christoph (@LinqLover),

why this issue is specific to Context class>>contextEnsure:, why would the following example not trigger the original bug?

The crucial difference between the two examples is that the original one (block := [[Compiler evaluate: 'x'] on: UndeclaredVariableNotification do: [:ex | ex resume]]) has a non-local return that calls #contextEnsure: (via #unwindTo: and #runUntilReturnFrom:). When the computation gets stopped at the precise moment the #contextEnsure: disconnects the remainder of its sender chain before jumping back to the top of disconnected remainder and your ingenious testing scenario terminates the process, the termination start at the chopped off head end and fails.

If you're happy with the fix I'd do the same modification to #contextOn:do: for the sake of consistency. I can't see a scenario where this would cause a similar problem but who would have thought about what you observed :)

Kernel-jar.1552 can be moved to treated

Sure, thanks for the cleanup. It was just the first approximation but as you said it's bound to bytecodes which is bad...

diving into the depths of simulation and unwinding is never something I can handle in a couple of minutes

mildly put :)

I have just added ProcessTest>>#testTerminateEverywhere (KernelTests-ct.451) with a more minified block, which runs in under 1 second.

Great! I'd love to see this really as a "pattern" to just feed it a block, a test method (anything, not just terminate) and an expectation and watch the result (ok or where it derails). I haven't really thought it through it just feels so powerful :)

(PS: regarding the other issues I haven't had time yet but I'll get there)

@LinqLover
Copy link
Contributor Author

Ah, I got it! So a more minimal example would be something like

[| c |
[c := thisContext sender sender swapSender: nil]
	ensure: [thisContext sender sender swapSender: c]] value]

Yes, it sounds wise to patch contextOn:do: as well, even though I wonder why we cannot find an example where this is a problem.

Oh no, here is another example that still fails when put inside #testTerminateEverywhere:

block := [| c |
	c := thisContext.
	[] ensure: [c jump].
	42].

What is going on here?

I have just added ProcessTest>>#testTerminateEverywhere (KernelTests-ct.451) with a more minified block, which runs in under 1 second.

Great! I'd love to see this really as a "pattern" to just feed it a block, a test method (anything, not just terminate) and an expectation and watch the result (ok or where it derails). I haven't really thought it through it just feels so powerful :)

Sure, feel free to extract the logic from #testTerminateEverywhere as it fits your needs. :-)

@isCzech
Copy link

isCzech commented Feb 2, 2024

Oh no, here is another example that still fails when put inside #testTerminateEverywhere:
What is going on here?

I think this is inevitable... jump inside the ensure argument block is a nightmare and if it jumps over my unwind guard in #runUntilReturnFrom: I don't know what can be done about it. I'll explore this along with your other examples around the stepOver bug. Coming soon, I hope :)

Yes, it sounds wise to patch contextOn:do: as well, even though I wonder why we cannot find an example where this is a problem.

I guess the example would have to involve #runUntilErrorOrReturnFrom: which is usually called only in simulation... I haven't had the energy to try so I only guess :) I'll send the fix.

@isCzech
Copy link

isCzech commented Feb 2, 2024

contextOn:do: fix in Kernel-jar.1555

@LinqLover
Copy link
Contributor Author

LinqLover commented Feb 7, 2024

@isCzech Thanks, willl take a look soon! In the meantime, here is just another example that triggers the same error:

block := [(Context runSimulated: [41]) + 1].

This one is actually of practical relevance for me, because in my scenario I am running a lot of sandboxed simulations in background processes ... I wonder whether we could think this together with the stepOver/runUntilErrorOrReturnFrom issue which shares the same vulnerability to irregular context switches ... Like, place a marker on the context stack/a safe bottom context while performing a temporary context switch and when we are terminating a process, check for such a marker, and if yes, continue execution regularly until the marker is no longer set? Or a bit stateless, use a pragma in known context switching methods such as #contextEnsure: (the old version) that instruct the unwinding logic to defer unwinding until that method has popped? Hm ... this is tricky ^^

@LinqLover
Copy link
Contributor Author

Ah, I see. The ensure context in Context>>#runSimulated:contextAtEachStep: incorrectly acts like valueUninterruptably, forcing a return from the home context even when the execution shall not continue. This was introduced to support situations like

[Context runSimulated: [2/0]] on: ZeroDivide do: [:ex | ex return: 1]

but when the execution is actually to abort, this behavior makes no sense. Hmm... maybe ensure: should have an aborting variable and cull it to aBlock? ;-) Or how else could we find out from within that specific ensure block that we must not try to proceed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
base system [SCOPE] Squeak's basic (language) concerns such as Kernel, Collections, Graphics, Network bug [WHAT] Something isn&#39;t working as expected. Automated tests beneficial. :-
Projects
None yet
Development

No branches or pull requests

2 participants