"brixen (Brian Ford)" <brixen / gmail.com> wrote:
> There are serious problems with aliasing the same fd as IO#dup does.
> It is not consistent across platforms. At the very least, the
> implementation leaves huge holes, like allowing one fd to be closed,
> and that IO's #closed? will return true, but the aliasing IO's
> #closed? will return false, and closing it will raise Errno::EBADF.
> That behavior shouldn't be behind a common Ruby method or concept.

On *nix, IO#dup creates a _new_ fd, but _not_ a new file handle (inside
the kernel).   The close() syscall decrements a refcount and only
releases the file handle when there are no other references to it.

I don't know non-*nix platforms, but assuming those platforms don't
support fork(), either, a non-*nix IO#close/#dup which emulates
POSIX semantics could probably work like this:

  FD_REFCOUNT = [] # fileno => refcount
  FD_REFCOUNT_LOCK = Mutex.new

  def close
    raise IOError if @closed
    @closed = true
    FD_REFCOUNT_LOCK.synchronize do
      FD_REFCOUNT[fileno] -= 1
      refcount = FD_REFCOUNT[fileno]

      # call real close() only when refcount is zero
      sysclose(fileno) if refcount == 0
    end
  end

  def dup
    raise IOError if @closed
    FD_REFCOUNT_LOCK.synchronize do
      FD_REFCOUNT[fileno] += 1
    end
    super
  end

This is basically what happens inside the kernel anyways (except the
kernel is multi-process-aware).  For platforms without fork(), you
should be able to emulate POSIX dup()/close() semantics using the
example above as a starting point.

> Further, if StringIO is supposed to mimic all this, there are numerous
> bugs because StringIO instances that are aliases via #dup do report
> the same value for eg closed?.

Yeah, StringIO looks buggy here (but I don't expect StringIO to ever
perfectly match IO).