#よくわかっていないですが、resetの外側のguardの有無で挙動が変わるのが腑に落ちないのと、リークする場合何がリークしているのでしょう?
#エラーハンドラが増えていっているのなら、なぜ過去のエラーハンドラを保持し続ける必要があるのでしょうか?
#私も確定的なことは言えないんですが、本来、reset-shiftで切り取られる部分継続はreset以前の動的環境を含まなくて良いはずなんですが、Gaucheの継続の持ち方だと以前問題が出て、現在は部分継続が間接的にどこかで動的環境を参照するようになってたと思います。その動的環境の中にエラーハンドラも含まれてるはずです。
#現状、以下のようになっているイメージです。どのバージョンでもリークすると思います。
(let loop () (guard (e (else #f)) (loop)))
#guard が外側ではなく、cont でジャンプするときは 2 個前の reset までを切り取っているので、guard を 1 個飛び出していると思います。
#うーん、shiftのあとはresetの継続に処理が移るからguardを毎回抜けるはずなので、その現状のイメージのコードとは違うように思うのですがどうでしょう?
#10回ループして例外を投げるようにしてみると、その(let loop () ...)のケースでは
##% gosh loop.scm
catch!
catch!
catch!
catch!
catch!
catch!
catch!
catch!
catch!
catch!
*** ERROR: err
Stack Trace:
_______________________________________
0 #:G0
[unknown location]
#となりますが、元のコードの場合では
##% gosh pcdemo5.scm
catch!
*** ERROR: err
Stack Trace:
_______________________________________
0 (error "err")
at "./pcdemo5.scm":23
#となってエラーハンドラが呼ばれる回数が違います
#うーむ、現状の考えとしては、何というか、末尾再帰でないのに、戻ってこないことを前提にしたジャンプでループしているのではないか、ということです。
#例えば、guard がなくても、以下のケースでもリークにはなります。
(let loop () (+ (loop) 1))
#0.9.8 以前では、shift の以下の箇所で、ダイナミックチェーンというか、継続のチェーンを NULL にしていたので、解放されていた可能性があります。
https://github.com/shirok/Gauche/pull/529/files#diff-86406329889f2c13524766839f0a96b3L2503
(オレンジの 2503 行が見えていない場合は、少し上にスクロールしてください)
#ただ、これは、残りの計算の部分に戻って来た場合に SEGV を引き起こしていました。
#自分の理解が不十分な個所があるのですが、"末尾再起でないのに、戻ってこないことを前提にしたジャンプ"というのはどこの部分を指していますか? (reset ...)の部分は内部でshiftが終わった後に値が返されますし、その後に(next)の呼び出しからも返ってくるので、特に問題なさそうに思えます。
##% gosh pcdemo6.scm
#?=(next)
#?=(reset (thunk))
#?- end-of-shift
#?- end-of-wrap
#?=(next)
#?=(reset (thunk))
#?- end-of-shift
#?- end-of-wrap
...
#となり、戻ってきているようです。
#もともとasync/await相当のことを行おうと思って、限定継続を使ってコードを書いていたのですが(yieldの部分でRPCの値が返されていたら、継続を実行していく感じ)、問題があるとすればどう変更すればいいのかと考えています。
#shift/resetをcall/ccで実装する手があるんですが、それでやるとどうなるか興味あります (昔のgauche.partcontが確かそれを使ってたはず)。call/ccで実装した場合、性能は落ちますが動的環境の処理はまともになるはず
#Kahuaにあったpartcontをコピーしてみました
##% gosh pcdemo7.scm
((:total-heap-size 5939200) (:free-bytes 2281472) (:bytes-since-gc 1662720) (:total-bytes 29698896))
((:total-heap-size 5939200) (:free-bytes 2560000) (:bytes-since-gc 1384944) (:total-bytes 56579424))
((:total-heap-size 5939200) (:free-bytes 2875392) (:bytes-since-gc 1065776) (:total-bytes 83461456))
((:total-heap-size 5939200) (:free-bytes 3133440) (:bytes-since-gc 805696) (:total-bytes 110340240))
((:total-heap-size 5939200) (:free-bytes 3362816) (:bytes-since-gc 540368) (:total-bytes 137230016))
((:total-heap-size 5939200) (:free-bytes 3452928) (:bytes-since-gc 286976) (:total-bytes 164114544))
((:total-heap-size 5939200) (:free-bytes 57344) (:bytes-since-gc 3881232) (:total-bytes 191005056))
((:total-heap-size 5939200) (:free-bytes 360448) (:bytes-since-gc 3577024) (:total-bytes 217888272))
((:total-heap-size 5939200) (:free-bytes 688128) (:bytes-since-gc 3255776) (:total-bytes 244765712))
#リークしないようです
#む、これは私の勘違いなのかな? main の最初の (enqueue! queue ... からよみ始めて、
(^[] (guard (reset (while #t (shift cont ...) ) ) ) )
のようにみえたので、cont の実行が、while の中を回り続けるようにみえたのですが。
#んー、contが再開される時にリストアされる動的環境はresetからshiftの間だけのはずで、一旦resetを抜けてguardも抜けていればその分の動的環境はもう捨てられるはずですよね。それが残っちゃってるとか?
#うむ、あまりよく分かっていませんが、ちゃんと cont から戻って来ているのであれば、自分の勘違いだったようですね。
#とすると、やはりプルリク 529 があやしいので、まずは少しずづ revert しながら原因を探してみます。
#Kahua のものは、有名な shift-reset.scm に近いものだと思いますが、何か問題があったような気がします。
#(「常にトップレベルを経由しないといけない」「あちらを立てればこちらが立たず」というメモが残っています。それを改良したバージョンもネットにあったような。。。)
#少し思い出しました。shift-reset.scm は、ここの (leak-test1 (lambda () (reset (shift k k)))) が通らなかった。
http://okmij.org/ftp/continuations/against-callcc.html#memory-leak