implementing the feature of commit one transaction per http request in
a Seaside application exposed a problem regarding Dolphin
Continuations.
Crhis Uppal in Nov 10, 2005 exposed this piece of code:
c := nil.
block := [Continuation currentDo:[:cc | c := cc. c value:
'Aha!!']].
sem := Semaphore new.
value := nil.
[
[value := block value]
ensure: [sem signal.
Transcript show: 'ooerr...'; cr.]
] fork.
sem wait.
It is very interesting because it reveals Dolphin printing in
transcript 3 times "ooer..."
Caution: with Continuations I learned one rule: don't try to think
them, experiment them.
Use #halt and debugger because that way is healthier for the brain ;)
I've then compared VisualWorks and Squeak behavior with this very same
script. They print in transcript just once. Always.
I'm not sure if asking why is a good idea because I think this
particular point leaves open if this is about a bug or a feature, so
I'll limit myself to ask if someone knows a way to achieve the same
behavior as Squeak or VisualWorks on a case like this.
To be able to execute the #ensure: block only once is mandatory for
Seaside applications which want to use the good practice of wrapping
the http request with a transaction. When you get more than one
execution of that block you get weird things like mutex protected
blocks look like getting violated or transactions trying to get
committed twice.
I've tried the Crhis hack in:
Continuation>>continue: aProcess with: anObject
Processor activeProcess exceptionEnvironment: nil.
aProcess continueWith: anObject
which was discouraged with reservations by Blair. This hack only
compensates 1 of the 3 evaluations.
I ask if someone knows how to compensate the other so Squeak/
VisualWorks behavior gets emulated.
Better will be to understand its origin so ideas of a fix arise.
Unless, of course, this behavior gets defined as bug and a proper
patch is made about Continuations.
cheers,
Sebstian
Note: I've tested with the semaphore in a temp like here and also as
instVar. Same results.
TEST for Squeak and VisualWorks both green
ContinuationTest>>testBlockEnsure
| action sem |
tmp2 := 0.
action := [self callcc: [:cc |
tmp := cc.
true]].
sem := Semaphore new.
[[action value] ensure:[
sem signal.
tmp2 := tmp2 +1.
self deny: tmp2 > 1]] fork.
sem wait.
tmp := nil.
TEST for Dolphin will also show green but then shows the assertion
failed
ContinuationTest>>testBlockEnsure
| action sem |
tmp2 := 0.
action := [[:cc |
tmp := cc.
true] callCC].
sem := Semaphore new.
[[action value] ensure:[
tmp2 := tmp2 +1.
self deny: tmp2 > 1.
sem signal]] fork.
sem wait.
tmp := nil.
Due to continuations the #deny: has to be inside the ensured block
otherwise it will assert but the ensured block will actually be
executed more than once (as you can check by printing in Transcript
like in the Chris script).
cheers,
Sebastian
PD1: for Seaside this has dramatic consequences because code of the
app which uses #ensure: (as usual meant to run once) will also be ran
more than once.
PD2: maybe we can say the continuation to finish the process in a
"special" way to compensate this? any pointer about how?
Here was the test case that I used:
Object>>runCC
| count |
count := Array with: 0.
[[:c | ] callCC] ensure: [count at: 1 put: count first + 1].
^count
with a test script of:
| count |
count := nil runCC.
MemoryManager current collectGarbage; administerLastRites.
Transcript print: count; cr; flush
Without the #beUnfinalizable call, it prints an array with 2. However,
with the call, it prints an array with 1.
John Brant
That made the test go green (and remain green ;)
Only experience will say if undesirable secondary consequences arises
from that or not. I'm optimistic about this one and for now that was
definitively most helpful.
I'm now solving other subtleties which emerged once that is working as
expected. They are regarding to Seaside itself and to achieve my
original goal.
Thank you very very much Jhon,
Sebastian
an update: Jhon's patch made that test go green but is not enough.
I was moving forward in my goal and found some problems again related
to #ensure: and #ifCurtailed:
So I've checked again the Chris test:
c := nil.
block := [[:cc | c := cc. c value: 'Aha!!'] callCC].
sem := Semaphore new.
value := nil.
[
[value := block value]
ensure: [Transcript display: 'ooerr...'; cr. sem signal]
] fork.
sem wait.
And even with this patch wont get better.
The test which fails assertion is:
testCallContinuationInsideAndBlockEnsure
| action sem |
tmp2 := 0.
action := [[:cc |
tmp := cc.
tmp value: 'Aha!'] callCC].
sem := Semaphore new.
[[action value] ensure:[
Transcript cr; show: 'opa'.
tmp2 := tmp2 +1.
self deny: tmp2 > 1.
sem signal]] fork.
sem wait.
tmp := nil.
I've checked VisualWorks and Squeak they both do green on that.
All Seasiders will appreciate a fix on this. Any clue?
cheers,
Sebastian
> I've checked VisualWorks and Squeak they both do green on that.
>
> All Seasiders will appreciate a fix on this. Any clue?
It appears that VW doesn't run the unwind blocks when it creates a
continuation and terminates the current process (see
Continuation>>terminate: in VW). However, in Dolphin, they are just
using the standard #terminate method (Process>>continueWith:) and that
runs the unwind blocks. I believe you could change the "Processor
terminateActive" to be "Processor activeProcess kill" in the
Process>>continueWith: method.
John Brant
Exactly what I though! I was about to propose a special termination
for continuations. Let me check how your patch performs...
Sebastian
It is working. I'm more comfortable with the idea of killing the
process instead of setting nil in the environment.
I'll be using both of your patches.
Thank you so much John!
Sebastian
PD: Andy/Blair do you see any problem in integrating John patches?
Could you (or someone) provide a summary of the Continuation patches (a
fileIn perhaps) and some SUnit tests that demonstrate them working. If
so, then I'm happy to integrate the changes into the next image.
Best regards
Andy Bower