[squeak-dev] The Inbox: Kernel-jar.1385.mcz

commits at source.squeak.org commits at source.squeak.org
Sun Apr 11 07:20:03 UTC 2021


A new version of Kernel was added to project The Inbox:
http://source.squeak.org/inbox/Kernel-jar.1385.mcz

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

Name: Kernel-jar.1385
Author: jar
Time: 11 April 2021, 9:19:58.900241 am
UUID: fc94f858-9307-e943-b23a-180be1ffad4a
Ancestors: Kernel-mt.1383

Fix a list of termination bugs described in http://forum.world.st/Solving-multiple-termination-bugs-summary-amp-proposal-td5128285.html

A set of tests will follow.

=============== Diff against Kernel-mt.1383 ===============

Item was changed:
  ----- Method: Process>>isSuspended (in category 'testing') -----
  isSuspended
+ 	"A process is suspended if it is not active, not terminated and 
+ 	not waiting in a scheduler or a semaphore queue."
+ 
+ 	self isActiveProcess ifTrue: [^ false].
+ 	^myList isNil and: [self isTerminated not]!
- 	"A process is suspended if it has been suspended with the suspend primitive.
- 	 It is distinguishable from the active process and a terminated process by
- 	 having a non-nil suspendedContext that is either not the bottom context
- 	 or has not reached its endPC."
- 	^nil == myList
- 	  and: [nil ~~ suspendedContext
- 	  and: [suspendedContext isBottomContext
- 			ifTrue: [suspendedContext closure
- 						ifNil: [suspendedContext methodClass ~~ Process
- 							or: [suspendedContext selector ~~ #terminate]]
- 						ifNotNil: [suspendedContext pc < suspendedContext closure endPC]]
- 			ifFalse: [true]]]!

Item was changed:
  ----- Method: Process>>isTerminated (in category 'testing') -----
  isTerminated
+ 	"Answer if the receiver is terminated. A process is considered terminated
+ 	if the suspendedContext is the bottomContext and the pc is at the endPC"
+ 
- 	"Answer if the receiver is terminated, or at least terminating."
  	self isActiveProcess ifTrue: [^ false].
+ 	^suspendedContext isNil or: [
+ 		suspendedContext isBottomContext and: [
+ 			suspendedContext isDead or: [suspendedContext atEnd]]]!
- 	^suspendedContext isNil
- 	  or: ["If the suspendedContext is the bottomContext it is the block in Process>>newProcess.
- 		   If so, and the pc is at the endPC, the block has already sent and returned
- 		   from value and there is nothing more to do."
- 		suspendedContext isBottomContext
- 		and: [suspendedContext closure
- 						ifNil: [suspendedContext methodClass == Process
- 							and: [suspendedContext selector == #terminate]]
- 						ifNotNil: [suspendedContext pc >= suspendedContext closure endPC]]]!

Item was changed:
  ----- Method: Process>>terminate (in category 'changing process state') -----
  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 outerMost |
+ 	self isActiveProcess ifTrue: [
+ 		"If terminating the active process, suspend it first and terminate it as a suspended process."
+ 		[self terminate] fork.
+ 		^self suspend].
- 	| ctxt unwindBlock oldList |
- 	self isActiveProcess ifTrue:
- 		[ctxt := thisContext.
- 		 [ctxt := ctxt findNextUnwindContextUpTo: nil.
- 		  ctxt ~~ nil] whileTrue:
- 			[(ctxt tempAt: 2) ifNil:
- 				["N.B. Unlike Context>>unwindTo: we do not set complete (tempAt: 2) to true."
- 				 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;
+ 		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)"
+ 					suspendedContext := outerMost]]. 
- 		"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]].
  
+ 		"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]!
- 		ctxt := self popTo: suspendedContext bottomContext.
- 		ctxt == suspendedContext bottomContext ifFalse:
- 			[self debugWithTitle: 'Unwind error during termination' translated full: false].
- 		"Set the context to its endPC for the benefit of isTerminated."
- 		ctxt pc: ctxt endPC]!



More information about the Squeak-dev mailing list