[squeak-dev] The Trunk: Kernel-ct.1434.mcz

commits at source.squeak.org commits at source.squeak.org
Thu Dec 16 01:31:01 UTC 2021


Christoph Thiede uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-ct.1434.mcz

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

Name: Kernel-ct.1434
Author: ct
Time: 16 December 2021, 2:17:45.105664 am
UUID: 53141af1-1a95-ef44-92b4-e1a67565e375
Ancestors: Kernel-mt.1433

Revises termination modi on Process. Besides classic #terminate, adds #terminateAggressively (dismiss all already active unwind contexts) and #destroy (private - no unwinding). Connects the debugger's Abandon button to new #terminateAggressively to restore the old semantics known from 2020/Squeak 5.3 and older).

Implementation specifics: #terminateAggressively reuses the unwinding mechanism from Context >> #resume: by activating a return context to an extra "zeroth" bottom context (tombstone) on the stack. Extracted #completeTo:ifError: to supply a custom handler for the unhandled error, i.e., to make it visible to the user. More information: http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-May/215604.html

Note that the existing #terminate implementation still contains a few bugs which will need to be addressed in near future. See: http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-April/214633.html

Thanks to Jaromir (jar) and Marcel (mt) for the valuable feedback!

=============== Diff against Kernel-mt.1433 ===============

Item was changed:
  ----- Method: Context>>findNextHandlerContext (in category 'private-exceptions') -----
  findNextHandlerContext
  	"find next context marked with <primitive: 199>.
  	This can be either a handler context (on:do:),
  	or a handling context (handleSignal:)"
  
+ 	^ self sender ifNotNil: [:ctx | ctx findNextHandlerContextStarting]!
- 	^ self sender findNextHandlerContextStarting!

Item was changed:
  ----- Method: Process>>complete: (in category 'changing suspended state') -----
  complete: aContext 
- 	"Run self until aContext is popped or an unhandled error is raised.  Return self's new top context, unless an unhandled error was raised then return the signaler context (rather than open a debugger)."
  	
+ 	^ self complete: aContext ifError: [:error |
+ 		Notification new
- 	| ctxt pair error |
- 	ctxt := suspendedContext.
- 	suspendedContext := nil.  "disable this process while running its stack in active process below"
- 	pair := Processor activeProcess
- 				evaluate: [ctxt runUntilErrorOrReturnFrom: aContext]
- 				onBehalfOf: self.
- 	suspendedContext := pair first.
- 	error := pair second.
- 	error ifNotNil:
- 		["Give a debugger a chance to update its title to reflect the new exception"
- 		 Notification new
  			tag: {aContext. error};
  			signal.
+ 		 ^error signalerContext]!
- 		 ^error signalerContext].
- 	^ suspendedContext!

Item was added:
+ ----- Method: Process>>complete:ifError: (in category 'changing suspended state') -----
+ complete: aContext ifError: errorBlock
+ 	"Run the receiver until aContext is popped or an unhandled error is raised. If aContext was popped regularly, return self's new top context; otherwise, evaluate errorBlock with the error and return the signaler context (rather than opening a debugger)."
+ 
+ 	| ctxt error pair |
+ 	ctxt := suspendedContext.
+ 	suspendedContext := nil. "disable this process while running its stack in active process below"
+ 	pair := Processor activeProcess
+ 		evaluate: [ctxt runUntilErrorOrReturnFrom: aContext]
+ 		onBehalfOf: self.
+ 	suspendedContext := pair first.
+ 	error := pair second.
+ 	error ifNotNil: [errorBlock value: error].
+ 	^ suspendedContext!

Item was added:
+ ----- Method: Process>>destroy (in category 'changing process state') -----
+ destroy
+ 	"Destroy the receiver immediately without attempting to run any of its unwind blocks. Kind of private!! Unless you have a very good reason (such as recursive stack errors), you should send #terminate or #terminateAggressively instead which will allow for cleaning up any open resources properly."
+ 
+ 	suspendedContext := nil.
+ 	self terminateAggressively.!

Item was changed:
  ----- Method: Process>>terminate (in category 'changing process state') -----
  terminate 
+ 	"Stop the receiver forever.
+ 	Run all unwind contexts (#ensure:/#ifCurtailed: blocks) on the stack, even if they are currently in progress. If already active unwind contexts should not be continued, send #terminateAggressively instead.
+ 	Note that ill unwind contexts are theoretically able to stall the termination (for instance, by placing a non-local return in an unwind block); however, this is a disrecommended practice.
+ 	If the process is in the middle of a critical section, release it properly."
- 	"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 outerMost |
  	self isActiveProcess ifTrue: [
  		"If terminating the active process, suspend it first and terminate it as a suspended process."
  		[self terminate] fork.
  		^self suspend].
  
  	"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;
  		if there are multiple such nested unwind blocks, try to complete the outer-most one; the inner
  		blocks will be completed in the process."
  		ctxt := suspendedContext.
  		[(ctxt := ctxt findNextUnwindContextUpTo: nil) isNil] whileFalse: 
  			"Contexts under evaluation have already set their complete (tempAt: 2) to true."
  			[(ctxt tempAt:2) ifNotNil: [outerMost := ctxt]].
  		outerMost ifNotNil: [
  			"This is the outer-most unwind context currently under evaluation;
  			let's find an inner context executing outerMost's argument block (tempAt: 1)"
  			(suspendedContext findContextSuchThat: [:ctx | 
  				ctx closure == (outerMost tempAt: 1)]) ifNotNil: [:inner | 
  					"Let's finish the unfinished unwind context only (i.e. up to inner) and return here"
  					suspendedContext runUntilErrorOrReturnFrom: inner. 
  					"Update the receiver's suspendedContext (the previous step reset its sender to nil);
  					return, if the execution stack reached its bottom (e.g. in case of non-local returns)."
  					(suspendedContext := outerMost sender) ifNil: [^self]]]. 
  
  		"Now all unwind blocks caught halfway through have been completed; 
  		let's execute the ones still pending. Note: #findNextUnwindContextUpTo: starts
  		searching from the receiver's sender but the receiver itself may be an unwind context."
  		ctxt := suspendedContext.
  		ctxt isUnwindContext ifFalse: [ctxt := ctxt findNextUnwindContextUpTo: nil].
  		[ctxt isNil] whileFalse: [
  			(ctxt tempAt: 2) ifNil: [
  				ctxt tempAt: 2 put: true.
  				unwindBlock := ctxt tempAt: 1.
  				"Create a context for the unwind block and execute it on the unwind block's stack. 
  				Note: using #value instead of #runUntilErrorOrReturnFrom: would lead to executing 
  				the unwind on the wrong stack preventing the correct execution of non-local returns."
  				suspendedContext := unwindBlock asContextWithSender: ctxt.
  				suspendedContext runUntilErrorOrReturnFrom: suspendedContext].
  			ctxt := ctxt findNextUnwindContextUpTo: nil].
  
  		"Reset the context's pc and sender to nil for the benefit of isTerminated."
  		suspendedContext terminate]!

Item was added:
+ ----- Method: Process>>terminateAggressively (in category 'changing process state') -----
+ terminateAggressively
+ 	"Stop the receiver represents forever.
+ 	Run all unwind contexts (#ensure:/#ifCurtailed: blocks) on the stack that have not yet been started. 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. If even those unwind contexts should be continued, send #terminate instead.
+ 	Note that ill unwind contexts are theoretically able to stall the termination (for instance, by placing a non-local return in an unwind block); however, this is a disrecommended practice.
+ 	If the process is in the middle of a critical: critical section, release it properly."
+ 
+ 	| oldList bottom tombstone |
+ 	self isActiveProcess ifTrue: [
+ 		"If terminating the active process, suspend it first and terminate it as a suspended process."
+ 		[self terminate] fork.
+ 		^self suspend].
+ 	
+ 	"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."
+ 	oldList := self suspend.
+ 	suspendedContext ifNil: [^ self "Process is already terminated"].
+ 	
+ 	"Release any method marked with the <criticalSection> pragma. The argument is whether the process is runnable."
+ 	self releaseCriticalSection: (oldList isNil or: [oldList class == LinkedList]).
+ 	
+ 	bottom := suspendedContext bottomContext.
+ 	tombstone := bottom insertSender: [self suspend "terminated"] asContext.
+ 	suspendedContext := self
+ 		activateReturn: bottom
+ 		value: nil.
+ 	self complete: tombstone ifError: [:ex |
+ 		suspendedContext privRefresh. "Restart the handler context of UnhandledError so that when the receiver is resumed, its #defaultAction will be reached. See implementation details in #runUntilErrorOrReturnFrom:."
+ 		"We're not yet done, resume the receiver to spawn a new debugger on the error."
+ 		self resume].!



More information about the Squeak-dev mailing list