On Wed, 2010-12-01, Daniel Shahaf wrote: 
> Julian Foad wrote on Wed, Dec 01, 2010 at 12:32:45 +0000:
> > On Wed, 2010-12-01, stef...@apache.org wrote:
> > > Port (not merge) a fix for a FSFS packing race condition from the
> > > performance branch to /trunk: There is a slight time window
> > > between finding the name of a rev file and actually open it. If
> > > the revision in question gets packed just within this window,
> > > we will find the new (and final) file name with just one retry.
> > > 
> > > * subversion/libsvn_fs_fs/fs_fs.c
> > >   (open_pack_or_rev_file): retry once upon "missing file" error
> > 
> > Hi Stefan.
> > 
> > (1) I think the doc string of svn_fs_fs__path_rev_absolute() should note
> > that the returned path may no longer be valid by the time you come to
> > use it, and the reason for that.  Does my patch below sound right?
> > 
> 
> Well, yes, and Stefan already added such a comment at the definition at
> my suggestion.  But +1 to move that to the declaration.
> 
> > (2) Doesn't the exact same race exist in *all* uses of
> > svn_fs_fs__path_rev_absolute()?  There are five other calls to it,
> 
> As far as I can tell, apart from open_pack_or_rev_file(), all callers
> always[1] run under the write lock.  Since pack operation take the write
> lock too, it's not possible for the result of svn_fs_fs__path_rev_absolute()
> to become outdated for those callers.

OK, that's good.  No change needed then.


> [1] with the exception of set_revision_proplist(), which can be called
> either under the write lock or under write_revision_zero() --- but in
> the latter case it's called before the format file has been created.
> 
> > all of them using the result as a permissions reference to set the
> > file permissions on some new file.  It seems to me that those uses can
> > fail in the same way.
> > 
> > The root problem is the existence of a "get the path of this file" API
> > in the presence of semantics whereby the file might be removed from that
> > path at any time.
> > 
> > Perhaps your fix is the best fix for this caller, but for the other
> > callers I think creating and using a "copy file permissions from
> > rev-file of rev R" API would be a better solution than adding re-tries
> > there.  That API can do the re-try internally.
> > 
> > What do you think?
> > 
> > 
> > Patch for (1) above:
> > [[[
> > Index: subversion/libsvn_fs_fs/fs_fs.h
> > ===================================================================
> > --- subversion/libsvn_fs_fs/fs_fs.h (revision 1040662)
> > +++ subversion/libsvn_fs_fs/fs_fs.h (working copy)
> > @@ -404,13 +404,16 @@ svn_error_t *svn_fs_fs__txn_changes_fetc
> >     PERMS_REFERENCE.  Temporary allocations are from POOL. */
> >  svn_error_t *svn_fs_fs__move_into_place(const char *old_filename,
> >                                          const char *new_filename,
> >                                          const char *perms_reference,
> >                                          apr_pool_t *pool);
> >  
> > -/* Sets *PATH to the path of REV in FS, whether in a pack file or not.
> > +/* Set *PATH to the path of REV in FS, whether in a pack file or not.
> > +   The path is not guaranteed to remain correct after the function returns,
> > +   because it is possible that the revision will become packed just after
> > +   this call, so the caller should re-try once if the path is not found.
> >     Allocate in POOL. */
> 
> Sounds right.
> 
> Bordering on bikeshed, I would suggest some changes:
> 
> * Separate describing what the function does ("Sets *PATH to FOO and
>   allocates in POOL") from describing the conditions the caller should
>   beware of / check for ("sometimes the return value will be out-of-date
>   by the time you receive it").
> 
> * Mention that the non-guarantee is not in effect if the caller has the
>   write lock.

OK, committed in r1041056 with these improvements.

> >  svn_error_t *
> >  svn_fs_fs__path_rev_absolute(const char **path,
> >                               svn_fs_t *fs,
> >                               svn_revnum_t rev,
> >                               apr_pool_t *pool);
> 
> Also, svn_fs_fs__path_rev_absolute() also does, internally, a single
> repeat loop.  Theoretically this isn't needed any more now (since all
> callers either run under the write lock or retry)...

Can you check the attached patch for this, please?

- Julian


Remove the re-try logic from svn_fs_fs__path_rev_absolute().  Since
r1040832, all its callers correctly account for the possibility of an
out-of-date result due to a concurrent packing operation.

The re-try logic was introduced in r875097 and reduced but did not eliminate
the window of opportunity for the caller to use an out-of-date result.

See the email thread <http://svn.haxx.se/dev/archive-2010-12/0019.shtml>,
subject "Re: svn commit: r1040832 - Port a fix for a FSFS packing race".

* subversion/libsvn_fs_fs/fs_fs.c
  (svn_fs_fs__path_rev_absolute): Remove the re-try logic.

* subversion/libsvn_fs_fs/fs_fs.h
  (svn_fs_fs__path_rev_absolute): Update the doc string accordingly.
--This line, and those below, will be ignored--

Index: subversion/libsvn_fs_fs/fs_fs.c
===================================================================
--- subversion/libsvn_fs_fs/fs_fs.c	(revision 1041339)
+++ subversion/libsvn_fs_fs/fs_fs.c	(working copy)
@@ -246,8 +246,6 @@ path_rev(svn_fs_t *fs, svn_revnum_t rev,
                               apr_psprintf(pool, "%ld", rev), NULL);
 }
 
-/* Returns the path of REV in FS, whether in a pack file or not.
-   Allocate in POOL. */
 svn_error_t *
 svn_fs_fs__path_rev_absolute(const char **path,
                              svn_fs_t *fs,
@@ -256,45 +254,16 @@ svn_fs_fs__path_rev_absolute(const char 
 {
   fs_fs_data_t *ffd = fs->fsap_data;
 
-  if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT)
+  if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
+      || ! is_packed_rev(fs, rev))
     {
       *path = path_rev(fs, rev, pool);
-      return SVN_NO_ERROR;
     }
-
-  if (! is_packed_rev(fs, rev))
+  else
     {
-      svn_node_kind_t kind;
-
-      /* Initialize the return variable. */
-      *path = path_rev(fs, rev, pool);
-
-      SVN_ERR(svn_io_check_path(*path, &kind, pool));
-      if (kind == svn_node_file)
-        {
-          /* *path is already set correctly. */
-          return SVN_NO_ERROR;
-        }
-      else
-        {
-          /* Someone must have run 'svnadmin pack' while this fs object
-           * was open. */
-
-          SVN_ERR(update_min_unpacked_rev(fs, pool));
-
-          /* The rev really should be present now. */
-          if (! is_packed_rev(fs, rev))
-            return svn_error_createf(APR_ENOENT, NULL,
-                                     _("Revision file '%s' does not exist, "
-                                       "and r%ld is not packed"),
-                                     svn_dirent_local_style(*path, pool),
-                                     rev);
-          /* Fall through. */
-        }
+      *path = path_rev_packed(fs, rev, "pack", pool);
     }
 
-  *path = path_rev_packed(fs, rev, "pack", pool);
-
   return SVN_NO_ERROR;
 }
 
Index: subversion/libsvn_fs_fs/fs_fs.h
===================================================================
--- subversion/libsvn_fs_fs/fs_fs.h	(revision 1041339)
+++ subversion/libsvn_fs_fs/fs_fs.h	(working copy)
@@ -411,8 +411,10 @@ svn_error_t *svn_fs_fs__move_into_place(
    Allocate *PATH in POOL.
 
    Note: If the caller does not have the write lock on FS, then the path is
-   not guaranteed to remain correct after the function returns, because the
-   revision might become packed just after this call. */
+   not guaranteed to be correct or to remain correct after the function
+   returns, because the revision might become packed before or after this
+   call.  If a file exists at that path, then it is correct; if not, then
+   the caller should call update_min_unpacked_rev() and re-try once. */
 svn_error_t *
 svn_fs_fs__path_rev_absolute(const char **path,
                              svn_fs_t *fs,

Reply via email to