Bug #14472
closed`File::open` closes the file too early when used with callcc
Description
First of all, I know callcc
is deprecated feature and I'm not in trouble with this bug.
I was just curious and happened to find this bug.
Bug Description¶
This code throws an IOError, but expected to exit normally:
require 'continuation'
f1 = callcc {|k| File::open("test1", 'w') {|f| k.(f)}}
f1.write("hello")
$ ruby openfile_cont.rb
/usr/lib/x86_64-linux-gnu/ruby/2.3.0/continuation.so: warning: callcc is obsolete; use Fiber instead
openfile_cont.rb:14:in `write': closed stream (IOError)
from openfile_cont.rb:14:in `<main>'
I think this is a bug because the code above must be the same as code below, which works fine:
File::open("test1", 'w') {|f1| f1.write("hello")}
In fact, an equivalent scheme code works fine:
(let* ((f1 (call/cc (lambda (k) (call-with-output-file "test1" k)))))
(display "hello" f1))
$ gosh openfile_cont.scm
$ cat test1
hello
Importance¶
Again, I'm not in trouble with this bug.
The bugging code is useful to rewrite nested open
blocks to flat style like this:
File::open("test1", 'w') do |f1|
File::open("test2", 'w') do |f2|
f1.write("hello")
f2.write("hello")
end
end
f1 = callcc {|k| File::open("test1", 'w') {|f| k.(f)}}
f2 = callcc {|k| File::open("test2", 'w') {|f| k.(f)}}
f1.write("hello")
f2.write("hello")
Or, even code that cannot be written with nested open
s can be written with callcc:
["test1", "test2", "test3"].map{|path| callcc {|k| File::open(path, 'w') {|f| k.(f)}}}.each do |f|
f.write("hello")
end
Again, equivalent scheme code works fine:
(let ((paths '("test1" "test2" "test3")))
(let ((ports (map (lambda (path) (call/cc (lambda (k) (call-with-output-file path k)))) paths)))
(dolist (port ports)
(display "hello" port))))
Updated by nobu (Nobuyoshi Nakada) almost 7 years ago
- Related to Bug #9105: callcc による不整合(例:Hash) added
Updated by nobu (Nobuyoshi Nakada) almost 7 years ago
- Status changed from Open to Rejected
blackenedgold (Sunrin SHIMURA) wrote:
This code throws an IOError, but expected to exit normally:
callcc
rollbacks ensure
s.
The bugging code is useful to rewrite nested
open
blocks to flat style like this:f1 = callcc {|k| File::open("test1", 'w') {|f| k.(f)}} f2 = callcc {|k| File::open("test2", 'w') {|f| k.(f)}} f1.write("hello") f2.write("hello")
You can do it by File.open
without a block.
If callcc
weren't fire ensure
s, it's unpredictable when f1
and f2
will get closed.
It's same as w/o-block form open
.
Updated by blackenedgold (Sunrin SHIMURA) almost 7 years ago
You can do it by
File.open
without a block.
I know that. Again, I'm not in trouble.
callcc
rollbacks ensures.
I understand it isn't a bug, but an expected behaviour.
Then the question is why this behaviour?
If
callcc
weren't fire ensures, it's unpredictable whenf1
andf2
will get closed.
Why? Is firing when control reached to the end not sufficient?
In scheme, closing port before reaching the end is explicitly forbidden
In #9105 case, error was occurring because control reached the end twice and ensure was fired twice. I think it's natural
In this case, error is occurring because ensure is fired even though control doesn't reach the end. I think it's unnatural.
Updated by blackenedgold (Sunrin SHIMURA) almost 7 years ago
I used raw continuations for the simplicity of explanation but I found it was a bit confusing (and I was also confused).
... code above must be the same as code below, which works fine:
It was wrong. and
You can do it by
File.open
without a block.
is absolutely correct.
I wrote wrong code. I intended to write code with delimited continuations like below.
require 'continuation'
def shift
callcc {|c1| $ctn.(yield(proc {|v| callcc {|c2| $ctn = c2; c1.(v) } })) }
end
def reset
callcc {|c| $ctn = c; v = yield; $ctn.(v) }
end
reset do
f1 = shift {|k| File::open("test1", 'w') {|f| k.(f)}}
f1.write("hello")
end
This is exactly the same as
File::open("test1", 'w') {|f1| f1.write("hello")}
including file is closed after the write is done.