The Inbox: Kernel-jar.1385.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

The Inbox: Kernel-jar.1385.mcz

commits-2
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]!