Marcel Taeumel uploaded a new version of KernelTests to project The Trunk:
http://source.squeak.org/trunk/KernelTests-jar.443.mcz
==================== Summary ====================
Name: KernelTests-jar.443
Author: jar
Time: 30 January 2023, 5:33:22.893677 pm
UUID: 4f815d5d-a166-d141-8fca-d9d0d57f0b93
Ancestors: KernelTests-tpr.441
Complement Kernel-jar.1498
=============== Diff against KernelTests-tpr.441 ===============
Item was removed:
- ----- Method: MutexTest>>expectedFailures (in category 'failures') -----
- expectedFailures
-
- ^ #(testUnwindMutexBlockedInCritical)!
Item was added:
+ ----- Method: MutexTest>>testMutexAfterCriticalWait2 (in category 'tests') -----
+ testMutexAfterCriticalWait2
+ "This tests whether a process that has just left the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it with the mutex correctly released."
+ | lock p beenHere |
+ lock := Mutex new. beenHere := false.
+ p := [lock critical: [beenHere := true]] newProcess.
+ p priority: Processor activePriority - 1.
+ lock critical: "We now own it; p can't enter properly"
+ [p resume.
+ "wait until p enters the critical section; it doesn't own the Mutex so is blocked..."
+ [p suspendingList == lock] whileFalse: [(Delay forMilliseconds: 10) wait].
+ self deny: lock isEmpty].
+ "p is waiting on lock; on our exiting critical: p is now the notional owner. Terminate before it has a chance to run".
+ p terminate.
+ self deny: lock isOwned.
+ self assert: lock isEmpty.
+ self deny: beenHere!
Item was added:
+ ----- Method: MutexTest>>testMutexAfterCriticalWaitCf (in category 'tests') -----
+ testMutexAfterCriticalWaitCf
+ "This tests whether a process that has just left the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it with the mutex correctly released."
+ | lock p beenHere |
+ lock := Mutex new. beenHere := false.
+ p := [lock critical: [beenHere := true]] newProcess.
+ p priority: Processor activePriority - 1.
+ lock critical: "We now own it; p can't enter properly"
+ [p resume.
+ "wait until p enters the critical section; it doesn't own the Mutex so is blocked..."
+ [p suspendingList == lock] whileFalse: [(Delay forMilliseconds: 10) wait].
+ self deny: lock isEmpty.
+ "p is waiting on lock; on our exiting critical: p is now the notional owner. Terminate before it has a chance to run".
+ p terminate].
+ self deny: lock isOwned.
+ self assert: lock isEmpty.
+ self deny: beenHere!
Item was added:
+ ----- Method: MutexTest>>testMutexAfterCriticalWaitInEnsure (in category 'tests') -----
+ testMutexAfterCriticalWaitInEnsure
+ "This tests whether a process that has just left the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it with the mutex correctly released."
+ | lock p |
+ lock := Mutex new.
+ p := [[] ensure: [lock critical: []]] newProcess.
+ p priority: Processor activePriority - 1.
+ lock critical: "We now own it; p can't enter properly"
+ [p resume.
+ "wait until p enters the critical section; it doesn't own the Mutex so is blocked..."
+ [p suspendingList == lock] whileFalse: [(Delay forMilliseconds: 10) wait].
+ self deny: lock isEmpty].
+ "p is waiting on lock; on our exiting critical: p is now the notional owner. Terminate before it has a chance to run".
+ p terminate.
+ self deny: lock isOwned.
+ self assert: lock isEmpty!
Item was added:
+ ----- Method: MutexTest>>testMutexAfterCriticalWaitInEnsure2 (in category 'tests') -----
+ testMutexAfterCriticalWaitInEnsure2
+ "This tests whether a process that has just left the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it with the mutex correctly released."
+ | lock p beenHere |
+ lock := Mutex new. beenHere := false.
+ p := [[] ensure: [lock critical: [beenHere := true]]] newProcess.
+ p priority: Processor activePriority - 1.
+ lock critical: "We now own it; p can't enter properly"
+ [p resume.
+ "wait until p enters the critical section; it doesn't own the Mutex so is blocked..."
+ [p suspendingList == lock] whileFalse: [(Delay forMilliseconds: 10) wait].
+ self deny: lock isEmpty].
+ "p is waiting on lock; on our exiting critical: p is now the notional owner. Terminate before it has a chance to run".
+ p terminate.
+ self deny: lock isOwned.
+ self assert: lock isEmpty.
+ self assert: beenHere!
Item was added:
+ ----- Method: MutexTest>>testMutexAfterCriticalWaitInEnsureCf (in category 'tests') -----
+ testMutexAfterCriticalWaitInEnsureCf
+ "This tests whether a process that has just left the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it with the mutex correctly released."
+ | lock p beenHere |
+ lock := Mutex new. beenHere := false.
+ p := [[] ensure: [lock critical: [beenHere := true]]] newProcess.
+ p priority: Processor activePriority - 1.
+ lock critical: "We now own it; p can't enter properly"
+ [p resume.
+ "wait until p enters the critical section; it doesn't own the Mutex so is blocked..."
+ [p suspendingList == lock] whileFalse: [(Delay forMilliseconds: 10) wait].
+ self deny: lock isEmpty.
+ "p is waiting on lock; on our exiting critical: p is now the notional owner. Terminate before it has a chance to run".
+ p terminate].
+ self deny: lock isOwned.
+ self assert: lock isEmpty.
+ self deny: beenHere!
Item was added:
+ ----- Method: MutexTest>>testMutexBlockedInCritical (in category 'tests') -----
+ testMutexBlockedInCritical
+ "This tests whether a process that is inside the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unchanged."
+ | lock sock proc wait |
+ lock := Mutex new.
+ sock := Semaphore new.
+ proc := [lock critical: [sock wait]] fork.
+ wait := [lock critical: []] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == lock.
+ self deny: lock isEmpty.
+ self assert: lock isOwned.
+ wait terminate.
+ Processor yield.
+ self assert: wait isTerminated.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == nil.
+ self assert: lock isEmpty.
+ self assert: lock isOwned.
+ proc terminate!
Item was added:
+ ----- Method: MutexTest>>testMutexBlockedInCritical2 (in category 'tests') -----
+ testMutexBlockedInCritical2
+ "This tests whether a process that is inside the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unchanged."
+ | lock sock proc wait beenHere beenHereToo |
+ lock := Mutex new.
+ sock := Semaphore new.
+ beenHere := beenHereToo := false.
+ proc := [lock critical: [sock wait]] fork.
+ wait := [lock critical: [beenHere := true]. beenHereToo := true] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == lock.
+ self deny: lock isEmpty.
+ self assert: lock isOwned.
+ wait terminate.
+ Processor yield.
+ self assert: wait isTerminated.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == nil.
+ self assert: lock isEmpty.
+ self assert: lock isOwned.
+ self deny: beenHere.
+ self deny: beenHereToo.
+ proc terminate!
Item was added:
+ ----- Method: MutexTest>>testMutexBlockedInCriticalInEnsure (in category 'tests') -----
+ testMutexBlockedInCriticalInEnsure
+ "This tests whether a process that is inside the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unchanged."
+ | lock sock proc wait |
+ lock := Mutex new.
+ sock := Semaphore new.
+ proc := [lock critical: [sock wait]] fork.
+ wait := [[] ensure: [lock critical: []]] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == lock.
+ self deny: lock isEmpty.
+ self assert: lock isOwned.
+ wait terminate.
+ Processor yield.
+ self assert: wait isTerminated.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == nil.
+ self assert: lock isEmpty.
+ self assert: lock isOwned.
+ proc terminate!
Item was added:
+ ----- Method: MutexTest>>testMutexBlockedInCriticalInEnsure2 (in category 'tests') -----
+ testMutexBlockedInCriticalInEnsure2
+ "This tests whether a process that is inside the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unchanged."
+ | lock sock proc wait beenHere beenHereToo |
+ lock := Mutex new.
+ sock := Semaphore new.
+ beenHere := beenHereToo := false.
+ proc := [lock critical: [sock wait]] fork.
+ wait := [[] ensure: [lock critical: [beenHere := true]. beenHereToo := true]] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == lock.
+ self deny: lock isEmpty.
+ self assert: lock isOwned.
+ wait terminate.
+ Processor yield.
+ self assert: wait isTerminated.
+ self assert: proc suspendingList == sock.
+ self assert: wait suspendingList == nil.
+ self assert: lock isEmpty.
+ self assert: lock isOwned.
+ self deny: beenHere.
+ self assert: beenHereToo.
+ proc terminate!
Item was added:
+ ----- Method: MutexTest>>testMutexInCriticalWait2 (in category 'tests') -----
+ testMutexInCriticalWait2
+ "This tests whether a process that has got past the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unowned."
+ | lock sock proc beenHere beenHereToo |
+ lock := Mutex new.
+ sock := Semaphore new.
+ beenHere := beenHereToo := false.
+ proc := [lock critical: [sock wait. beenHere := true]. beenHereToo := true] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ proc terminate.
+ self deny: lock isOwned.
+ self assert: lock isEmpty.
+ self deny: beenHere.
+ self deny: beenHereToo!
Item was added:
+ ----- Method: MutexTest>>testMutexInCriticalWaitInEnsure (in category 'tests') -----
+ testMutexInCriticalWaitInEnsure
+ "This tests whether a process that has got past the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unowned."
+ | lock sock proc |
+ lock := Mutex new.
+ sock := Semaphore new.
+ proc := [[] ensure: [lock critical: [sock wait]]] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ proc terminate.
+ self deny: lock isOwned.
+ self assert: lock isEmpty!
Item was added:
+ ----- Method: MutexTest>>testMutexInCriticalWaitInEnsure2 (in category 'tests') -----
+ testMutexInCriticalWaitInEnsure2
+ "This tests whether a process that has got past the primitiveEnterCriticalSection in Mutex>>critical:
+ leaves it unowned."
+ | lock sock proc beenHere beenHereToo |
+ lock := Mutex new.
+ sock := Semaphore new.
+ beenHere := beenHereToo := false.
+ proc := [[] ensure: [lock critical: [sock wait. beenHere := true]. beenHereToo := true]] fork.
+ Processor yield.
+ self assert: proc suspendingList == sock.
+ proc terminate.
+ self deny: lock isOwned.
+ self assert: lock isEmpty.
+ self assert: beenHere.
+ self assert: beenHereToo!
Item was removed:
- ----- Method: MutexTest>>testUnwindMutexBlockedInCritical (in category 'tests') -----
- testUnwindMutexBlockedInCritical "self run: #testMutexBlockedInCritical"
- "This tests whether a mutex that is inside the primitiveEnterCriticalSection in Mutex>>critical:
- leaves it unchanged."
- | lock sock proc wait |
- lock := Mutex new.
- sock := Semaphore new.
- proc := [lock critical: [sock wait]] fork.
- wait := [[] ensure: [lock critical: []]] fork.
- Processor yield.
- self assert: proc suspendingList == sock.
- self assert: wait suspendingList == lock.
- self deny: lock isEmpty.
- self assert: lock isOwned.
- wait terminate.
- Processor yield.
- self assert: wait isTerminated.
- self assert: proc suspendingList == sock.
- self assert: wait suspendingList == nil.
- self assert: lock isEmpty.
- self assert: lock isOwned
- !
Item was removed:
- ----- Method: ProcessTest>>expectedFailures (in category 'failures') -----
- expectedFailures
-
- ^ #(testTerminateTerminatingProcess testResumeTerminatingProcess)!
Item was changed:
----- Method: ProcessTest>>testResumeTerminatingProcess (in category 'tests') -----
testResumeTerminatingProcess
+ "An attempt to resume a terminating process in the middle of preparing its suspended context should raise an error;
+ one possible solution is to nil the suspended context of the terminating process while executing the vulnerable code"
+
+ "Note: another solution could be to run the vulnerable part of the code in #terminate using #valueUnpreemptively;
+ currently, however, #valueUnpreemptively is affected by the #priority: bug described here:
+ http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-December/217473…"
- "An attempt to resume a terminating process should probably raise an error;
- leave this test as an expected failure for the moment."
+ | terminatee terminator unwound |
+ unwound := false.
+ terminatee := [[Processor activeProcess suspend] ensure: [unwound := unwound not]] newProcess.
+ terminatee priority: Processor activePriority + 1.
+ terminatee resume.
- | terminatee terminator resumed |
- terminatee := [semaphore critical:[]. resumed := true] fork.
- Processor yield.
terminator := [terminatee terminate] newProcess.
+ terminator priority: Processor activePriority + 1.
+ self assert: terminatee isSuspended.
- self assert: terminatee suspendingList == semaphore.
self assert: terminator isSuspended.
"run terminator and stop inside #terminate"
+ [terminator suspendedContext selectorToSendOrSelf == #bottomContext] whileFalse: [terminator step].
+ self assert: terminatee suspendedContext isNil.
+ self deny: unwound.
- terminator runUntil: [:ctx | ctx selectorToSendOrSelf = #priority:].
self assert: terminator isSuspended.
+ "resuming the terminatee process should causes the VM to raise an error while its suspended context is nil"
- "resume the terminatee process and and check if the VM raises an error;
- an error is expected because terminatee's suspendedContext equals nil"
self should: [terminatee resume] raise: Error.
+ self assert: terminatee suspendedContext isNil.
+ self deny: unwound.
+ self assert: terminator isSuspended.
+ "now let the terminator finish its interrupted termination of the terminatee process"
- "now let the terminator finish terminating the terminatee process"
terminator resume.
- Processor yield.
- self assert: resumed isNil.
self assert: terminatee isTerminated.
+ self assert: unwound.
self assert: terminator isTerminated!
Item was changed:
----- Method: ProcessTest>>testTerminateTerminatingProcess (in category 'tests') -----
testTerminateTerminatingProcess
+ "An attempt to terminate a terminating process in the middle of preparing its suspended context should do nothing;
+ one possible solution is to nil the suspended context of the terminating process while executing the vulnerable code."
+
+ "Note: another solution could be to run the vulnerable part of the code in #terminate using #valueUnpreemptively;
+ currently, however, #valueUnpreemptively is affected by the #priority: bug described here:
+ http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-December/217473…"
- "An attempt to terminate a terminating process should probably raise an error;
- leave this test as an expected failure for the moment."
+ | terminatee terminator unwound |
+ unwound := false.
+ terminatee := [[Processor activeProcess suspend] ensure: [unwound := unwound not]] newProcess.
+ terminatee priority: Processor activePriority + 1.
+ terminatee resume.
- | terminatee terminator resumed |
- terminatee := [semaphore critical:[]. resumed := true] fork.
- Processor yield.
terminator := [terminatee terminate] newProcess.
+ terminator priority: Processor activePriority + 1.
+ self assert: terminatee isSuspended.
- self assert: terminatee suspendingList == semaphore.
self assert: terminator isSuspended.
"run terminator and stop inside #terminate"
+ [terminator suspendedContext selectorToSendOrSelf == #bottomContext] whileFalse: [terminator step].
+ self assert: terminatee suspendedContext isNil.
+ self deny: unwound.
- terminator runUntil: [:ctx | ctx selectorToSendOrSelf = #priority:].
self assert: terminator isSuspended.
+ "terminating the terminatee process again should make no difference while its suspended context is nil"
+ terminatee terminate.
+ self assert: terminatee suspendedContext isNil.
+ self deny: unwound.
+ self assert: terminator isSuspended.
+ "now let the terminator finish its interrupted termination of the terminatee process"
- "terminate the terminatee process again and let the termination finish;
- an error is expected because #terminate detected multiple termination"
- self should: [terminatee terminate] raise: Error.
- "now let the terminator finish terminating the terminatee process"
terminator resume.
- Processor yield.
- self assert: resumed isNil.
self assert: terminatee isTerminated.
+ self assert: unwound.
self assert: terminator isTerminated!
Item was added:
+ ----- Method: ProcessTest>>testTerminateTerminatingProcessBeforeUnwindTo (in category 'tests') -----
+ testTerminateTerminatingProcessBeforeUnwindTo
+ "Terminating a terminatee process after the terminator process restarted the terminatee
+ process should unwind the terminatee process and let the terminator process terminate."
+
+ | terminator terminatee unwound |
+ unwound := false.
+ terminatee := [[Semaphore new wait] ensure: [unwound := true]] fork.
+ Processor yield.
+ terminator := [terminatee terminate] newProcess.
+ self assert: terminatee isBlocked.
+ self assert: terminator isSuspended.
+ terminator runUntil: [:ctx | ctx selectorToSendOrSelf = #resume].
+ "terminator steps until terminatee's unwind context is set"
+ terminator suspendedContext nextInstruction. "skip terminatee resume instruction"
+ terminator resume. "and run until parked at wait"
+ "terminatee is inside its helper stack, about to call #unwindTo:"
+ self assert: terminatee isSuspended.
+ terminatee terminate.
+ self assert: terminatee isTerminated.
+ self assert: unwound.
+ Processor yield.
+ self assert: terminator isTerminated!
Item was removed:
- ----- Method: SemaphoreTest>>expectedFailures (in category 'failures') -----
- expectedFailures
-
- ^ #(testUnwindSemaInCriticalWait)!
Item was added:
+ ----- Method: SemaphoreTest>>testSemaAfterCriticalWait2 (in category 'tests') -----
+ testSemaAfterCriticalWait2
+ "This tests whether a process that has just left the wait in Semaphore>>critical:
+ leaves it with signaling the associated semaphore."
+ | s p beenHere |
+ s := Semaphore new. beenHere := false.
+ p := [s critical: [beenHere := true]] forkAt: Processor activePriority-1.
+ "wait until p entered the critical section"
+ [p suspendingList == s] whileFalse: [(Delay forMilliseconds: 10) wait].
+ "Now that p entered it, signal the semaphore. p now 'owns' the semaphore
+ but since we are running at higher priority than p it will not get to do
+ anything."
+ s signal.
+ p terminate.
+ self assert: 1 equals: s excessSignals.
+ self deny: beenHere!
Item was added:
+ ----- Method: SemaphoreTest>>testSemaAfterCriticalWaitInEnsure (in category 'tests') -----
+ testSemaAfterCriticalWaitInEnsure
+ "This tests whether a process that has just left the wait in Semaphore>>critical:
+ leaves it with signaling the associated semaphore."
+ | s p |
+ s := Semaphore new.
+ p := [[] ensure: [s critical:[]]] forkAt: Processor activePriority-1.
+ "wait until p entered the critical section"
+ [p suspendingList == s] whileFalse: [(Delay forMilliseconds: 10) wait].
+ "Now that p entered it, signal the semaphore. p now 'owns' the semaphore
+ but since we are running at higher priority than p it will not get to do
+ anything."
+ s signal.
+ p terminate.
+ self assert: 1 equals: s excessSignals!
Item was added:
+ ----- Method: SemaphoreTest>>testSemaAfterCriticalWaitInEnsure2 (in category 'tests') -----
+ testSemaAfterCriticalWaitInEnsure2
+ "This tests whether a process that has just left the wait in Semaphore>>critical:
+ leaves it with signaling the associated semaphore."
+ | s p beenHere |
+ s := Semaphore new. beenHere := false.
+ p := [[] ensure: [s critical: [beenHere := true]]] forkAt: Processor activePriority-1.
+ "wait until p entered the critical section"
+ [p suspendingList == s] whileFalse: [(Delay forMilliseconds: 10) wait].
+ "Now that p entered it, signal the semaphore. p now 'owns' the semaphore
+ but since we are running at higher priority than p it will not get to do
+ anything."
+ s signal.
+ p terminate.
+ self assert: 1 equals: s excessSignals.
+ self assert: beenHere!
Item was added:
+ ----- Method: SemaphoreTest>>testSemaInCriticalWait2 (in category 'tests') -----
+ testSemaInCriticalWait2
+ "This tests whether a process that has entered the wait in Semaphore>>critical:
+ leaves it without signaling the associated semaphore."
+ | s p beenHere |
+ s := Semaphore new. beenHere := false.
+ p := [s critical: [beenHere := true]] fork.
+ Processor yield.
+ self assert: s equals: p suspendingList.
+ p terminate.
+ self assert: 0 equals: s excessSignals.
+ self deny: beenHere!
Item was added:
+ ----- Method: SemaphoreTest>>testSemaInCriticalWaitInEnsure (in category 'tests') -----
+ testSemaInCriticalWaitInEnsure
+ "This tests whether a process that has entered the wait in Semaphore>>critical:
+ leaves it without signaling the associated semaphore."
+ | s p |
+ s := Semaphore new.
+ p := [[] ensure: [s critical: []]] fork.
+ Processor yield.
+ self assert: s equals: p suspendingList.
+ p terminate.
+ self assert: 0 equals: s excessSignals!
Item was added:
+ ----- Method: SemaphoreTest>>testSemaInCriticalWaitInEnsure2 (in category 'tests') -----
+ testSemaInCriticalWaitInEnsure2
+ "This tests whether a process that has entered the wait in Semaphore>>critical:
+ leaves it without signaling the associated semaphore."
+ | s p beenHere |
+ s := Semaphore new. beenHere := false.
+ p := [[] ensure: [s critical: [beenHere := true]]] fork.
+ Processor yield.
+ self assert: s equals: p suspendingList.
+ p terminate.
+ self assert: 0 equals: s excessSignals.
+ self deny: beenHere!
Item was removed:
- ----- Method: SemaphoreTest>>testUnwindSemaInCriticalWait (in category 'tests') -----
- testUnwindSemaInCriticalWait "self run: #testSemaInCriticalWait"
- "This tests whether a semaphore that has entered the wait in Semaphore>>critical:
- leaves it without signaling the associated semaphore."
- | s p |
- s := Semaphore new.
- p := [[] ensure: [s critical:[]]] fork.
- Processor yield.
- self assert:(p suspendingList == s).
- p terminate.
- self assert: 0 equals: s excessSignals!
Marcel Taeumel uploaded a new version of Kernel to project The Treated Inbox:
http://source.squeak.org/treated/Kernel-jar.1399.mcz
==================== Summary ====================
Name: Kernel-jar.1399
Author: jar
Time: 2 May 2021, 3:12:37.830089 pm
UUID: d288b516-c6f1-ce43-9061-2220422b8ab4
Ancestors: Kernel-jar.1398
Fix inconsistent implementation of an explicit and an implicit exception return.
I'd like to return to my original proposal in http://forum.world.st/The-Inbox-Kernel-nice-1391-mcz-tp5129040p5129084.html. The problem then was a bug in #outer that confused me. The bug has been fixed and the original proposal in my opinion makes sense again - to unify how the two kinds of exception return are implemented. Theoretically it's possible to change the #return definition in the future and then the two returns would diverge.
=============== Diff against Kernel-jar.1398 ===============
Item was changed:
----- Method: Context>>handleSignal: (in category 'private-exceptions') -----
handleSignal: exception
"Sent to handler (on:do:) contexts only.
Execute the handler action block"
| val |
<primitive: 199> "just a marker, fail and execute the following"
exception privHandlerContext: self contextTag.
self deactivateHandler. "Prevent re-entering the action block, unless it is explicitely rearmed"
val := [self fireHandlerActionForSignal: exception] ensure: [self reactivateHandler].
+ exception return: val "return from exception handlerContext if not otherwise directed in handle block"!
- self return: val "return from self if not otherwise directed in handle block"!
Marcel Taeumel uploaded a new version of Kernel to project The Treated Inbox:
http://source.squeak.org/treated/Kernel-jar.1415.mcz
==================== Summary ====================
Name: Kernel-jar.1415
Author: jar
Time: 20 November 2021, 12:22:39.046144 pm
UUID: 2058b1fd-425a-fa46-a2e0-297d1bfab952
Ancestors: Kernel-jar.1414
Fix a bug: when debugging things like this:
[^2] ensure: [Transcript cr; show: 'done']
if we step into the protected block [^2] and then step over ^2, we incorrectly get a BlockCannotReturn error.
This is an alternative proposal to Kernel-jar.1421 (and Kernel-jar.1413); the solution remains the same but it attempts to present a cleaner code (trying to address Christoph's objection in [2] and [3]).
The bug is described in detail in Kernel-nice.1407 and discussed in [1] and most recently in [2] and [3]:
[1] http://forum.world.st/stepping-over-non-local-return-in-a-protected-block-t…
[2] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216214.h…
[3] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-November/216971…
=============== Diff against Kernel-jar.1414 ===============
Item was changed:
----- Method: Context>>resume:through: (in category 'controlling') -----
resume: value through: firstUnwindCtxt
"Unwind thisContext to self and resume with value as result of last send.
Execute any unwind blocks while unwinding.
ASSUMES self is a sender of thisContext."
| ctxt unwindBlock |
self isDead ifTrue: [self cannotReturn: value to: self].
+ ctxt := firstUnwindCtxt value. "evaluate in case firstUnwindCtxt is a block (used in simulation)"
- ctxt := firstUnwindCtxt ifNil: [thisContext findNextUnwindContextUpTo: self].
[ctxt isNil] whileFalse:
[(ctxt tempAt: 2) ifNil:
[ctxt tempAt: 2 put: true.
unwindBlock := ctxt tempAt: 1.
thisContext terminateTo: ctxt.
unwindBlock value].
ctxt := ctxt findNextUnwindContextUpTo: self].
thisContext terminateTo: self.
^value
!
Item was changed:
----- Method: Context>>return:from: (in category 'instruction decoding') -----
return: value from: aSender
"For simulation. Roll back self to aSender and return value from it. Execute any unwind blocks on the way. ASSUMES aSender is a sender of self"
| newTop |
aSender isDead ifTrue:
[^self send: #cannotReturn: to: self with: {value}].
newTop := aSender sender.
(self findNextUnwindContextUpTo: newTop) ifNotNil:
+ [^self send: #aboutToReturn:through: to: self with: {value. [thisContext findNextUnwindContextUpTo: newTop]}].
- [^self send: #aboutToReturn:through: to: self with: {value. nil}].
self releaseTo: newTop.
newTop ifNotNil: [newTop push: value].
^newTop!
Marcel Taeumel uploaded a new version of Kernel to project The Treated Inbox:
http://source.squeak.org/treated/Kernel-jar.1421.mcz
==================== Summary ====================
Name: Kernel-jar.1421
Author: jar
Time: 15 November 2021, 2:01:40.493442 am
UUID: a0158f6a-ffeb-4946-bd0a-25ac6b1947d5
Ancestors: Kernel-eem.1420
Fix a bug: when debugging things like this:
[^2] ensure: [Transcript cr; show: 'done']
if we step into the protected block [^2] and then step over ^2, we incorrectly get a lockCannotReturn. The bug is described in detail in Kernel-nice.1407 and discussed in http://forum.world.st/stepping-over-non-local-return-in-a-protected-block-t…
Add a flag and an explanatory comment.
Supersedes Kernel-jar.1413; please kindly remove from the Inbox
=============== Diff against Kernel-eem.1420 ===============
Item was changed:
----- Method: Context>>resume:through: (in category 'controlling') -----
resume: value through: firstUnwindCtxt
"Unwind thisContext to self and resume with value as result of last send.
Execute any unwind blocks while unwinding.
ASSUMES self is a sender of thisContext."
| ctxt unwindBlock |
+ self flag: #simulator. "jar: see Note below"
self isDead ifTrue: [self cannotReturn: value to: self].
+ ctxt := firstUnwindCtxt ifNil: [thisContext findNextUnwindContextUpTo: self].
- ctxt := firstUnwindCtxt.
[ctxt isNil] whileFalse:
[(ctxt tempAt: 2) ifNil:
[ctxt tempAt: 2 put: true.
unwindBlock := ctxt tempAt: 1.
thisContext terminateTo: ctxt.
unwindBlock value].
ctxt := ctxt findNextUnwindContextUpTo: self].
thisContext terminateTo: self.
^value
+
+ "Note (11/2021): when debugging things like this:
+ [^2] ensure: [Transcript cr; show: 'done']
+ if we step into the protected block [^2] and then step over ^2, we incorrectly get a BlockCannotReturn.
+ To avoid this bug, a close interplay between #return:from: and #resume:through: has been established:
+ During simulation, #return:from: passes firstUnwindCtxt = nil to #aboutToReturn:through: which then
+ propagates to resume:through: and causes a fresh search for the first unwind context. The matter has
+ been discussed in http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216214.h…"!
- !
Item was changed:
----- Method: Context>>return:from: (in category 'instruction decoding') -----
return: value from: aSender
"For simulation. Roll back self to aSender and return value from it. Execute any unwind blocks on the way. ASSUMES aSender is a sender of self"
| newTop |
+ self flag: #simulator. "jar: see Note below"
aSender isDead ifTrue:
[^self send: #cannotReturn: to: self with: {value}].
newTop := aSender sender.
(self findNextUnwindContextUpTo: newTop) ifNotNil:
+ [^self send: #aboutToReturn:through: to: self with: {value. nil}].
- [:unwindProtectCtxt|
- ^self send: #aboutToReturn:through: to: self with: {value. unwindProtectCtxt}].
self releaseTo: newTop.
newTop ifNotNil: [newTop push: value].
+ ^newTop
+
+ "Note (11/2021): when debugging things like this:
+ [^2] ensure: [Transcript cr; show: 'done']
+ if we step into the protected block [^2] and then step over ^2, we incorrectly get a BlockCannotReturn.
+ To avoid this bug, a close interplay between #return:from: and #resume:through: has been established:
+ During simulation, #return:from: passes firstUnwindCtxt = nil to #aboutToReturn:through: which then
+ propagates to resume:through: and causes a fresh search for the first unwind context. The matter has
+ been discussed in http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216214.h…"!
- ^newTop!
Marcel Taeumel uploaded a new version of Kernel to project The Treated Inbox:
http://source.squeak.org/treated/Kernel-jar.1447.mcz
==================== Summary ====================
Name: Kernel-jar.1447
Author: jar
Time: 22 February 2022, 8:18:05.479858 pm
UUID: 3772b6a0-c6ab-634c-97bb-a82bda2ecb6a
Ancestors: Kernel-eem.1444
#terminate - latest version working independently of the revised suspend semantics in the latest VMs; structured similarly as original versions of #terminate in Squeak 1.x thru 3.5
supersede Kernel-jar.1443; please remove Kernel-jar.1443, Kernel-jar.1442, Kernel-jar.1437, Kernel-jar.1436, Kernel-jar.1435, Kernel-jar.1426
=============== Diff against Kernel-eem.1444 ===============
Item was added:
+ ----- Method: Context>>releaseCriticalSection: (in category 'private') -----
+ releaseCriticalSection: oldList
+ "Figure out if we are terminating a process that is in the ensure: block of a critical section.
+ In this case, if it hasn't made progress but is beyond the wait (which we can tell by the oldList
+ being one of the runnable lists, i.e. a LinkedList, not a Semaphore or Mutex, et al), then the ensure:
+ block needs to be run."
+
+ | selectorJustSent |
+ (self method pragmaAt: #criticalSection) ifNil: [^self].
+ (oldList isNil or: [oldList class == LinkedList]) ifFalse: [^self].
+ selectorJustSent := self selectorJustSentOrSelf.
+
+ "If still at the wait the ensure: block has not been activated, so signal to restore."
+ selectorJustSent == #wait ifTrue:
+ [self receiver signal].
+
+ "If still at the lock primitive and the lock primitive just acquired ownership (indicated by it answering false)
+ then the ensure block has not been activated, so explicitly run primitiveExitCriticalSection to unlock."
+ (selectorJustSent == #primitiveEnterCriticalSection
+ or: [selectorJustSent == #primitiveTestAndSetOwnershipOfCriticalSection]) ifTrue:
+ [(self stackPtr > 0
+ and: [self top == false]) ifTrue:
+ [self receiver primitiveExitCriticalSection]]!
Item was added:
+ ----- Method: Context>>runUntilReturnFrom: (in category 'private-exceptions') -----
+ runUntilReturnFrom: aContext
+ "Run the receiver (which must be its stack top context) until aContext returns. Avoid a context that cannot return.
+ Note: to avoid infinite recursion of MNU error inside unwind blocks, implement e.g. a wrapper around the message
+ sentTo: receiver in #doesNotUnderstand:. Note: This method is a trivialized version of #runUntilErrorOrReturnFrom:
+ and was intended to be used by #unwindTo as a helper method to unwind non-local returns inside unwind blocks."
+
+ | here unwindBottom newTop |
+ here := thisContext.
+ "Avoid a context that cannot return (see Note 1 below)"
+ unwindBottom := (self findContextSuchThat: [:ctx | ctx selector = #cannotReturn:]) ifNil: [aContext].
+ newTop := aContext sender.
+ "Insert ensure context under unwindBottom in self's stack (see Note 2 below)"
+ unwindBottom insertSender: (Context contextEnsure: [here jump]).
+ self jump. "Control jumps to the receiver's stack (see Note 2 below)"
+ "Control resumes here once the above inserted ensure block is executed (see #jump comments)"
+ ^newTop "Return the new top context (see Note 3 below)"
+
+ "Note 1: returning from #cannotReturn's sender would crash the VM so we install a guard ensure context right
+ above it; after returning here the unwind will continue safely. Try running and debugging this example
+ (avoid Proceeding the BCR error though; it may indeed crash the image):
+ [[[] ensure: [^2]] ensure: [^42]] fork"
+
+ "Note 2: the receiver (self) is run by jumping directly to it (the active process abandons thisContext and executes
+ self on its own stack; self must be its top context). However, before jumping to self we insert an ensure block under
+ unwindBottom context that will execute a jump back to thisContext when evaluated. The inserted guard ensure
+ context is removed once control jumps back to thisContext."
+
+ "Note 3: it doesn't matter newTop is not a proper stack top context because #unwindTo will only use it as a starting
+ point in the search for the next unwind context and the computation will never return here. We could make newTop
+ a proper top context by pushing nil to its stack (^newTop push: nil) if need be (see #jump comments).
+ Cf. the pattern in #runUntilErrorOrReturnFrom:: removing the inserted ensure context by stepping until popped
+ when executing non-local returns wouldn't work here and would fail tests testTerminateInNestedEnsureWithReturn1
+ through 4."!
Item was changed:
----- Method: Context>>unwindTo: (in category 'private-exceptions') -----
unwindTo: aContext
+ "Unwind the receiver to aContext to execute all pending unwind blocks."
+ self unwindTo: aContext safely: true!
- | ctx unwindBlock |
- ctx := self.
- [(ctx := ctx findNextUnwindContextUpTo: aContext) isNil] whileFalse: [
- (ctx tempAt: 2) ifNil:[
- ctx tempAt: 2 put: true.
- unwindBlock := ctx tempAt: 1.
- unwindBlock value]
- ].
- !
Item was added:
+ ----- Method: Context>>unwindTo:safely: (in category 'private-exceptions') -----
+ unwindTo: aContext safely: aBoolean
+ "Unwind self to aContext to execute pending #ensure:/#ifCurtailed: argument blocks between self
+ and aContext. If aBoolean is false, unwind only blocks that have not run yet, otherwise complete all
+ pending unwind blocks including those currently in the middle of their execution; these blocks will
+ just finish their execution. Run all unwinds on their original stack using #runUntilReturnFrom:."
+
+ | top ctx |
+ ctx := top := self.
+ aBoolean ifTrue: [
+ "If self is a top context of a stack already halfways through an unwind, complete the outer-most
+ unfinished unwind block first; all nested pending unwind blocks will be completed in the process;
+ see testTerminationDuringUnwind and tests in ProcessTest/UnwindTest.
+ Note: Halfway-through blocks have already set the complete variable (ctxt tempAt: 2) in their
+ defining #ensure:/#ifCurtailed contexts from nil to true; we'll search for the bottom-most one."
+ | outerMost |
+ ctx isUnwindContext ifFalse: [ctx := ctx findNextUnwindContextUpTo: aContext].
+ [ctx isNil] whileFalse: [
+ (ctx tempAt:2) ifNotNil: [
+ outerMost := ctx].
+ ctx := ctx findNextUnwindContextUpTo: aContext].
+ outerMost ifNotNil: [top := (top runUntilReturnFrom: outerMost) sender]].
+ "By now no halfway-through unwind blocks are on the stack.
+ Note: top points to the former outerMost sender now, i.e. to the next context to be explored."
+
+ ctx := top.
+ "#findNextUnwindContextUpTo: starts searching from the receiver's sender so we must check
+ the receiver explicitly whether it is an unwind context; see testTerminateEnsureAsStackTop.
+ Create a new top context (i.e. a new branch off the original stack) for each pending unwind block
+ (ctxt tempAt: 1) and execute it on the unwind block's stack to evaluate non-local returns correctly."
+ ctx isUnwindContext ifFalse: [ctx := ctx findNextUnwindContextUpTo: aContext].
+ [ctx isNil] whileFalse: [
+ (ctx tempAt: 2) ifNil: [
+ ctx tempAt: 2 put: true.
+ top := (ctx tempAt: 1) asContextWithSender: ctx. "see the note below"
+ top runUntilReturnFrom: top].
+ ctx := ctx findNextUnwindContextUpTo: aContext]
+ "Note: Cf. the unwind pattern in the previous versions of unwindTo: (1999-2021). Using #value
+ instead of #runUntilReturnFrom: lead to a failure to evaluate some non-local returns correctly;
+ a non-local return must be evaluated in the evaluation context (sender chain) in which it was defined."!
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;
+ allow all unwind blocks to run; if they are currently in progress, let them finish.
+ If the process is in the middle of a #critical: critical section, release it properly."
+
+ "This is the kind of behavior we expect when terminating a healthy process.
+ See further comments in #terminateAggressively and #destroy methods dealing
+ with process termination when closing the debugger or after a catastrophic failure."
+
+ "If terminating the active process, create a parallel stack and run unwinds from there;
+ if terminating a suspended process, again, create a parallel stack for the process being
+ terminated and resume the suspended process to complete its termination from the new
+ parallel stack. Use a priority higher than the active priority to make the process that
+ invoked the termination wait for its completion."
- "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."
+ "If terminating a suspended process (including runnable and blocked), always suspend
+ the terminating process first so it doesn't accidentally get woken up. Equally important is
+ the side effect of the suspension; In 2022 a new suspend semantics has been introduced:
+ the revised #suspend backs up a process waiting on a conditional variable to the send that
+ invoked the wait state, while the pre-2022 #suspend simply removed the process from
+ the conditional variable's list it was previously waiting on; see Process>>suspend comments.
+ Release any method marked with the <criticalSection> pragma via #releaseCriticalSection[:].
+ Execute termination in the ensure argument block to ensure it completes even if the
+ terminator process itself gets terminated before it's finished; see testTerminateInTerminate."
+
+ | context |
- | ctxt unwindBlock oldList outerMost |
self isActiveProcess ifTrue: [
+ context := thisContext.
+ ^[context unwindTo: nil. self suspend] asContext jump].
- "If terminating the active process, suspend it first and terminate it as a suspended process."
- [self terminate] fork.
- ^self suspend].
+ [] ensure: [ | oldList |
+ oldList := myList.
+ self suspend.
+ context := suspendedContext ifNil: [^self].
+ suspendedContext := [
+ context releaseCriticalSection: oldList; unwindTo: nil.
+ self suspend] asContext.
+ self priority: Processor activePriority + 1; resume]!
- "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]!