Project

General

Profile

Bug #14472

`File::open` closes the file too early when used with callcc

Added by blackenedgold (Sunrin SHIMURA) 9 months ago. Updated 9 months ago.

Status:
Rejected
Priority:
Normal
Assignee:
-
Target version:
-
ruby -v:
ruby 2.3.3p222 (2016-11-21) [x86_64-linux-gnu]
[ruby-core:85527]

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 opens 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))))

Related issues

Related to Ruby trunk - Bug #9105: callcc による不整合(例:Hash)Closed2013-11-13

History

#1 Updated by nobu (Nobuyoshi Nakada) 9 months ago

  • Related to Bug #9105: callcc による不整合(例:Hash) added

#2 [ruby-core:85537] Updated by nobu (Nobuyoshi Nakada) 9 months ago

  • Status changed from Open to Rejected

blackenedgold (Sunrin SHIMURA) wrote:

This code throws an IOError, but expected to exit normally:

callcc rollbacks ensures.

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 ensures, it's unpredictable when f1 and f2 will get closed.
It's same as w/o-block form open.

#3 [ruby-core:85539] Updated by blackenedgold (Sunrin SHIMURA) 9 months 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 when f1 and f2 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.

#4 [ruby-core:85540] Updated by blackenedgold (Sunrin SHIMURA) 9 months 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.

Also available in: Atom PDF