[Bug:trunk] add explicit constraints for WONTFIX IO bug

Hi, all

I propose writing two constraints into rdoc of IO:

  • after running IO#dup, both an original IO and generated IO may
    cause “bezarre behavior” except IO#close

  • after running IO#reopen, an original IO that passed to reopen
    may cause “bezarre behavior” except IO#close

In short, it means “IO#dup should not be used” and “IO that was once
passed to IO#reopen should be just closed.”
“bezarre behavior” does not mean undefined behavior (such as SEGV),
but means wrong order of reading and writing, wrong value from
IO#pos, etc. (see Appendix)

These behaviors are caused by bug of core’s wrong buffering handing.
But to fix these, we must change the structure `rb_io_t’, public API
in include/ruby/io.h, resulting in binary incompatibility.
We are currently thinking that these behaviors won’t cause so serious
issue to ought to be fixed with even binary incompatibility.
So I’m proposing writing the above constraints to assert WONTFIX in
the immediate future.

Please say your rebuttal if the above constraints make trouble in
“real world” example. If valid and convincing rebuttal is come, the
issue will be fixed with breaking binary compatibility. If not, the
above constraint statement will be added.

Answers for anticipated rebuttal:

  • how to replace stdout and stderr temporarily to invoke subprocess

Use Kernel#spawn’s option.

  • my existing code will not work

Please fix your code.
Fixing this, all users will be forced to reinstall ext library
(even worse, the code of the library may need to be modified).
We are expecting rebuttals such as examples that cannot be absorbed
by Ruby-level fix or that requires all users to do more cumbersome
things.

If anyone says no objection in three days, I’ll add the constraints.

Thanks,

Appendix: current bezarre behaviors of IO#dup and reopen

foo.txt

A
B
C

example: first-come-first-served gets after IO#reopen (1)

f1 = File.new(“foo.txt”)
f2 = File.new(“foo.txt”)
f1.reopen(f2)
p f1.gets #=> “A\n”
p f2.gets #=> nil (cannot read)

example: first-come-first-served gets after IO#reopen (2)

f1 = File.new(“foo.txt”)
f2 = File.new(“foo.txt”)
f1.reopen(f2)
p f2.gets #=> “A\n”
p f1.gets #=> nil (cannot read)

example: negative value of IO#pos after IO#reopen

f1 = File.new(“foo.txt”)
f2 = File.new(“foo.txt”)
f2.gets
f1.reopen(f2)
f2.gets
f1.rewind
p f2.pos #=> -2

example: wrong value of IO#pos after IO#dup

f1 = File.new(“foo.txt”)
f2 = f1.dup()
p f1.pos #=> 0
p f2.gets #=> “A\n”
p f1.pos #=> 6 (neither 0 or 2)

example: IO#pos with side-effect after IO#reopen

f1 = File.new(“foo.txt”)
f2 = File.new(“foo.txt”)
f2.gets
f1.reopen(f2)
f2.gets
p f1.pos #=> 6
f2.pos
p f1.pos #=> 4 (changed)

example: wrong order of reading after IO#dup

r, w = IO.pipe
Thread.new do
w.print “Foo\nBar”
sleep 1
w.print “Baz\n”
sleep 1
w.print “Qux\n”
end
p r.gets #=> “Foo\n”
r2 = r.dup
p r2.gets #=> “Baz\n” (not “BarBaz\n”)
p r.gets #=> “BarQux\n” (not “Qux\n”)

example: wrong order of writing after IO#dup

f = File.new(“out.txt”, “w”)
f1 = File.new(“foo.txt”)
f2 = File.new(“foo.txt”)
f1.reopen(f)
f2.reopen(f)
f2.puts(“foo”)
f1.puts(“bar”)
#=> resulting “bar\nfoo\n” in out.txt, not “foo\nbar\n”

2010/2/22 Yusuke ENDOH [email protected]:

In short, it means “IO#dup should not be used” and “IO that was once
passed to IO#reopen should be just closed.”
“bezarre behavior” does not mean undefined behavior (such as SEGV),
but means wrong order of reading and writing, wrong value from
IO#pos, etc. (see Appendix)

These behaviors are caused by bug of core’s wrong buffering handing.

I think it is difficult to fix.

The “bizarre” behavior can be occur without IO#dup and IO#reopen.

By IO.new:

% ./ruby -e ’
f1 = File.new(“foo.txt”)
f2 = IO.new(f1.fileno)
p f1.gets
p f2.gets

“A\n”
nil

file descriptors may be shared at outside of ruby:

% ./ruby -e ’
f1 = IO.new(3)
f2 = IO.new(4)
p f1.gets
p f2.gets
’ 3< foo.txt 4<&3
“A\n”
nil

file descriptors may be shared between processes:

% ( ./ruby -e ‘p STDIN.gets’; ./ruby -e ‘p STDIN.gets’ ) < foo.txt
“A\n”
nil

This behavior is inherent result of buffering.

I think we should live with this behavior because
buffering is important for performance.

Hi,

2010/2/24 Tanaka A. [email protected]:

These behaviors are caused by bug of core’s wrong buffering handing.

I think it is difficult to fix.

snip

I think we should live with this behavior because
buffering is important for performance.

OK, I agree.

Then, what should user care to avoid bizarre behavior? IOW, what
behavior
can be guaranteed? How about:

User must not read/write at the time multiple IOs that share the same
file descriptor or duplicated file descriptor.

Anyway, I think we agreed these bizarre behaviors are WONTFIX.
IO#reopen’s spec in rubyspec fails because of this. I’ll soon
quarantine!
the spec.

2010/2/25 Yusuke ENDOH [email protected]:

Then, what should user care to avoid bizarre behavior? IOW, what behavior
can be guaranteed? How about:

User must not read/write at the time multiple IOs that share the same
file descriptor or duplicated file descriptor.

In most case, STDIN/STDOUT/STDERR are inherited from a parent process.
So, they are shared by ruby and the parent.

Your idea forbids read/write for STDIN/STDOUT/STDERR.
It is too restrictive.

I think users should understand following property of I/O operations.

  • read operations may use buffering.
    The operations may read more than minimal.
  • write operations may use buffering.
    The operations may defer.

Issue #2775 has been updated by Yusuke E…

Status changed from Open to Rejected
Target version set to 1.9.x
ruby -v set to ruby 1.9.2dev (2010-03-24 trunk 27033) [i686-linux]

This ticket was rejected. These won’t be fixed.
The current spec is explained in [ruby-core:28335]


Yusuke E. [email protected]

http://redmine.ruby-lang.org/issues/show/2775