[Ecls-list] open :supersede and rename-file advise

Richard M Kreuter kreuter at progn.net
Thu Nov 29 07:25:09 UTC 2007


Hi,

In my previous message, I forgot to mention an important issue in
implementing "lazy superseding" on Unix, and perhaps elsewhere.  On
Unix, open files can be shared among multiple processes.  This creates a
problem for a naive implementation of lazy superseding: the process
opens a uniquely named "write-beside" file, records that the
write-beside file is to be renamed when the stream is closed, then calls
fork(3).  Now there are two processes that will attempt to rename the
write-beside file a close-time, and since one process will necessarily
close its stream before the other, one of the two will find that its
write-beside file doesn't exist.

I think it important for an implementation that tries to do lazy
superseding to take some deliberate position about this, though I don't
know quite what the position should be.  A minimum requirement, I think,
is that it is not acceptable to unconditionally ignore a failure to
rename the write-beside file at close-time.  That is, you don't want to
to ever have the process that wants to close the stream lose the
write-beside file or leave it with some random name without signalling
an error to the user.

Here are some options I can think of, given such a requirement:

(1) The simplest thing might be to let the first process that closes the
    file do the superseding, and have all subsequent close attempts skip
    the rename if the write-beside file has already been renamed.
    Here's a pseudo-lisp routine for doing this:

    (let ((descriptor-status (fstat (file-descriptor stream)))
          (file-status (stat (pathname stream))))
      (if (zerop (st-nlink descriptor-stat))
          (restart-case (error "the write-beside file got unlinked?")
             #| various restarts |#)
          (if (= (st-ino file-status) (st-ino descriptor-status))
              ;; somebody already put the write-beside file in place
	      nil
              (rename (truename stream) (pathname stream)))))

    (I read the dictionary entries for TRUENAME and PATHNAME to mean
    that the name of the write-beside file should be returned, while
    PATHNAME returns the file name used to open the stream, i.e., the
    name of the destination file.)

    Unfortunately, this approach breaks the intended feature that the
    write-beside file is only renamed after the last write to that file.

(2) The implementation could try to do the superseding only when the
    last process closes the file.  AFAICT, doing this in general
    requires subsuming the functionality of lsof, which is probably far
    more work than is practical.

(3) The implementation could wimp out and say that it's the user's
    responsibility to close the stream in a way that inhibits attempt to
    supersede, for example by using the ABORT argument to CLOSE.  ISTM
    that this option forces a tacky sort of manual resource management,
    where the user can only get things right by knowing the entire state
    of the universe.

(4) The implementation could prohibit sharing lazily-superseded files
    among processes.  For example, the implementation could wrap fork(3)
    in a routine that closes all lazily-superseded file streams in the
    child without superseding, and so only the process that opened the
    file would perform the superseding.  (It's not uncommon for some
    system calls to be unsafe for direct calling on some implementations
    (e.g., read(2) and write(2) on buffered file stream descriptors,
    installing handlers for some signals), so fork(2) could be one of
    those on such an implementation.)  This would preserve the intended
    lazy-superseding semantics and be invisible to the user, but would
    make forking more costly.

(5) A hybrid approach might be to have the implementation record the pid
    of the opening process in a mutable slot in the stream, have the
    CLOSE compare the recorded pid to the process's pid, and only
    supersede if they are equal.  This way, the default behavior would
    be for the opening process to do the superseding, but the user could
    override that.  This is still probably somewhat error-prone, since
    this would let the user set two processes to supersede, and so the
    basic problem will remain.

Maybe there are more options available, too.  I think there's some
murkiness or hair involved in any case.  These problems might be
surmountable, though I think some careful consideration is called for in
any case.

It gets weirder in more obscure cases of file sharing, e.g., passing
descriptors over Unix domain sockets.  I haven't any intuitions right
now about these cases.

(Note that the LispM never needed to deal with this issue, since it only
ever used the Unix file system remotely, and so there were no file
sharing issues.  Additionally, because the standard Unix API doesn't do
any lazy superseding, the programmer has to do all this work explicitly,
and so one doesn't run into accidental conflicts here.  By contrast some
libraries for communicating with non-embedded databases have exactly
these issues when they try to implement automatic rollback behavior in a
finalizer; and maybe there are sockets libraries that call shutdown(2)
in a finalizer.  So maybe there are some worthwhile ideas out there that
can be adapted for lazy file superseding.)

--
RmK




More information about the ecl-devel mailing list