A new version of Kernel was added to project The Inbox: http://source.squeak.org/inbox/Kernel-ct.1405.mcz
==================== Summary ====================
Name: Kernel-ct.1405 Author: ct Time: 15 May 2021, 3:56:24.713389 pm UUID: 9a92be9b-d778-b54f-b659-713451a2ddb2 Ancestors: Kernel-nice.1402
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended. There are two reasons for that:
1. Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: a := [true ifTrue: [^ 1]. 2]. "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" a value. In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
2. The pattern proposed by Jaromir reminds me of the current implementation of Object >> #doesNotUnderstand: or Object >> #at:, that is, when the error was resumed, just try it again in the manner of a (potentially) infinite recursion. While the issue with infinite debuggers (which was eventually tripped by exactly this pattern) has been solved some time ago [1], I do not really agree with the pattern in general - it makes it unnecessarily hard for ToolSet implementors to consciously resume from an error instead of retrying it (which we have an extra selector on Exception for).
[1] http://forum.world.st/Please-try-out-Fixes-for-debugger-invocation-during-co...
=============== Diff against Kernel-nice.1402 ===============
Item was added: + ----- Method: BlockCannotReturn>>defaultResumeValue (in category 'defaults') ----- + defaultResumeValue + + ^ self result!
Item was changed: ----- Method: Context>>cannotReturn: (in category 'private-exceptions') ----- cannotReturn: result
+ closureOrNil ifNotNil: [ + | resumptionValue | + resumptionValue := self cannotReturn: result to: self home sender. + self pc > self endPC ifTrue: [ + "This block has ended, continue with sender" + thisContext privSender: self sender]. + ^ resumptionValue]. - closureOrNil ifNotNil: [^ self cannotReturn: result to: self home sender]. Processor debugWithTitle: 'Computation has been terminated!!' translated full: false.!
The #defaultResumeValue was actually a commit slip and should not have been included in this version.
Nevertheless, this raises another question - what would you expect from this example to return?
a := [true ifTrue: [^ 1] yourself]. "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" [a value] on: BlockCannotReturn do: [:ex | ex resume].
Should it be 1 or nil? In the Trunk, is it nil, if we override #defaultResumeValue as below, it will be 1.
(Note that I appended #yourself to the block to prevent inlined compilation - when inlined, the snippet always returns nil because the optimization does not assume that the methodReturn could be skipped ...)
Best, Christoph
----- Carpe Squeak! -- Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Christoph,
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended.
I was considering the idea whether it could make sense to "fix" the stack but dumped it eventually because it would completely change the semantics of non-local returns. In my opinion once the home context sender is not available it means it's gone irreparably. There are two situation to consider: double return to the same context within one stack (e.g. the return context is gone or it may even still exist but its pc has moved) or the home sender is on a different context stack - in case of forks etc. Non-local returns between forks could in theory work but not in the current environment; Squeak strictly requires the home context sender to be on the same stack.
Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: `a := [true ifTrue: [^ 1]. 2].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `a value.` In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
My interpretation of this example is the home sender of ^1 is gone once the first do-it ends. So the second do-it correctly, in my opinion, invokes the cannot return error. Current Trunk returning 2 seems wildly incorrect to me.
Resuming BlockCannotReturn sounds crazy to me by definition and you're right: it's set as resumable, I haven't noticed. I'd set it non-resumable. If a block cannot return, why should we be tempted to do that? :)
Nevertheless, this raises another question - what would you expect from this example to return?
`a := [true ifTrue: [^ 1] yourself].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `[a value] on: BlockCannotReturn do: [:ex | ex resume].`
Should it be 1 or nil? In the Trunk, is it nil, if we override #defaultResumeValue as below, it will be 1.
This is a mean example... My fix ended in an infinite loop :) I tried to fix it but the only clean solution that occurred to me is to set BlockCannotReturn as non-resumable.
But again, my interpretation here is any attempt to "repair" the context that cannot return means a substantial change of the non-local return semantics. It means I'd return nil because the meaning of the error is: I cannot return 1 to my home sender. Here's one of my examples I'm planning to send as test cases to the Inbox soon:
[ [ [ ] ensure: [ [] ensure: [ ^Transcript show: 'x1']. Transcript show: 'x2'] ] ensure: [ Transcript show: 'x3']. Transcript show: 'x4' ] fork
In this case the expected outcome is ---> x1 x3. Neither x2 nor x4 should be printed (x2 is intentionally skipped by the non-local return and x4 is outside the ensure blocks). With the fix you propose the outcome is either ---> x1 x2 x3 if pressed Abandon or ---> x1 x2 x3 x4 if pressed Proceed - this would be equivalent to no non-local return at all :)
I hope I'll be able to put the tests together and publish in a few days.
Juan Vuletich showed me a beautiful example about the non-local return semantics - take a look in [1] in the middle of the post.
Thanks for discussing this!
best,
[1] [[Cuis-dev] Unwind mechanism during termination is broken and inconsistent](https://lists.cuis.st/mailman/archives/cuis-dev/2021-April/003055.html)
----- ^[^ Jaromir -- Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Jaromir,
first of all, please let me clarify one thing:
BlockCannotReturn is an Error, of course, and thus under normal circumstances should never be resumed. Just like it would usually be an anti-pattern to resume from a subscript error, KeyNotFound error, or anything else. Not for nothing, Error implements #isResumeable with false so that sending #resume[:] to an error will usually raise an IllegalResumeAttempt.
Nevertheless, our debugger supports enforcing the resumption of an exception using Proceed, even if the exception is not marked as resumable. So this should only be used for debugging scenarios, but still, I think that we could - and maybe also should - make it as convenient as possible to perform this non-recommended operation if you are debugging some unusual scenarios. This is mainly helpful to manipulate the behavior of a program during runtime (similar to the "return entered value" in the stack list menu of the debugger).
Resuming BlockCannotReturn sounds crazy to me by definition and you're right: it's set as resumable, I haven't noticed. I'd set it non-resumable. If a block cannot return, why should we be tempted to do that? :)
I don't know whether there was a special reason to mark it as resumable, but if there wasn't one, I totally agree with you on this point! Just talkin' about the conscious decision of the debugging person to ignore the non-resumability of this exception and resume it anyway, just like you can do it for any other normal error. :-)
`a := [true ifTrue: [^ 1]. 2].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `a value.`
In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
My interpretation of this example is the home sender of ^1 is gone once the first do-it ends. So the second do-it correctly, in my opinion, invokes the cannot return error. Current Trunk returning 2 seems wildly incorrect to me.
Again, just for clarification: The example does raise a BlockCannotReturn in the second expression in the current Trunk as it should do. :-) Maybe my explanation was not precise enough here. I was only referring to the unusual edge case in which you attempt to proceed the process from this BlockCannotReturn error. In the Trunk, the expression will return 2 in this case. With your example, you won't be able to escape from the situation without pressing Abandon.
[ [ [ ] ensure: [ [] ensure: [ ^Transcript show: 'x1']. Transcript show: 'x2'] ] ensure: [ Transcript show: 'x3']. Transcript show: 'x4' ] fork
In this case the expected outcome is ---> x1 x3. Neither x2 nor x4 should be printed (x2 is intentionally skipped by the non-local return and x4 is outside the ensure blocks). With the fix you propose the outcome is either ---> x1 x2 x3 if pressed Abandon or ---> x1 x2 x3 x4 if pressed Proceed - this would be equivalent to no non-local return at all :)
Wait, wait, wait. This smells to me. :-) #cannotReturn: should not be *resumed* after the error was abandoned. Otherwise, something is wrong with the termination logic. Process >> #terminate *must not* resume in this place. Terminating means only executing all uncompleted unwind contexts. I just reverted to the version ct 1/17/2021 18:35 of Process >> #terminate and with regard to your example, both implementations of #cannotReturn: behave the save (---> x1 x3) as expected. Hm, I'm sorry, but Process >> #terminate is not yet done correctly IMHO. I'll send you another message concerning this method soon, but I'm checking many things while drafting the message, so please have some more patience. :-)
Thanks for discussing this!
Thank *you* for whipping the "machine room" of Squeak into shape again and bringing up all these interesting questions along the way! :-)
Best, Christoph
________________________________ Von: Squeak-dev squeak-dev-bounces@lists.squeakfoundation.org im Auftrag von Jaromir Matas m@jaromir.net Gesendet: Samstag, 15. Mai 2021 23:20:54 An: squeak-dev@lists.squeakfoundation.org Betreff: Re: [squeak-dev] The Inbox: Kernel-ct.1405.mcz
Hi Christoph,
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended.
I was considering the idea whether it could make sense to "fix" the stack but dumped it eventually because it would completely change the semantics of non-local returns. In my opinion once the home context sender is not available it means it's gone irreparably. There are two situation to consider: double return to the same context within one stack (e.g. the return context is gone or it may even still exist but its pc has moved) or the home sender is on a different context stack - in case of forks etc. Non-local returns between forks could in theory work but not in the current environment; Squeak strictly requires the home context sender to be on the same stack.
Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: `a := [true ifTrue: [^ 1]. 2].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `a value.` In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
My interpretation of this example is the home sender of ^1 is gone once the first do-it ends. So the second do-it correctly, in my opinion, invokes the cannot return error. Current Trunk returning 2 seems wildly incorrect to me.
Resuming BlockCannotReturn sounds crazy to me by definition and you're right: it's set as resumable, I haven't noticed. I'd set it non-resumable. If a block cannot return, why should we be tempted to do that? :)
Nevertheless, this raises another question - what would you expect from this example to return?
`a := [true ifTrue: [^ 1] yourself].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `[a value] on: BlockCannotReturn do: [:ex | ex resume].`
Should it be 1 or nil? In the Trunk, is it nil, if we override #defaultResumeValue as below, it will be 1.
This is a mean example... My fix ended in an infinite loop :) I tried to fix it but the only clean solution that occurred to me is to set BlockCannotReturn as non-resumable.
But again, my interpretation here is any attempt to "repair" the context that cannot return means a substantial change of the non-local return semantics. It means I'd return nil because the meaning of the error is: I cannot return 1 to my home sender. Here's one of my examples I'm planning to send as test cases to the Inbox soon:
[ [ [ ] ensure: [ [] ensure: [ ^Transcript show: 'x1']. Transcript show: 'x2'] ] ensure: [ Transcript show: 'x3']. Transcript show: 'x4' ] fork
In this case the expected outcome is ---> x1 x3. Neither x2 nor x4 should be printed (x2 is intentionally skipped by the non-local return and x4 is outside the ensure blocks). With the fix you propose the outcome is either ---> x1 x2 x3 if pressed Abandon or ---> x1 x2 x3 x4 if pressed Proceed - this would be equivalent to no non-local return at all :)
I hope I'll be able to put the tests together and publish in a few days.
Juan Vuletich showed me a beautiful example about the non-local return semantics - take a look in [1] in the middle of the post.
Thanks for discussing this!
best,
[1] [[Cuis-dev] Unwind mechanism during termination is broken and inconsistent](https://lists.cuis.st/mailman/archives/cuis-dev/2021-April/003055.html)
----- ^[^ Jaromir -- Sent from: http://forum.world.st/Squeak-Dev-f45488.html Smalltalk - Squeak - Dev | Mailing List Archivehttp://forum.world.st/Squeak-Dev-f45488.html forum.world.st Squeak - Dev forum and mailing list archive. The general-purpose Squeak developers list
Hi Christoph,
Christoph Thiede wrote
[ [ [ ] ensure: [ [] ensure: [ ^Transcript show: 'x1']. Transcript show: 'x2'] ] ensure: [ Transcript show: 'x3']. Transcript show: 'x4' ] fork
In this case the expected outcome is ---> x1 x3. Neither x2 nor x4
should be printed (x2 is intentionally skipped by the non-local return and x4 is outside the ensure blocks). With the fix you propose the outcome is either ---> x1 x2 x3 if pressed Abandon or ---> x1 x2 x3 x4 if pressed Proceed - this would be equivalent to no non-local return at all :)
Wait, wait, wait. This smells to me. :-) #cannotReturn: should not be *resumed* after the error was abandoned. Otherwise, something is wrong with the termination logic. Process >> #terminate *must not* resume in this place. Terminating means only executing all uncompleted unwind contexts. I just reverted to the version ct 1/17/2021 18:35 of Process >> #terminate and with regard to your example, both implementations of #cannotReturn: behave the save (---> x1 x3) as expected. Hm, I'm sorry, but Process >> #terminate is not yet done correctly IMHO.
What happened: in your changeset you made #cannotReturn: return to its sender after choosing Proceed, i.e. the execution continued into the preceding #ensure context. This, I feel, introduces an incorrect semantics here: the real sender of the #cannotReturn: was the VM that tried to execute a non-local return and failed. For lack of other options (I guess) the VM set the #ensure: context as a sender of #cannotReturn: - my guess the main purpose of this link is to keep the stack chain available for unwind - but not for resuming the execution - so this is my objection.
Proceeding after BlockCannotReturn actually means: Proceed as if no non-local return was ever there. This doesn't seem right to me but maybe there could be good a reason to do this in debugging, I don't know.
The crucial point here is #terminate now attempts to complete the outer-most unfinished unwind block instead of the inner-most only (i.e. the deepest unfinished unwind block as opposed to the most shallow one). In this particular example current #terminate correctly leaves the unfinished unwind block after abandoning BlockCannotReturn (skipping 'x2') and finds another unwind block with 'x3'. But if you apply your #cannotReturn patch and press Proceed then #cannotReturn: returns and current #terminate simply continues the unwind within the current unwind block and finds 'x2'.
To avoid any confusion: by no means #terminate resumes after BlockCannotReturn - absolutely not, #terminate just continues unwinding remaining unwind blocks only; that's different.
With your example, you won't be able to escape from the situation without pressing Abandon.
Well, yes, that was the point: I can't imagine a reasonable next step from a non-local return with no home context to return to... That's why I looped #cannotReturn: to itself with the only way out via Abandon, i.e. terminating :)
I'm sending an alternative proposal to solve the infinite recursion of BlockCannotReturn:
``` cannotReturn: result
closureOrNil ifNotNil: [self cannotReturn: result to: self home sender. [self cannotReturnRecursive: result to: self home sender. self notify: 'Invoking an infinite loop'. true] whileTrue]. Processor debugWithTitle: 'Computation has been terminated!' translated full: false. ```
where #cannotReturnRecursive:to: sets a Boolean variable for the user to be able to deal with the recursion. Resuming BCR or not should no longer be an issue...
I know you're questioning whether Abandoning the debugger should be equivalent to terminating; or more precisely you're suggesting termination logic should be reduced to follow a normal return or exception return logic, i.e. skipping the unwind blocks currently under evaluation as discussed in [1]. As you know I disagree here and maintain the general termination logic should be as broad as possible but I see your point in reducing the termination logic in case of abandoning a debugger in case the debugged process is broken.
Thanks and best regards!
[1] http://forum.world.st/The-semantics-of-halfway-executed-unwind-contexts-duri...
----- ^[^ Jaromir -- Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Jaromir,
preamble: Yes, I agree that we need something different than the current #terminate for the debugger's Abandon - see [1]. Correct me if I am wrong, but I think further termination details do not need to be discussed in this thread but only in [1]. :-)
This [proposal], I feel, introduces an incorrect semantics here: the real sender of the #cannotReturn: was the VM that tried to execute a non-local return and failed. For lack of other options (I guess) the VM set the #ensure: context as a sender of #cannotReturn: - my guess the main purpose of this link is to keep the stack chain available for unwind - but not for resuming the execution - so this is my objection.
Thanks for the explanation. This is where our mental models appear to clash: I am not thinking of the VM as an instance that can be the sender of a method - the VM always resists on a meta-level apart from the actual code or stack trace. A sender has to be a context, and a context has to have things such a method, a pc, and a receiver - but what would these properties be for the VM itself? I would rather compare #cannotReturn: to #doesNotUnderstand:, which is a substitute for a different send/instruction in the causing method and validly has that causing method as a sender. You could also think of "^ foo" as a shortcut for "thisContext (home) return: foo" to make sense of this.
Proceeding after BlockCannotReturn actually means: Proceed as if no non-local return was ever there.
Yes ...
This doesn't seem right to me but maybe there could be good a reason to do this in debugging, I don't know.
... my point here is: Proceeding from an error almost always doesn't seem "right". :-) It is always a decision by the debugging programmer to override the default control flow and switch to the "next plausible alternative control flow", i.e., resume as if the error would have never been raised. Applied to the attempt to return from a method, for me, this means to ignore the return (thinking of it in message sends: to ignore the "thisContext (home) return"). Yeah, and if there is no further statement after that return, my best understanding of the user's intention to "proceed" would be to return to the place from where the block has been invoked ...
I'm sending an alternative proposal to solve the infinite recursion of BlockCannotReturn
Hm, in this example, you moved the relevant logic into Process >> #complete:to:. This means that BlockCannotReturn(Exception) >> #resumeUnchecked: will not have the same effect, won't it? :-( Also, can you convince me why you would need some extra state in the exception for this?
Argh, here is another example which does not yet match my expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here. ^^
Best, Christoph
[1] http://forum.world.st/The-semantics-of-halfway-executed-unwind-contexts-duri...
Hi Christoph,
... my point here is: Proceeding from an error almost always doesn't seem "right". :-) It is always a decision by the debugging programmer to override the default control flow and switch to the "next plausible alternative control flow", i.e., resume as if the error would have never been raised. Applied to the attempt to return from a method, for me, this means to ignore the return (thinking of it in message sends: to ignore the "thisContext (home) return"). Yeah, and if there is no further statement after that return, my best understanding of the user's intention to "proceed" would be to return to the place from where the block has been invoked ...
Agreed :) The more I think about it the more I like it ;) And well, the non-local return could have been a typo anyways... so actually, this makes the best sense and preserves all options open for the user - perfect!
Also, can you convince me why you would need some extra state in the exception for this?
No, I hated adding an extra state :) The only thing I really need for the full #terminate to work correctly is a way to distinguish between normal proceeding the computation and resuming the unwind procedure when the BlockCannotReturn error occurs inside an unwind block currently being evaluated. All I need then is a Warning the computation proceeds beyond the BlockCannotReturn; here's what I mean:
``` cannotReturn: result
closureOrNil ifNotNil: [ | resumptionValue | resumptionValue := self cannotReturn: result to: self home sender. ProceedBlockCannotReturn new signal: 'This block has ended, continue with sender?'. self pc > self endPC ifTrue: [ "This block has ended, continue with sender" thisContext privSender: self sender]. ^ resumptionValue]. Processor debugWithTitle: 'Computation has been terminated!' translated full: false ```
So if you're fine with this addition, full #terminate would recognize when it's beyond BlockCannotReturn and would continue unwinding the non-local return accordingly.
I think it's useful to warn the user about such an unusual (and new) option as Proceeding safely beyond BlockCannotReturn anyway :)
Argh, here is another example which does not yet match my expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my understanding of it)
1) if you print-it or do-it you get Computation terminated instead of Block cannot return
2) so I wrapped it in [] value ``` [sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value ``` Now it raises BlockCannotReturn and returns 2 with your changeset (if Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
I'm confused...
Many thanks for your comments and your proposed solution to cannot return; I'll update #terminate and remove my previous attempts from the Inbox. best,
----- ^[^ Jaromir -- Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Jaromir,
sorry, I had already addressed your ProceedBlockCannotReturn warning in [1] instead - but this thread is definitely the better place to continue this discussion. :-)
As already mentioned there, I doubt that warning the user again after he/she already had deliberately proceeded from an interpreter error makes much sense. But this is also not a strong opinion, so once again I'm calling for a third opinion here!
---
To now address this example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
Very unfortunately, I cannot reproduce any longer what I have reported there myself. But the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here. closureOrNil is nil here because the compiler does inline the ifTrue instruction - add a yourself behind the [^ 1] and you will get a correct BlockCannotReturn which you also can resume from to return the 2. Anyway, I originally brought the example to illustrate that all the VM optimizations with inlined message sends and whatever else could possibly destroy our assumptions about returnable blocks and contexts. But for the superordinate discussion here, I just would treat this as an imperfect VM implementation but nothing to worry about. :D
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
Ouh, these are several simulator bugs. And if you forego the enclosing [] value and step into the ^1, you get just a different MNU ... This should be addressed, too. :-) Without the enclosing [] value, I could fix both buttons by inserting the following in Context >> #return:from: right after the assignment to newTop which you can try out by loading Kernel-ct.1409 frmo the inbox. With the enclosing [] value, however, it is not that easy. Case B) you mentioned is worst. In this example, the simulated snippet answers 1 instead of 2! If you debug the simulator ("debug button action" of the "Into" button in the last step), you can see that the difference here is that unless the VM executor, the simulator knows about the home context of the top context even if the latter has a nil sender (see Context >> #methodReturnTop). I can't tell which one of them is wrong, but they are not consistent, this is not good. ^^
Best, Christoph
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216211.ht...
--- Sent from Squeak Inbox Talk
On 2021-05-30T11:50:52-05:00, m@jaromir.net wrote:
Hi Christoph,
... my point here is: Proceeding from an error almost always doesn't seem "right". :-) It is always a decision by the debugging programmer to override the default control flow and switch to the "next plausible alternative control flow", i.e., resume as if the error would have never been raised. Applied to the attempt to return from a method, for me, this means to ignore the return (thinking of it in message sends: to ignore the "thisContext (home) return"). Yeah, and if there is no further statement after that return, my best understanding of the user's intention to "proceed" would be to return to the place from where the block has been invoked ...
Agreed :) The more I think about it the more I like it ;) And well, the non-local return could have been a typo anyways... so actually, this makes the best sense and preserves all options open for the user - perfect!
Also, can you convince me why you would need some extra state in the exception for this?
No, I hated adding an extra state :) The only thing I really need for the full #terminate to work correctly is a way to distinguish between normal proceeding the computation and resuming the unwind procedure when the BlockCannotReturn error occurs inside an unwind block currently being evaluated. All I need then is a Warning the computation proceeds beyond the BlockCannotReturn; here's what I mean:
cannotReturn: result closureOrNil ifNotNil: [ | resumptionValue | resumptionValue := self cannotReturn: result to: self home sender. ProceedBlockCannotReturn new signal: 'This block has ended, continue with sender?'. self pc > self endPC ifTrue: [ "This block has ended, continue with sender" thisContext privSender: self sender]. ^ resumptionValue]. Processor debugWithTitle: 'Computation has been terminated!' translated full: false
So if you're fine with this addition, full #terminate would recognize when it's beyond BlockCannotReturn and would continue unwinding the non-local return accordingly.
I think it's useful to warn the user about such an unusual (and new) option as Proceeding safely beyond BlockCannotReturn anyway :)
Argh, here is another example which does not yet match my expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my understanding of it)
- if you print-it or do-it you get Computation terminated instead of Block
cannot return
- so I wrapped it in [] value
[sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value
Now it raises BlockCannotReturn and returns 2 with your changeset (if Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
I'm confused...
Many thanks for your comments and your proposed solution to cannot return; I'll update #terminate and remove my previous attempts from the Inbox. best,
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Christoph,
I've addressed your comments regarding the BlockCannotReturn behavior in [1] and copy my comments here (and expand a bit):
Isn't that ProceedBlockCannotReturn tautologous? I think that by actively proceeding from a BlockCannotReturn error users already accept that they are going to resume execution in another way.
Well, the very proceeding from a BlockCannotReturn error sort of violates common sense but during our lengthy discussion you convinced me it makes a very good sense when troubleshooting :) The idea is by no means trivial - unlike hitting Proceed :) So an extra warning can't hurt...
But more importantly, I need something to know the user let the process continue after reaching the BlockCannotReturn error - thus the new ProceedBlockCannotReturn exception which allows Process >> #complete:to: to deal with the new course of events.
It's just that introducing ProceedBlockCannotReturn seemed the least intrusive to the code and it's working. It allowed me to remove the extra state I introduced earlier and hated it :)
Apart from that, the message text of your new warning is not correct if self pc <= self endPC. :-)
Yes, and I'd like to make the warning message more verbose so even if someone hit Proceed without much thinking they could get an idea what's about to happen :) Should the warning interfere with some potential automation efforts we could come up with some alternative way.
Handling BlockCannotReturn error is no doubt a separate issue from #terminate and I included your patch in Kernel-jar.1414 along with the main termination code because it perfectly complements it and prevents the disastrous crashes while allowing to Proceed the BCR error when troubleshooting (or maybe even for exception handling, I'll have to refresh my memory: I'm referring to your example:
``` "Both statements need to be executed separately in a Workspace" a := [true ifTrue: [^ 1] yourself] [a value] on: BlockCannotReturn do: [:ex | ex resume] ``` ).
To your other great example:
``` sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2 ```
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
[...] the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here.
Yes, I agree; your example clearly shows there's definitely more to the problem that needs to be studied and discussed :) I don't like the 'computation terminated' message either; it's seems to me a bit out of place (I mean calling the Debugger directly rather than raisin an exception - it confuses me) and maybe even the whole #cannotReturn: should be simplified - I just didn't want to start messing with this when working on #terminate :)
I very much look forward to your further comments and ideas.
Best,
Jaromir
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-November/216999....
^[^ Jaromir --
Sent from Squeak Inbox Talk
On 2021-08-22T17:33:59+02:00, christoph.thiede@student.hpi.uni-potsdam.de wrote:
Hi Jaromir,
sorry, I had already addressed your ProceedBlockCannotReturn warning in [1] instead - but this thread is definitely the better place to continue this discussion. :-)
As already mentioned there, I doubt that warning the user again after he/she already had deliberately proceeded from an interpreter error makes much sense. But this is also not a strong opinion, so once again I'm calling for a third opinion here!
To now address this example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
Very unfortunately, I cannot reproduce any longer what I have reported there myself. But the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here. closureOrNil is nil here because the compiler does inline the ifTrue instruction - add a yourself behind the [^ 1] and you will get a correct BlockCannotReturn which you also can resume from to return the 2. Anyway, I originally brought the example to illustrate that all the VM optimizations with inlined message sends and whatever else could possibly destroy our assumptions about returnable blocks and contexts. But for the superordinate discussion here, I just would treat this as an imperfect VM implementation but nothing to worry about. :D
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
Ouh, these are several simulator bugs. And if you forego the enclosing [] value and step into the ^1, you get just a different MNU ... This should be addressed, too. :-) Without the enclosing [] value, I could fix both buttons by inserting the following in Context >> #return:from: right after the assignment to newTop which you can try out by loading Kernel-ct.1409 frmo the inbox. With the enclosing [] value, however, it is not that easy. Case B) you mentioned is worst. In this example, the simulated snippet answers 1 instead of 2! If you debug the simulator ("debug button action" of the "Into" button in the last step), you can see that the difference here is that unless the VM executor, the simulator knows about the home context of the top context even if the latter has a nil sender (see Context >> #methodReturnTop). I can't tell which one of them is wrong, but they are not consistent, this is not good. ^^
Best, Christoph
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216211.ht...
Sent from Squeak Inbox Talk
On 2021-05-30T11:50:52-05:00, m at jaromir.net wrote:
Hi Christoph,
... my point here is: Proceeding from an error almost always doesn't seem "right". :-) It is always a decision by the debugging programmer to override the default control flow and switch to the "next plausible alternative control flow", i.e., resume as if the error would have never been raised. Applied to the attempt to return from a method, for me, this means to ignore the return (thinking of it in message sends: to ignore the "thisContext (home) return"). Yeah, and if there is no further statement after that return, my best understanding of the user's intention to "proceed" would be to return to the place from where the block has been invoked ...
Agreed :) The more I think about it the more I like it ;) And well, the non-local return could have been a typo anyways... so actually, this makes the best sense and preserves all options open for the user - perfect!
Also, can you convince me why you would need some extra state in the exception for this?
No, I hated adding an extra state :) The only thing I really need for the full #terminate to work correctly is a way to distinguish between normal proceeding the computation and resuming the unwind procedure when the BlockCannotReturn error occurs inside an unwind block currently being evaluated. All I need then is a Warning the computation proceeds beyond the BlockCannotReturn; here's what I mean:
cannotReturn: result ����closureOrNil ifNotNil: [ ��������| resumptionValue | ��������resumptionValue := self cannotReturn: result to: self home sender. ��������ProceedBlockCannotReturn new signal: 'This block has ended, continue with sender?'. ��������self pc > self endPC ifTrue: [ ������������"This block has ended, continue with sender" ������������thisContext privSender: self sender]. ��������^ resumptionValue]. ����Processor debugWithTitle: 'Computation has been terminated!' translated full: false
So if you're fine with this addition, full #terminate would recognize when it's beyond BlockCannotReturn and would continue unwinding the non-local return accordingly.
I think it's useful to warn the user about such an unusual (and new) option as Proceeding safely beyond BlockCannotReturn anyway :)
Argh, here is another example which does not yet match my expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my understanding of it)
- if you print-it or do-it you get Computation terminated instead of Block
cannot return
- so I wrapped it in [] value
[sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value
Now it raises BlockCannotReturn and returns 2 with your changeset (if Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
I'm confused...
Many thanks for your comments and your proposed solution to cannot return; I'll update #terminate and remove my previous attempts from the Inbox. best,
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Christoph,
I just want to update this conversation with the latest: finally I found a solution that avoids BCR crashes in the new #teminate (Kernel-jar.1426) without the need for additional states or exemptions (so ProceedBlockCannotReturn says goodbye and your proposal in Kernel-ct.1405 is fully compatible with Kernel-jar.1426 without any further changes).
Thanks again for pointing out the weak spots in my solution! :)
Best,
^[^ Jaromir --
Sent from Squeak Inbox Talk
On 2021-11-17T21:54:33+01:00, mail@jaromir.net wrote:
Hi Christoph,
I've addressed your comments regarding the BlockCannotReturn behavior in [1] and copy my comments here (and expand a bit):
Isn't that ProceedBlockCannotReturn tautologous? I think that by actively proceeding from a BlockCannotReturn error users already accept that they are going to resume execution in another way.
Well, the very proceeding from a BlockCannotReturn error sort of violates common sense but during our lengthy discussion you convinced me it makes a very good sense when troubleshooting :) The idea is by no means trivial - unlike hitting Proceed :) So an extra warning can't hurt...
But more importantly, I need something to know the user let the process continue after reaching the BlockCannotReturn error - thus the new ProceedBlockCannotReturn exception which allows Process >> #complete:to: to deal with the new course of events.
It's just that introducing ProceedBlockCannotReturn seemed the least intrusive to the code and it's working. It allowed me to remove the extra state I introduced earlier and hated it :)
Apart from that, the message text of your new warning is not correct if self pc <= self endPC. :-)
Yes, and I'd like to make the warning message more verbose so even if someone hit Proceed without much thinking they could get an idea what's about to happen :) Should the warning interfere with some potential automation efforts we could come up with some alternative way.
Handling BlockCannotReturn error is no doubt a separate issue from #terminate and I included your patch in Kernel-jar.1414 along with the main termination code because it perfectly complements it and prevents the disastrous crashes while allowing to Proceed the BCR error when troubleshooting (or maybe even for exception handling, I'll have to refresh my memory: I'm referring to your example:
"Both statements need to be executed separately in a Workspace" a := [true ifTrue: [^ 1] yourself] [a value] on: BlockCannotReturn do: [:ex | ex resume]
).
To your other great example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
[...] the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here.
Yes, I agree; your example clearly shows there's definitely more to the problem that needs to be studied and discussed :) I don't like the 'computation terminated' message either; it's seems to me a bit out of place (I mean calling the Debugger directly rather than raisin an exception - it confuses me) and maybe even the whole #cannotReturn: should be simplified - I just didn't want to start messing with this when working on #terminate :)
I very much look forward to your further comments and ideas.
Best,
Jaromir
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-November/216999....
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-08-22T17:33:59+02:00, christoph.thiede at student.hpi.uni-potsdam.de wrote:
Hi Jaromir,
sorry, I had already addressed your ProceedBlockCannotReturn warning in [1] instead - but this thread is definitely the better place to continue this discussion. :-)
As already mentioned there, I doubt that warning the user again after he/she already had deliberately proceeded from an interpreter error makes much sense. But this is also not a strong opinion, so once again I'm calling for a third opinion here!
To now address this example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
Very unfortunately, I cannot reproduce any longer what I have reported there myself. But the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here. closureOrNil is nil here because the compiler does inline the ifTrue instruction - add a yourself behind the [^ 1] and you will get a correct BlockCannotReturn which you also can resume from to return the 2. Anyway, I originally brought the example to illustrate that all the VM optimizations with inlined message sends and whatever else could possibly destroy our assumptions about returnable blocks and contexts. But for the superordinate discussion here, I just would treat this as an imperfect VM implementation but nothing to worry about. :D
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
Ouh, these are several simulator bugs. And if you forego the enclosing [] value and step into the ^1, you get just a different MNU ... This should be addressed, too. :-) Without the enclosing [] value, I could fix both buttons by inserting the following in Context >> #return:from: right after the assignment to newTop which you can try out by loading Kernel-ct.1409 frmo the inbox. With the enclosing [] value, however, it is not that easy. Case B) you mentioned is worst. In this example, the simulated snippet answers 1 instead of 2! If you debug the simulator ("debug button action" of the "Into" button in the last step), you can see that the difference here is that unless the VM executor, the simulator knows about the home context of the top context even if the latter has a nil sender (see Context >> #methodReturnTop). I can't tell which one of them is wrong, but they are not consistent, this is not good. ^^
Best, Christoph
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216211.ht...
Sent from Squeak Inbox Talk
On 2021-05-30T11:50:52-05:00, m at jaromir.net wrote:
Hi Christoph,
... my point here is: Proceeding from an error almost always doesn't seem "right". :-) It is always a decision by the debugging programmer to override the default control flow and switch to the "next plausible alternative control flow", i.e., resume as if the error would have never been raised. Applied to the attempt to return from a method, for me, this means to ignore the return (thinking of it in message sends: to ignore the "thisContext (home) return"). Yeah, and if there is no further statement after that return, my best understanding of the user's intention to "proceed" would be to return to the place from where the block has been invoked ...
Agreed :) The more I think about it the more I like it ;) And well, the non-local return could have been a typo anyways... so actually, this makes the best sense and preserves all options open for the user - perfect!
Also, can you convince me why you would need some extra state in the exception for this?
No, I hated adding an extra state :) The only thing I really need for the full #terminate to work correctly is a way to distinguish between normal proceeding the computation and resuming the unwind procedure when the BlockCannotReturn error occurs inside an unwind block currently being evaluated. All I need then is a Warning the computation proceeds beyond the BlockCannotReturn; here's what I mean:
cannotReturn: result ����closureOrNil ifNotNil: [ ��������| resumptionValue | ��������resumptionValue := self cannotReturn: result to: self home sender. ��������ProceedBlockCannotReturn new signal: 'This block has ended, continue with sender?'. ��������self pc > self endPC ifTrue: [ ������������"This block has ended, continue with sender" ������������thisContext privSender: self sender]. ��������^ resumptionValue]. ����Processor debugWithTitle: 'Computation has been terminated!' translated full: false
So if you're fine with this addition, full #terminate would recognize when it's beyond BlockCannotReturn and would continue unwinding the non-local return accordingly.
I think it's useful to warn the user about such an unusual (and new) option as Proceeding safely beyond BlockCannotReturn anyway :)
Argh, here is another example which does not yet match my expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my understanding of it)
- if you print-it or do-it you get Computation terminated instead of Block
cannot return
- so I wrapped it in [] value
[sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value
Now it raises BlockCannotReturn and returns 2 with your changeset (if Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
I'm confused...
Many thanks for your comments and your proposed solution to cannot return; I'll update #terminate and remove my previous attempts from the Inbox. best,
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Jaromir, Eliot, all,
forgive me for awakening the dead again, but in attempt to clean up some old inbox versions, I have stumbled upon this again. While I think you have fixed all the BCR issues (?), this point seems unresolved:
- Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: a := [true ifTrue: [^ 1]. 2]. "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" a value.
In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
Have we ever come to an aggreement whether we want to support this - run the second statement, press Proceed, get the result 2 or not? After searching on the mailing list for more than 30 minutes, I still have not found a definite answer to that question. :D What I have found is: Currently, this does not work because we are niling the pc in Context>>#cannotReturn:to:. If I comment out that line, it would work. Why do we need to nil it out? Do we already have an example, preferably a test in the trunk, for that? :-)
Best, Christoph
--- Sent from Squeak Inbox Talk
On 2021-11-28T19:10:59+01:00, mail@jaromir.net wrote:
Hi Christoph,
I just want to update this conversation with the latest: finally I found a solution that avoids BCR crashes in the new #teminate (Kernel-jar.1426) without the need for additional states or exemptions (so ProceedBlockCannotReturn says goodbye and your proposal in Kernel-ct.1405 is fully compatible with Kernel-jar.1426 without any further changes).
Thanks again for pointing out the weak spots in my solution! :)
Best,
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-11-17T21:54:33+01:00, mail at jaromir.net wrote:
Hi Christoph,
I've addressed your comments regarding the BlockCannotReturn behavior in [1] and copy my comments here (and expand a bit):
Isn't that ProceedBlockCannotReturn tautologous? I think that by actively proceeding from a BlockCannotReturn error users already accept that they are going to resume execution in another way.
Well, the very proceeding from a BlockCannotReturn error sort of violates common sense but during our lengthy discussion you convinced me it makes a very good sense when troubleshooting :) The idea is by no means trivial - unlike hitting Proceed :) So an extra warning can't hurt...
But more importantly, I need something to know the user let the process continue after reaching the BlockCannotReturn error - thus the new ProceedBlockCannotReturn exception which allows Process >> #complete:to: to deal with the new course of events.
It's just that introducing ProceedBlockCannotReturn seemed the least intrusive to the code and it's working. It allowed me to remove the extra state I introduced earlier and hated it :)
Apart from that, the message text of your new warning is not correct if self pc <= self endPC. :-)
Yes, and I'd like to make the warning message more verbose so even if someone hit Proceed without much thinking they could get an idea what's about to happen :) Should the warning interfere with some potential automation efforts we could come up with some alternative way.
Handling BlockCannotReturn error is no doubt a separate issue from #terminate and I included your patch in Kernel-jar.1414 along with the main termination code because it perfectly complements it and prevents the disastrous crashes while allowing to Proceed the BCR error when troubleshooting (or maybe even for exception handling, I'll have to refresh my memory: I'm referring to your example:
"Both statements need to be executed separately in a Workspace" a := [true ifTrue: [^ 1] yourself] [a value] on: BlockCannotReturn do: [:ex | ex resume]
).
To your other great example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
[...] the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here.
Yes, I agree; your example clearly shows there's definitely more to the problem that needs to be studied and discussed :) I don't like the 'computation terminated' message either; it's seems to me a bit out of place (I mean calling the Debugger directly rather than raisin an exception - it confuses me) and maybe even the whole #cannotReturn: should be simplified - I just didn't want to start messing with this when working on #terminate :)
I very much look forward to your further comments and ideas.
Best,
Jaromir
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-November/216999....
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-08-22T17:33:59+02:00, christoph.thiede at student.hpi.uni-potsdam.de wrote:
Hi Jaromir,
sorry, I had already addressed your ProceedBlockCannotReturn warning in [1] instead - but this thread is definitely the better place to continue this discussion. :-)
As already mentioned there, I doubt that warning the user again after he/she already had deliberately proceeded from an interpreter error makes much sense. But this is also not a strong opinion, so once again I'm calling for a third opinion here!
To now address this example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
Very unfortunately, I cannot reproduce any longer what I have reported there myself. But the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here. closureOrNil is nil here because the compiler does inline the ifTrue instruction - add a yourself behind the [^ 1] and you will get a correct BlockCannotReturn which you also can resume from to return the 2. Anyway, I originally brought the example to illustrate that all the VM optimizations with inlined message sends and whatever else could possibly destroy our assumptions about returnable blocks and contexts. But for the superordinate discussion here, I just would treat this as an imperfect VM implementation but nothing to worry about. :D
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
Ouh, these are several simulator bugs. And if you forego the enclosing [] value and step into the ^1, you get just a different MNU ... This should be addressed, too. :-) Without the enclosing [] value, I could fix both buttons by inserting the following in Context >> #return:from: right after the assignment to newTop which you can try out by loading Kernel-ct.1409 frmo the inbox. With the enclosing [] value, however, it is not that easy. Case B) you mentioned is worst. In this example, the simulated snippet answers 1 instead of 2! If you debug the simulator ("debug button action" of the "Into" button in the last step), you can see that the difference here is that unless the VM executor, the simulator knows about the home context of the top context even if the latter has a nil sender (see Context >> #methodReturnTop). I can't tell which one of them is wrong, but they are not consistent, this is not good. ^^
Best, Christoph
[1] http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216211.ht...
Sent from Squeak Inbox Talk
On 2021-05-30T11:50:52-05:00, m at jaromir.net wrote:
Hi Christoph,
... my point here is: Proceeding from an error almost always doesn't seem "right". :-) It is always a decision by the debugging programmer to override the default control flow and switch to the "next plausible alternative control flow", i.e., resume as if the error would have never been raised. Applied to the attempt to return from a method, for me, this means to ignore the return (thinking of it in message sends: to ignore the "thisContext (home) return"). Yeah, and if there is no further statement after that return, my best understanding of the user's intention to "proceed" would be to return to the place from where the block has been invoked ...
Agreed :) The more I think about it the more I like it ;) And well, the non-local return could have been a typo anyways... so actually, this makes the best sense and preserves all options open for the user - perfect!
Also, can you convince me why you would need some extra state in the exception for this?
No, I hated adding an extra state :) The only thing I really need for the full #terminate to work correctly is a way to distinguish between normal proceeding the computation and resuming the unwind procedure when the BlockCannotReturn error occurs inside an unwind block currently being evaluated. All I need then is a Warning the computation proceeds beyond the BlockCannotReturn; here's what I mean:
cannotReturn: result ����closureOrNil ifNotNil: [ ��������| resumptionValue | ��������resumptionValue := self cannotReturn: result to: self home sender. ��������ProceedBlockCannotReturn new signal: 'This block has ended, continue with sender?'. ��������self pc > self endPC ifTrue: [ ������������"This block has ended, continue with sender" ������������thisContext privSender: self sender]. ��������^ resumptionValue]. ����Processor debugWithTitle: 'Computation has been terminated!' translated full: false
So if you're fine with this addition, full #terminate would recognize when it's beyond BlockCannotReturn and would continue unwinding the non-local return accordingly.
I think it's useful to warn the user about such an unusual (and new) option as Proceeding safely beyond BlockCannotReturn anyway :)
Argh, here is another example which does not yet match my expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM already has reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my understanding of it)
- if you print-it or do-it you get Computation terminated instead of Block
cannot return
- so I wrapped it in [] value
[sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value
Now it raises BlockCannotReturn and returns 2 with your changeset (if Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily continue
I'm confused...
Many thanks for your comments and your proposed solution to cannot return; I'll update #terminate and remove my previous attempts from the Inbox. best,
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi Christoph,
My understanding was we agreed that the above return was illegal (despite the fact it is "technically" possible to proceed).
As part of the "[squeak-dev] Re: Resuming on BlockCannotReturn exception" thread (https://lists.squeakfoundation.org/archives/list/squeak-dev@lists.squeakfoun...) Eliot wrote on Nov 11, 2023:
quote
IMO a better example is | expr | expr := true. [[expr ifTrue: [^ 1]. ^2] on: BlockCannotReturn do: [:ex | self halt. ex resume] ] fork
Here IMO we want a) the resume attempt to cause another BlockCannotReturn and/or terminate the computation opening a debugger which highlights ^1 b) we never want to execute ^2
unquote
On Nov 19, 2023 you asked:
quote
[[true ifTrue: [^ 1]] on: BlockCannotReturn do: #resume ] fork. "Illegal return from ^1"
Is this really illegal? Remember that resuming an exception is generally something special, and as this exception refers to a return instruction, resuming it is something I would definitely consider metaprogramming or worse. Thinking of ^ 1 as a syntactic sugar for "thisContext home return: 1", I would actually find it plausible that resuming from the exception would skip the #return: send. However, I have not read the entire conversation, so if Eliot says something different, please ignore this objection. :-)
unquote
Apologies for copying the responses into the message but I can't figure out how to reference the particular messages in the thread. The thread is super long and maybe you missed it. Or do you have some other concern on your mind?
Thanks for discussing these issues.
PS: Sorry for not responding to our other "open" discussions; I look forward to finding some time. Thanks for your patience.
Jaromir
On 25-Feb-24 12:29:38 AM, christoph.thiede@student.hpi.uni-potsdam.de wrote:
Hi Jaromir, Eliot, all,
forgive me for awakening the dead again, but in attempt to clean up some old inbox versions, I have stumbled upon this again. While I think you have fixed all the BCR issues (?), this point seems unresolved:
- Not in all situations, the receiver of #cannotReturn: is actually
unable to resume. Consider this example for a disproof:
a := [true ifTrue: [^ 1]. 2]. "Both statements need to be executed separately in a Workspace so
that [a outerContext sender] becomes nil!"
a value.
In this situation, it is valid to resume from BlockCannotReturn and
currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
Have we ever come to an aggreement whether we want to support this - run the second statement, press Proceed, get the result 2 or not? After searching on the mailing list for more than 30 minutes, I still have not found a definite answer to that question. :D What I have found is: Currently, this does not work because we are niling the pc in Context>>#cannotReturn:to:. If I comment out that line, it would work. Why do we need to nil it out? Do we already have an example, preferably a test in the trunk, for that? :-)
Best, Christoph
Sent from Squeak Inbox Talk https://github.com/hpi-swa-lab/squeak-inbox-talk
On 2021-11-28T19:10:59+01:00, mail@jaromir.net wrote:
Hi Christoph,
I just want to update this conversation with the latest: finally I
found a solution that avoids BCR crashes in the new #teminate (Kernel-jar.1426) without the need for additional states or exemptions (so ProceedBlockCannotReturn says goodbye and your proposal in Kernel-ct.1405 is fully compatible with Kernel-jar.1426 without any further changes).
Thanks again for pointing out the weak spots in my solution! :)
Best,
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-11-17T21:54:33+01:00, mail at jaromir.net wrote:
Hi Christoph,
I've addressed your comments regarding the BlockCannotReturn
behavior in [1] and copy my comments here (and expand a bit):
Isn't that ProceedBlockCannotReturn tautologous? I think that by
actively proceeding from a BlockCannotReturn error users already accept that they are going to resume execution in another way.
Well, the very proceeding from a BlockCannotReturn error sort of
violates common sense but during our lengthy discussion you convinced me it makes a very good sense when troubleshooting :) The idea is by no means trivial - unlike hitting Proceed :) So an extra warning can't hurt...
But more importantly, I need something to know the user let the
process continue after reaching the BlockCannotReturn error - thus the new ProceedBlockCannotReturn exception which allows Process >> #complete:to: to deal with the new course of events.
It's just that introducing ProceedBlockCannotReturn seemed the
least intrusive to the code and it's working. It allowed me to remove the extra state I introduced earlier and hated it :)
Apart from that, the message text of your new warning is not
correct if self pc <= self endPC. :-)
Yes, and I'd like to make the warning message more verbose so even
if someone hit Proceed without much thinking they could get an idea what's about to happen :) Should the warning interfere with some potential automation efforts we could come up with some alternative way.
Handling BlockCannotReturn error is no doubt a separate issue from
#terminate and I included your patch in Kernel-jar.1414 along with the main termination code because it perfectly complements it and prevents the disastrous crashes while allowing to Proceed the BCR error when troubleshooting (or maybe even for exception handling, I'll have to refresh my memory: I'm referring to your example:
"Both statements need to be executed separately in a Workspace" a := [true ifTrue: [^ 1] yourself] [a value] on: BlockCannotReturn do: [:ex | ex resume]
).
To your other great example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM
already has reset the pc in this example so we are helpless here.
[...] the computation terminated is also wrong, IMO, you should
get a BlockCannotReturn here.
Yes, I agree; your example clearly shows there's definitely more to
the problem that needs to be studied and discussed :) I don't like the 'computation terminated' message either; it's seems to me a bit out of place (I mean calling the Debugger directly rather than raisin an exception - it confuses me) and maybe even the whole #cannotReturn: should be simplified - I just didn't want to start messing with this when working on #terminate :)
I very much look forward to your further comments and ideas.
Best,
Jaromir
[1]
http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-November/216999....
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-08-22T17:33:59+02:00, christoph.thiede at
student.hpi.uni-potsdam.de wrote:
Hi Jaromir,
sorry, I had already addressed your ProceedBlockCannotReturn
warning in [1] instead - but this thread is definitely the better place to continue this discussion. :-)
As already mentioned there, I doubt that warning the user again
after he/she already had deliberately proceeded from an interpreter error makes much sense. But this is also not a strong opinion, so once again I'm calling for a third opinion here!
To now address this example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
Very unfortunately, I cannot reproduce any longer what I have
reported there myself. But the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here. closureOrNil is nil here because the compiler does inline the ifTrue instruction - add a yourself behind the [^ 1] and you will get a correct BlockCannotReturn which you also can resume from to return the 2. Anyway, I originally brought the example to illustrate that all the VM optimizations with inlined message sends and whatever else could possibly destroy our assumptions about returnable blocks and contexts. But for the superordinate discussion here, I just would treat this as an imperfect VM implementation but nothing to worry about. :D
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily
continue
Ouh, these are several simulator bugs. And if you forego the
enclosing [] value and step into the ^1, you get just a different MNU ... This should be addressed, too. :-)
Without the enclosing [] value, I could fix both buttons by
inserting the following in Context >> #return:from: right after the assignment to newTop which you can try out by loading Kernel-ct.1409 frmo the inbox.
With the enclosing [] value, however, it is not that easy. Case
B) you mentioned is worst. In this example, the simulated snippet answers 1 instead of 2! If you debug the simulator ("debug button action" of the "Into" button in the last step), you can see that the difference here is that unless the VM executor, the simulator knows about the home context of the top context even if the latter has a nil sender (see Context >> #methodReturnTop). I can't tell which one of them is wrong, but they are not consistent, this is not good. ^^
Best, Christoph
[1]
http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216211.ht...
Sent from Squeak Inbox Talk
On 2021-05-30T11:50:52-05:00, m at jaromir.net wrote:
Hi Christoph,
... my point here is: Proceeding from an error almost always
doesn't seem
"right". :-) It is always a decision by the debugging
programmer to
override the default control flow and switch to the "next
plausible
alternative control flow", i.e., resume as if the error would
have never
been raised. Applied to the attempt to return from a method,
for me, this
means to ignore the return (thinking of it in message sends:
to ignore the
"thisContext (home) return"). Yeah, and if there is no
further statement
after that return, my best understanding of the user's
intention to
"proceed" would be to return to the place from where the
block has been
invoked ...
Agreed :) The more I think about it the more I like it ;) And
well, the
non-local return could have been a typo anyways... so actually,
this makes
the best sense and preserves all options open for the user -
perfect!
Also, can you convince me why you would need some extra state
in the
exception for this?
No, I hated adding an extra state :) The only thing I really
need for the
full #terminate to work correctly is a way to distinguish
between normal
proceeding the computation and resuming the unwind procedure
when the
BlockCannotReturn error occurs inside an unwind block currently
being
evaluated. All I need then is a Warning the computation
proceeds beyond the
BlockCannotReturn; here's what I mean:
cannotReturn: result ����closureOrNil ifNotNil: [ ��������| resumptionValue | ��������resumptionValue := self cannotReturn: result to: self
home sender.
��������ProceedBlockCannotReturn new signal: 'This block has
ended, continue with
sender?'. ��������self pc > self endPC ifTrue: [ ������������"This block has ended, continue with sender" ������������thisContext privSender: self sender]. ��������^ resumptionValue]. ����Processor debugWithTitle: 'Computation has been
terminated!' translated
full: false
So if you're fine with this addition, full #terminate would
recognize when
it's beyond BlockCannotReturn and would continue unwinding the
non-local
return accordingly.
I think it's useful to warn the user about such an unusual (and
new) option
as Proceeding safely beyond BlockCannotReturn anyway :)
Argh, here is another example which does not yet match my
expectations:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM
already has
reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my
understanding of it)
- if you print-it or do-it you get Computation terminated
instead of Block
cannot return
- so I wrapped it in [] value
[sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value
Now it raises BlockCannotReturn and returns 2 with your
changeset (if
Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily
continue
I'm confused...
Many thanks for your comments and your proposed solution to
cannot return;
I'll update #terminate and remove my previous attempts from the
Inbox.
best,
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Okay, thank you for finding. Then I'm overruled here. Moved to treated. :-)
Best, Christoph
--- Sent from Squeak Inbox Talk
On 2024-02-25T13:29:31+00:00, mail@jaromir.net wrote:
Hi Christoph,
My understanding was we agreed that the above return was illegal (despite the fact it is "technically" possible to proceed).
As part of the "[squeak-dev] Re: Resuming on BlockCannotReturn exception" thread (https://lists.squeakfoundation.org/archives/list/squeak-dev(a)lists.squeakfo...) Eliot wrote on Nov 11, 2023:
quote
IMO a better example is | expr | expr := true. [[expr ifTrue: [^ 1]. ^2] on: BlockCannotReturn do: [:ex | self halt. ex resume] ] fork
Here IMO we want a) the resume attempt to cause another BlockCannotReturn and/or terminate the computation opening a debugger which highlights ^1 b) we never want to execute ^2
unquote
On Nov 19, 2023 you asked:
quote
[[true ifTrue: [^ 1]] on: BlockCannotReturn do: #resume ] fork. "Illegal return from ^1"
Is this really illegal? Remember that resuming an exception is generally something special, and as this exception refers to a return instruction, resuming it is something I would definitely consider metaprogramming or worse. Thinking of ^ 1 as a syntactic sugar for "thisContext home return: 1", I would actually find it plausible that resuming from the exception would skip the #return: send. However, I have not read the entire conversation, so if Eliot says something different, please ignore this objection. :-)
unquote
Apologies for copying the responses into the message but I can't figure out how to reference the particular messages in the thread. The thread is super long and maybe you missed it. Or do you have some other concern on your mind?
Thanks for discussing these issues.
PS: Sorry for not responding to our other "open" discussions; I look forward to finding some time. Thanks for your patience.
Jaromir
On 25-Feb-24 12:29:38 AM, christoph.thiede(a)student.hpi.uni-potsdam.de wrote:
Hi Jaromir, Eliot, all,
forgive me for awakening the dead again, but in attempt to clean up some old inbox versions, I have stumbled upon this again. While I think you have fixed all the BCR issues (?), this point seems unresolved:
- Not in all situations, the receiver of #cannotReturn: is actually
unable to resume. Consider this example for a disproof:
a := [true ifTrue: [^ 1]. 2]. "Both statements need to be executed separately in a Workspace so
that [a outerContext sender] becomes nil!"
a value. In this situation, it is valid to resume from BlockCannotReturn and
currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
Have we ever come to an aggreement whether we want to support this - run the second statement, press Proceed, get the result 2 or not? After searching on the mailing list for more than 30 minutes, I still have not found a definite answer to that question. :D What I have found is: Currently, this does not work because we are niling the pc in Context>>#cannotReturn:to:. If I comment out that line, it would work. Why do we need to nil it out? Do we already have an example, preferably a test in the trunk, for that? :-)
Best, Christoph
Sent from Squeak Inbox Talk https://github.com/hpi-swa-lab/squeak-inbox-talk
On 2021-11-28T19:10:59+01:00, mail(a)jaromir.net wrote:
Hi Christoph,
I just want to update this conversation with the latest: finally I
found a solution that avoids BCR crashes in the new #teminate (Kernel-jar.1426) without the need for additional states or exemptions (so ProceedBlockCannotReturn says goodbye and your proposal in Kernel-ct.1405 is fully compatible with Kernel-jar.1426 without any further changes).
Thanks again for pointing out the weak spots in my solution! :)
Best,
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-11-17T21:54:33+01:00, mail at jaromir.net wrote:
Hi Christoph,
I've addressed your comments regarding the BlockCannotReturn
behavior in [1] and copy my comments here (and expand a bit):
Isn't that ProceedBlockCannotReturn tautologous? I think that by
actively proceeding from a BlockCannotReturn error users already accept that they are going to resume execution in another way.
Well, the very proceeding from a BlockCannotReturn error sort of
violates common sense but during our lengthy discussion you convinced me it makes a very good sense when troubleshooting :) The idea is by no means trivial - unlike hitting Proceed :) So an extra warning can't hurt...
But more importantly, I need something to know the user let the
process continue after reaching the BlockCannotReturn error - thus the new ProceedBlockCannotReturn exception which allows Process >> #complete:to: to deal with the new course of events.
It's just that introducing ProceedBlockCannotReturn seemed the
least intrusive to the code and it's working. It allowed me to remove the extra state I introduced earlier and hated it :)
Apart from that, the message text of your new warning is not
correct if self pc <= self endPC. :-)
Yes, and I'd like to make the warning message more verbose so even
if someone hit Proceed without much thinking they could get an idea what's about to happen :) Should the warning interfere with some potential automation efforts we could come up with some alternative way.
Handling BlockCannotReturn error is no doubt a separate issue from
#terminate and I included your patch in Kernel-jar.1414 along with the main termination code because it perfectly complements it and prevents the disastrous crashes while allowing to Proceed the BCR error when troubleshooting (or maybe even for exception handling, I'll have to refresh my memory: I'm referring to your example:
"Both statements need to be executed separately in a Workspace" a := [true ifTrue: [^ 1] yourself] [a value] on: BlockCannotReturn do: [:ex | ex resume]
).
To your other great example:
sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2
I think this should eventually answer 2. Apparently, the VM
already has reset the pc in this example so we are helpless here.
[...] the computation terminated is also wrong, IMO, you should
get a BlockCannotReturn here.
Yes, I agree; your example clearly shows there's definitely more to
the problem that needs to be studied and discussed :) I don't like the 'computation terminated' message either; it's seems to me a bit out of place (I mean calling the Debugger directly rather than raisin an exception - it confuses me) and maybe even the whole #cannotReturn: should be simplified - I just didn't want to start messing with this when working on #terminate :)
I very much look forward to your further comments and ideas.
Best,
Jaromir
[1]
http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-November/216999....
^[^ Jaromir
Sent from Squeak Inbox Talk
On 2021-08-22T17:33:59+02:00, christoph.thiede at
student.hpi.uni-potsdam.de wrote:
Hi Jaromir,
sorry, I had already addressed your ProceedBlockCannotReturn
warning in [1] instead - but this thread is definitely the better place to continue this discussion. :-)
As already mentioned there, I doubt that warning the user again
after he/she already had deliberately proceeded from an interpreter error makes much sense. But this is also not a strong opinion, so once again I'm calling for a third opinion here!
To now address this example:
> ``` > sender := thisContext swapSender: nil. > true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" > thisContext privSender: sender. > ^ 2 > ```
Very unfortunately, I cannot reproduce any longer what I have
reported there myself. But the computation terminated is also wrong, IMO, you should get a BlockCannotReturn here. closureOrNil is nil here because the compiler does inline the ifTrue instruction - add a yourself behind the [^ 1] and you will get a correct BlockCannotReturn which you also can resume from to return the 2. Anyway, I originally brought the example to illustrate that all the VM optimizations with inlined message sends and whatever else could possibly destroy our assumptions about returnable blocks and contexts. But for the superordinate discussion here, I just would treat this as an imperfect VM implementation but nothing to worry about. :D
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily
continue
Ouh, these are several simulator bugs. And if you forego the
enclosing [] value and step into the ^1, you get just a different MNU ... This should be addressed, too. :-)
Without the enclosing [] value, I could fix both buttons by
inserting the following in Context >> #return:from: right after the assignment to newTop which you can try out by loading Kernel-ct.1409 frmo the inbox.
With the enclosing [] value, however, it is not that easy. Case
B) you mentioned is worst. In this example, the simulated snippet answers 1 instead of 2! If you debug the simulator ("debug button action" of the "Into" button in the last step), you can see that the difference here is that unless the VM executor, the simulator knows about the home context of the top context even if the latter has a nil sender (see Context >> #methodReturnTop). I can't tell which one of them is wrong, but they are not consistent, this is not good. ^^
Best, Christoph
[1]
http://lists.squeakfoundation.org/pipermail/squeak-dev/2021-August/216211.ht...
Sent from Squeak Inbox Talk
On 2021-05-30T11:50:52-05:00, m at jaromir.net wrote:
Hi Christoph,
> > ... my point here is: Proceeding from an error almost always
doesn't seem
> "right". :-) It is always a decision by the debugging
programmer to
> override the default control flow and switch to the "next
plausible
> alternative control flow", i.e., resume as if the error would
have never
> been raised. Applied to the attempt to return from a method,
for me, this
> means to ignore the return (thinking of it in message sends:
to ignore the
> "thisContext (home) return"). Yeah, and if there is no
further statement
> after that return, my best understanding of the user's
intention to
> "proceed" would be to return to the place from where the
block has been
> invoked ...
Agreed :) The more I think about it the more I like it ;) And
well, the
non-local return could have been a typo anyways... so actually,
this makes
the best sense and preserves all options open for the user -
perfect!
> Also, can you convince me why you would need some extra state
in the
> exception for this?
No, I hated adding an extra state :) The only thing I really
need for the
full #terminate to work correctly is a way to distinguish
between normal
proceeding the computation and resuming the unwind procedure
when the
BlockCannotReturn error occurs inside an unwind block currently
being
evaluated. All I need then is a Warning the computation
proceeds beyond the
BlockCannotReturn; here's what I mean:
cannotReturn: result ����closureOrNil ifNotNil: [ ��������| resumptionValue | ��������resumptionValue := self cannotReturn: result to: self
home sender.
��������ProceedBlockCannotReturn new signal: 'This block has
ended, continue with
sender?'. ��������self pc > self endPC ifTrue: [ ������������"This block has ended, continue with sender" ������������thisContext privSender: self sender]. ��������^ resumptionValue]. ����Processor debugWithTitle: 'Computation has been
terminated!' translated
full: false
So if you're fine with this addition, full #terminate would
recognize when
it's beyond BlockCannotReturn and would continue unwinding the
non-local
return accordingly.
I think it's useful to warn the user about such an unusual (and
new) option
as Proceeding safely beyond BlockCannotReturn anyway :)
> > Argh, here is another example which does not yet match my
expectations:
> ``` > sender := thisContext swapSender: nil. > true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" > thisContext privSender: sender. > ^ 2 > ``` > I think this should eventually answer 2. Apparently, the VM
already has
> reset the pc in this example so we are helpless here.
There's something wrong with this example :) (or my
understanding of it)
- if you print-it or do-it you get Computation terminated
instead of Block
cannot return
- so I wrapped it in [] value
[sender := thisContext swapSender: nil. true ifTrue: [^ 1]. "Proceed the BlockCannotReturn" thisContext privSender: sender. ^ 2] value
Now it raises BlockCannotReturn and returns 2 with your
changeset (if
Proceeded)...
BUT
if you debug it and: A) step through the ^1 - you get Message not understood B) step into ^1 - you don't get any error and can happily
continue
I'm confused...
Many thanks for your comments and your proposed solution to
cannot return;
I'll update #terminate and remove my previous attempts from the
Inbox.
best,
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
On May 15, 2021, at 2:20 PM, Jaromir Matas m@jaromir.net wrote:
Hi Christoph,
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended.
I was considering the idea whether it could make sense to "fix" the stack but dumped it eventually because it would completely change the semantics of non-local returns. In my opinion once the home context sender is not available it means it's gone irreparably. There are two situation to consider: double return to the same context within one stack (e.g. the return context is gone or it may even still exist but its pc has moved) or the home sender is on a different context stack - in case of forks etc. Non-local returns between forks could in theory work but not in the current environment; Squeak strictly requires the home context sender to be on the same stack.
+1
Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: `a := [true ifTrue: [^ 1]. 2].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `a value.` In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
My interpretation of this example is the home sender of ^1 is gone once the first do-it ends. So the second do-it correctly, in my opinion, invokes the cannot return error. Current Trunk returning 2 seems wildly incorrect to me.
+1000
Resuming BlockCannotReturn sounds crazy to me by definition and you're right: it's set as resumable, I haven't noticed. I'd set it non-resumable. If a block cannot return, why should we be tempted to do that? :)
Resumability *is not the same* as continuability. If I want to catch BlockCannotReturn, perform surgery to make continuing possible, I shouldn’t be prevented by the damn exception being non-resumable. In general I find non-resumabity being hard-coded and not controllable by an inst var & accessor no Exception to be a RRPITA (a right royal pain in the ass).
So I agree that BlockCannotReturn should be non-resumable by default. But I wish any and all exceptions could be made resumable via an accessor.
Nevertheless, this raises another question - what would you expect from this example to return?
`a := [true ifTrue: [^ 1] yourself].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `[a value] on: BlockCannotReturn do: [:ex | ex resume].`
Should it be 1 or nil? In the Trunk, is it nil, if we override #defaultResumeValue as below, it will be 1.
This is a mean example... My fix ended in an infinite loop :)
Because that’s what it is.
I tried to fix it but the only clean solution that occurred to me is to set BlockCannotReturn as non-resumable.
+1.
But again, my interpretation here is any attempt to "repair" the context that cannot return means a substantial change of the non-local return semantics. It means I'd return nil because the meaning of the error is: I cannot return 1 to my home sender. Here's one of my examples I'm planning to send as test cases to the Inbox soon:
[ [ [ ] ensure: [ [] ensure: [ ^Transcript show: 'x1']. Transcript show: 'x2'] ] ensure: [ Transcript show: 'x3']. Transcript show: 'x4' ] fork
In this case the expected outcome is ---> x1 x3. Neither x2 nor x4 should be printed (x2 is intentionally skipped by the non-local return and x4 is outside the ensure blocks). With the fix you propose the outcome is either ---> x1 x2 x3 if pressed Abandon or ---> x1 x2 x3 x4 if pressed Proceed - this would be equivalent to no non-local return at all :)
+1
I hope I'll be able to put the tests together and publish in a few days.
Juan Vuletich showed me a beautiful example about the non-local return semantics - take a look in [1] in the middle of the post.
Thanks for discussing this!
best,
[1] [[Cuis-dev] Unwind mechanism during termination is broken and inconsistent](https://lists.cuis.st/mailman/archives/cuis-dev/2021-April/003055.html)
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Hi again, Eliot,
Thanks a lot for your comments! I have a few questions below... thanks
On 2021-11-18T10:01:52-08:00, eliot.miranda@gmail.com wrote:
On May 15, 2021, at 2:20 PM, Jaromir Matas <m at jaromir.net> wrote:
Hi Christoph,
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended.
I was considering the idea whether it could make sense to "fix" the stack but dumped it eventually because it would completely change the semantics of non-local returns. In my opinion once the home context sender is not available it means it's gone irreparably. There are two situation to consider: double return to the same context within one stack (e.g. the return context is gone or it may even still exist but its pc has moved) or the home sender is on a different context stack - in case of forks etc. Non-local returns between forks could in theory work but not in the current environment; Squeak strictly requires the home context sender to be on the same stack.
+1
Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: `a := [true ifTrue: [^ 1]. 2].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `a value.` In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
My interpretation of this example is the home sender of ^1 is gone once the first do-it ends. So the second do-it correctly, in my opinion, invokes the cannot return error. Current Trunk returning 2 seems wildly incorrect to me.
+1000
Resuming BlockCannotReturn sounds crazy to me by definition and you're right: it's set as resumable, I haven't noticed. I'd set it non-resumable. If a block cannot return, why should we be tempted to do that? :)
Resumability *is not the same* as continuability. If I want to catch BlockCannotReturn, perform surgery to make continuing possible, I shouldn’t be prevented by the damn exception being non-resumable. In general I find non-resumabity being hard-coded and not controllable by an inst var & accessor no Exception to be a RRPITA (a right royal pain in the ass).
Yes, yes! this is what I was circling around but couldn't grasp: we do not want to "resume" from the BCR error but want to be able to "continue". Now I'd like to make sure I understand what you mean exactly:
A) The current #cannotReturn: allows you to continue (Proceed from BCR) and crash the VM if you do so:
closureOrNil ifNotNil: [^ self cannotReturn: result to: self home sender]. is this intentional or is it a bug (or just a lack of precautions)?
B) Initially I tried to "fix" the VM crash scenario by not allowing to continue:
closureOrNil ifNotNil: [self cannotReturn: result to: self home sender. thisContext privRefresh].
Is this what you're saying you DON'T want?
C) In this changeset Christoph proposed one *possible* path how to safely continue "after" a BCR error:
closureOrNil ifNotNil: [ | resumptionValue | resumptionValue := self cannotReturn: result to: self home sender. self pc > self endPC ifTrue: [ "This block has ended, continue with sender" thisContext privSender: self sender]. ^ resumptionValue].
Is that also what you say you DON'T want? Because that would be the type of obstacle you're referring to?
Forgive me if my questions sound silly. I lack your troubleshooting experience and came across examples like
[[self error: 'error'] ensure: [^2]] fork
when rewriting #terminate that I wanted to orderly terminate without crashes :)
Many thanks for your comments.
So I agree that BlockCannotReturn should be non-resumable by default. But I wish any and all exceptions could be made resumable via an accessor.
Nevertheless, this raises another question - what would you expect from this example to return?
`a := [true ifTrue: [^ 1] yourself].` "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" `[a value] on: BlockCannotReturn do: [:ex | ex resume].`
Should it be 1 or nil? In the Trunk, is it nil, if we override #defaultResumeValue as below, it will be 1.
This is a mean example... My fix ended in an infinite loop :)
Because that’s what it is.
I tried to fix it but the only clean solution that occurred to me is to set BlockCannotReturn as non-resumable.
+1.
But again, my interpretation here is any attempt to "repair" the context that cannot return means a substantial change of the non-local return semantics. It means I'd return nil because the meaning of the error is: I cannot return 1 to my home sender. Here's one of my examples I'm planning to send as test cases to the Inbox soon:
[ [ [ ] ensure: [ [] ensure: [ ^Transcript show: 'x1']. Transcript show: 'x2'] ] ensure: [ Transcript show: 'x3']. Transcript show: 'x4' ] fork
In this case the expected outcome is ---> x1 x3. Neither x2 nor x4 should be printed (x2 is intentionally skipped by the non-local return and x4 is outside the ensure blocks). With the fix you propose the outcome is either ---> x1 x2 x3 if pressed Abandon or ---> x1 x2 x3 x4 if pressed Proceed - this would be equivalent to no non-local return at all :)
+1
I hope I'll be able to put the tests together and publish in a few days.
Juan Vuletich showed me a beautiful example about the non-local return semantics - take a look in [1] in the middle of the post.
Thanks for discussing this!
best,
[1] [[Cuis-dev] Unwind mechanism during termination is broken and inconsistent](https://lists.cuis.st/mailman/archives/cuis-dev/2021-April/003055.html)
^[^ Jaromir
Sent from: http://forum.world.st/Squeak-Dev-f45488.html
Best regards, Jaromir
Hi Christoph, Hi Jaromir, Hi All,
On May 15, 2021, at 6:56 AM, commits@source.squeak.org wrote:
A new version of Kernel was added to project The Inbox: http://source.squeak.org/inbox/Kernel-ct.1405.mcz
==================== Summary ====================
Name: Kernel-ct.1405 Author: ct Time: 15 May 2021, 3:56:24.713389 pm UUID: 9a92be9b-d778-b54f-b659-713451a2ddb2 Ancestors: Kernel-nice.1402
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended. There are two reasons for that:
- Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: a := [true ifTrue: [^ 1]. 2]. "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" a value.
In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
- The pattern proposed by Jaromir reminds me of the current implementation of Object >> #doesNotUnderstand: or Object >> #at:, that is, when the error was resumed, just try it again in the manner of a (potentially) infinite recursion. While the issue with infinite debuggers (which was eventually tripped by exactly this pattern) has been solved some time ago [1], I do not really agree with the pattern in general - it makes it unnecessarily hard for ToolSet implementors to consciously resume from an error instead of retrying it (which we have an extra selector on Exception for).
But the pattern is a really useful one in a dynamic environment where one is doing edit and continue. I don’t see any practical alternative, having lived for several years without it, and been frustrated at not being able to catch MessageNotUnderstood, do something (become the receiver, create a method, etc), and continue.
To solve the infinite recursion problem we could implement a wrapper around the message sentTo: receiver e.g. [aMessage sentTo: receiver] on: MessageNotUnderstood do: [:ex| | args | args := ex message arguments. (ex receiver == self and: [ex message selector == aMessage selector and: [(1 to: aMessage size) allSatisfy: [:i| (args at: i) == (aMessage argumentAt: i)]]]) ifFalse: [ex pass]. self error: ‘infinite recursion in doesNotUnderstand:’]
(& similarly in at: etc). Right?
[1] http://forum.world.st/Please-try-out-Fixes-for-debugger-invocation-during-co...
=============== Diff against Kernel-nice.1402 ===============
Item was added:
- ----- Method: BlockCannotReturn>>defaultResumeValue (in category 'defaults') -----
- defaultResumeValue
- ^ self result!
Item was changed: ----- Method: Context>>cannotReturn: (in category 'private-exceptions') ----- cannotReturn: result
- closureOrNil ifNotNil: [
| resumptionValue |
resumptionValue := self cannotReturn: result to: self home sender.
self pc > self endPC ifTrue: [
"This block has ended, continue with sender"
thisContext privSender: self sender].
^ resumptionValue].
- closureOrNil ifNotNil: [^ self cannotReturn: result to: self home sender]. Processor debugWithTitle: 'Computation has been terminated!!' translated full: false.!
--==CelesteAttachment75902== Content-type: text/plain;charset=UTF-8
Hi Eliot,
On 2021-11-18T09:52:03-08:00, eliot.miranda@gmail.com wrote:
Hi Christoph, Hi Jaromir, Hi All,
On May 15, 2021, at 6:56 AM, commits at source.squeak.org wrote:
A new version of Kernel was added to project The Inbox: http://source.squeak.org/inbox/Kernel-ct.1405.mcz
==================== Summary ====================
Name: Kernel-ct.1405 Author: ct Time: 15 May 2021, 3:56:24.713389 pm UUID: 9a92be9b-d778-b54f-b659-713451a2ddb2 Ancestors: Kernel-nice.1402
Counterproposal to Kernel-jar.1404 for fixing VM crashes when resuming from a BlockCannotReturn. Instead of enforcing retrial, repair the context stack if the receiver has ended. There are two reasons for that:
- Not in all situations, the receiver of #cannotReturn: is actually unable to resume. Consider this example for a disproof: a := [true ifTrue: [^ 1]. 2]. "Both statements need to be executed separately in a Workspace so that [a outerContext sender] becomes nil!" a value.
In this situation, it is valid to resume from BlockCannotReturn and currently also possible in the Trunk. Note that BlockCannotReturn even overrides #isResumable to answer true, though the class comment discrecommends resuming it.
- The pattern proposed by Jaromir reminds me of the current implementation of Object >> #doesNotUnderstand: or Object >> #at:, that is, when the error was resumed, just try it again in the manner of a (potentially) infinite recursion. While the issue with infinite debuggers (which was eventually tripped by exactly this pattern) has been solved some time ago [1], I do not really agree with the pattern in general - it makes it unnecessarily hard for ToolSet implementors to consciously resume from an error instead of retrying it (which we have an extra selector on Exception for).
But the pattern is a really useful one in a dynamic environment where one is doing edit and continue. I donbt see any practical alternative, having lived for several years without it, and been frustrated at not being able to catch MessageNotUnderstood, do something (become the receiver, create a method, etc), and continue.
To solve the infinite recursion problem we could implement a wrapper around the message sentTo: receiver e.g. [aMessage sentTo: receiver] on: MessageNotUnderstood do: [:ex| | args | args := ex message arguments. (ex receiver == self and: [ex message selector == aMessage selector and: [(1 to: aMessage size) allSatisfy: [:i| (args at: i) == (aMessage argumentAt: i)]]]) ifFalse: [ex pass]. self error: binfinite recursion in doesNotUnderstand:b]
(& similarly in at: etc). Right?
Unbelievable!! Indeed it works like a charm :) If you felt like merging it into the Trunk right away it would simplify my #terminate changeset. I struggled to get around this recursion but this is of course the most appropriate place to address it. Thanks!
All worked, just replaced aMessage size with aMessage numArgs
Enclosing a fileout with #doesNotUnderstand:
doesNotUnderstand: aMessage "Handle the fact that there was an attempt to send the given message to the receiver but the receiver does not understand this message (typically sent from the machine when a message is sent to the receiver and no method is defined for that selector)."
"Testing: (3 activeProcess)"
| exception resumeValue | (exception := MessageNotUnderstood new) message: aMessage; receiver: self. resumeValue := exception signal. ^exception reachedDefaultHandler ifFalse: [resumeValue] ifTrue: [ [aMessage sentTo: self] on: MessageNotUnderstood do: [:ex| | args | args := ex message arguments. (ex receiver == self and: [ex message selector == aMessage selector and: [(1 to: aMessage numArgs) allSatisfy: [:i| (args at: i) == (aMessage argumentAt: i)]]]) ifFalse: [ex pass]. self error: 'infinite recursion in doesNotUnderstand:']]
[1] http://forum.world.st/Please-try-out-Fixes-for-debugger-invocation-during-co...
=============== Diff against Kernel-nice.1402 ===============
Item was added:
- ----- Method: BlockCannotReturn>>defaultResumeValue (in category 'defaults') -----
- defaultResumeValue
- ^ self result!
Item was changed: ----- Method: Context>>cannotReturn: (in category 'private-exceptions') ----- cannotReturn: result
- closureOrNil ifNotNil: [
| resumptionValue |
resumptionValue := self cannotReturn: result to: self home sender.
self pc > self endPC ifTrue: [
"This block has ended, continue with sender"
thisContext privSender: self sender].
^ resumptionValue].
- closureOrNil ifNotNil: [^ self cannotReturn: result to: self home sender]. Processor debugWithTitle: 'Computation has been terminated!!' translated full: false.!
["Object-doesNotUnderstand.st"] --==CelesteAttachment75902== Content-transfer-encoding: base64 Content-disposition: attachment;filename="Object-doesNotUnderstand.st" Content-type: application/octet-stream;name="Object-doesNotUnderstand.st"
J0Zyb20gU3F1ZWFrNi4wYWxwaGEgb2YgMjcgTWF5IDIwMjEgW2xhdGVzdCB1cGRhdGU6ICMy MDUzNV0gb24gMjEgTm92ZW1iZXIgMjAyMSBhdCA1OjI5OjUxIHBtJyENDSFPYmplY3QgbWV0 aG9kc0ZvcjogJ2Vycm9yIGhhbmRsaW5nJyBzdGFtcDogJ3Rlc3QgMTEvMjEvMjAyMSAxNzox NSchDWRvZXNOb3RVbmRlcnN0YW5kOiBhTWVzc2FnZSANCSAiSGFuZGxlIHRoZSBmYWN0IHRo YXQgdGhlcmUgd2FzIGFuIGF0dGVtcHQgdG8gc2VuZCB0aGUgZ2l2ZW4NCSAgbWVzc2FnZSB0 byB0aGUgcmVjZWl2ZXIgYnV0IHRoZSByZWNlaXZlciBkb2VzIG5vdCB1bmRlcnN0YW5kDQkg IHRoaXMgbWVzc2FnZSAodHlwaWNhbGx5IHNlbnQgZnJvbSB0aGUgbWFjaGluZSB3aGVuIGEg bWVzc2FnZQ0JIGlzIHNlbnQgdG8gdGhlIHJlY2VpdmVyIGFuZCBubyBtZXRob2QgaXMgZGVm aW5lZCBmb3IgdGhhdCBzZWxlY3RvcikuIg0NCSJUZXN0aW5nOiAoMyBhY3RpdmVQcm9jZXNz KSINDQl8IGV4Y2VwdGlvbiByZXN1bWVWYWx1ZSB8DQkoZXhjZXB0aW9uIDo9IE1lc3NhZ2VO b3RVbmRlcnN0b29kIG5ldykNCQltZXNzYWdlOiBhTWVzc2FnZTsNCQlyZWNlaXZlcjogc2Vs Zi4NCXJlc3VtZVZhbHVlIDo9IGV4Y2VwdGlvbiBzaWduYWwuDQleZXhjZXB0aW9uIHJlYWNo ZWREZWZhdWx0SGFuZGxlcg0JCWlmRmFsc2U6IFtyZXN1bWVWYWx1ZV0JCQ0JCWlmVHJ1ZTog Ww0JCQlbYU1lc3NhZ2Ugc2VudFRvOiBzZWxmXQ0JCQkJb246IE1lc3NhZ2VOb3RVbmRlcnN0 b29kDQkgICAgICAgICAJCWRvOiBbOmV4fCB8IGFyZ3MgfA0JCQkgICAgICAgICAgICAgICBh cmdzIDo9IGV4IG1lc3NhZ2UgYXJndW1lbnRzLg0JCQkgICAgICAgICAgICAgICAoZXggcmVj ZWl2ZXIgPT0gc2VsZg0JCQkgICAgICAgICAgICAgICBhbmQ6IFtleCBtZXNzYWdlIHNlbGVj dG9yID09IGFNZXNzYWdlIHNlbGVjdG9yDQkJCSAgICAgICAgICAgICAgICBhbmQ6IFsoMSB0 bzogYU1lc3NhZ2UgbnVtQXJncykgYWxsU2F0aXNmeTogWzppfCAoYXJncyBhdDogaSkgPT0g KGFNZXNzYWdlIGFyZ3VtZW50QXQ6IGkpXV1dKSBpZkZhbHNlOg0JCQkgICAgICAgICAgICAg ICBbZXggcGFzc10uDQkJCSAgICAgICAgICAgIHNlbGYgZXJyb3I6ICdpbmZpbml0ZSByZWN1 cnNpb24gaW4gZG9lc05vdFVuZGVyc3RhbmQ6J11dDSEgIQ0= --==CelesteAttachment75902==-- --=--
squeak-dev@lists.squeakfoundation.org