You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2016/04/29 20:38:56 UTC

svn commit: r1741682 [12/26] - in /subversion/branches/authzperf: ./ build/ build/ac-macros/ build/generator/ contrib/server-side/svncutter/ notes/ notes/api-errata/1.9/ notes/move-tracking/ subversion/ subversion/bindings/ctypes-python/ subversion/bin...

Modified: subversion/branches/authzperf/subversion/libsvn_fs_x/revprops.h
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_fs_x/revprops.h?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_fs_x/revprops.h (original)
+++ subversion/branches/authzperf/subversion/libsvn_fs_x/revprops.h Fri Apr 29 18:38:53 2016
@@ -25,6 +25,8 @@
 
 #include "svn_fs.h"
 
+#include "batch_fsync.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif /* __cplusplus */
@@ -39,8 +41,26 @@ svn_error_t *
 svn_fs_x__reset_revprop_generation_file(svn_fs_t *fs,
                                         apr_pool_t *scratch_pool);
 
+/* Invalidate the cached revprop generation value in FS->FSAP_DATA.
+ * This enforces a re-read upon the next revprop read. */
+void
+svn_fs_x__invalidate_revprop_generation(svn_fs_t *fs);
+
+/* Utility function serializing PROPLIST into FILE and adding the checksum.
+ * Use SCRATCH_POOL for temporary allocations.
+ *
+ * Call this only when creating initial revprop file contents.
+ * For modifications use svn_fs_x__set_revision_proplist.
+ */
+svn_error_t *
+svn_fs_x__write_non_packed_revprops(apr_file_t *file,
+                                    apr_hash_t *proplist,
+                                    apr_pool_t *scratch_pool);
+
 /* Read the revprops for revision REV in FS and return them in *PROPLIST_P.
  * If BYPASS_CACHE is set, don't consult the disks but always read from disk.
+ * If REFRESH is set, update the revprop generation info; otherwise access
+ * potentially outdated cache data directly.
  *
  * Allocate the *PROPLIST_P in RESULT_POOL and use SCRATCH_POOL for temporary
  * allocations.
@@ -50,6 +70,7 @@ svn_fs_x__get_revision_proplist(apr_hash
                                 svn_fs_t *fs,
                                 svn_revnum_t rev,
                                 svn_boolean_t bypass_cache,
+                                svn_boolean_t refresh,
                                 apr_pool_t *result_pool,
                                 apr_pool_t *scratch_pool);
 
@@ -77,7 +98,7 @@ svn_fs_x__packed_revprop_available(svn_b
 
 /* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
  * revprop files in it, create a packed shared at PACK_FILE_DIR in
- * filesystem FS.
+ * filesystem FS.  Schedule necessary fsync calls in BATCH.
  *
  * COMPRESSION_LEVEL defines how well the resulting pack file shall be
  * compressed or whether is shall be compressed at all.  Individual pack
@@ -93,8 +114,9 @@ svn_fs_x__pack_revprops_shard(svn_fs_t *
                               const char *shard_path,
                               apr_int64_t shard,
                               int max_files_per_dir,
-                              apr_off_t max_pack_size,
+                              apr_int64_t max_pack_size,
                               int compression_level,
+                              svn_fs_x__batch_fsync_t *batch,
                               svn_cancel_func_t cancel_func,
                               void *cancel_baton,
                               apr_pool_t *scratch_pool);

Modified: subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.c Fri Apr 29 18:38:53 2016
@@ -190,6 +190,10 @@ typedef struct dir_data_t
    * (it's int because the directory is an APR array) */
   int count;
 
+  /** Current length of the in-txn in-disk representation of the directory.
+   * SVN_INVALID_FILESIZE if unknown (i.e. committed data). */
+  svn_filesize_t txn_filesize;
+
   /* number of unused dir entry buckets in the index */
   apr_size_t over_provision;
 
@@ -234,18 +238,19 @@ serialize_dir_entry(svn_temp_serializer_
   svn_temp_serializer__pop(context);
 }
 
-/* Utility function to serialize the ENTRIES into a new serialization
+/* Utility function to serialize the DIR into a new serialization
  * context to be returned.
  *
  * Temporary allocation will be made form SCRATCH_POOL.
  */
 static svn_temp_serializer__context_t *
-serialize_dir(apr_array_header_t *entries,
+serialize_dir(svn_fs_x__dir_data_t *dir,
               apr_pool_t *scratch_pool)
 {
   dir_data_t dir_data;
   int i = 0;
   svn_temp_serializer__context_t *context;
+  apr_array_header_t *entries = dir->entries;
 
   /* calculate sizes */
   int count = entries->nelts;
@@ -254,8 +259,12 @@ serialize_dir(apr_array_header_t *entrie
                            * sizeof(svn_fs_x__dirent_t*);
   apr_size_t lengths_len = (count + over_provision) * sizeof(apr_uint32_t);
 
+  /* Estimate the size of a directory entry + its name. */
+  enum { ENTRY_SIZE = sizeof(svn_fs_x__dirent_t) + 32 };
+
   /* copy the hash entries to an auxiliary struct of known layout */
   dir_data.count = count;
+  dir_data.txn_filesize = dir->txn_filesize;
   dir_data.over_provision = over_provision;
   dir_data.operations = 0;
   dir_data.entries = apr_palloc(scratch_pool, entries_len);
@@ -268,7 +277,8 @@ serialize_dir(apr_array_header_t *entrie
    * estimate for the size of the buffer that we will need. */
   context = svn_temp_serializer__init(&dir_data,
                                       sizeof(dir_data),
-                                      50 + count * 200 + entries_len,
+                                      50 + count * ENTRY_SIZE
+                                         + entries_len + lengths_len,
                                       scratch_pool);
 
   /* serialize entries references */
@@ -292,26 +302,32 @@ serialize_dir(apr_array_header_t *entrie
   return context;
 }
 
-/* Utility function to reconstruct a dir entries array from serialized data
+/* Utility function to reconstruct a dir entries struct from serialized data
  * in BUFFER and DIR_DATA. Allocation will be made form RESULT_POOL.
  */
-static apr_array_header_t *
+static svn_fs_x__dir_data_t *
 deserialize_dir(void *buffer,
                 dir_data_t *dir_data,
                 apr_pool_t *result_pool)
 {
-  apr_array_header_t *result = apr_array_make(result_pool, dir_data->count,
-                                              sizeof(svn_fs_x__dirent_t *));
+  svn_fs_x__dir_data_t *result;
   apr_size_t i;
   apr_size_t count;
   svn_fs_x__dirent_t *entry;
   svn_fs_x__dirent_t **entries;
 
+  /* Construct empty directory object. */
+  result = apr_pcalloc(result_pool, sizeof(*result));
+  result->entries
+    = apr_array_make(result_pool, dir_data->count,
+                     sizeof(svn_fs_x__dirent_t *));
+  result->txn_filesize = dir_data->txn_filesize;
+
   /* resolve the reference to the entries array */
   svn_temp_deserializer__resolve(buffer, (void **)&dir_data->entries);
   entries = dir_data->entries;
 
-  /* fixup the references within each entry and add it to the hash */
+  /* fixup the references within each entry and add it to the RESULT */
   for (i = 0, count = dir_data->count; i < count; ++i)
     {
       svn_temp_deserializer__resolve(entries, (void **)&entries[i]);
@@ -321,7 +337,7 @@ deserialize_dir(void *buffer,
       svn_temp_deserializer__resolve(entry, (void **)&entry->name);
 
       /* add the entry to the hash */
-      APR_ARRAY_PUSH(result, svn_fs_x__dirent_t *) = entry;
+      APR_ARRAY_PUSH(result->entries, svn_fs_x__dirent_t *) = entry;
     }
 
   /* return the now complete hash */
@@ -553,7 +569,7 @@ svn_fs_x__serialize_properties(void **da
   /* create our auxiliary data structure */
   properties.count = apr_hash_count(hash);
   properties.keys = apr_palloc(pool, sizeof(const char*) * (properties.count + 1));
-  properties.values = apr_palloc(pool, sizeof(const char*) * properties.count);
+  properties.values = apr_palloc(pool, sizeof(const svn_string_t *) * properties.count);
 
   /* populate it with the hash entries */
   for (hi = apr_hash_first(pool, hash), i=0; hi; hi = apr_hash_next(hi), ++i)
@@ -663,16 +679,18 @@ svn_fs_x__deserialize_node_revision(void
 }
 
 /* Utility function that returns the directory serialized inside CONTEXT
- * to DATA and DATA_LEN. */
+ * to DATA and DATA_LEN.  If OVERPROVISION is set, allocate some extra
+ * room for future in-place changes by svn_fs_fs__replace_dir_entry. */
 static svn_error_t *
 return_serialized_dir_context(svn_temp_serializer__context_t *context,
                               void **data,
-                              apr_size_t *data_len)
+                              apr_size_t *data_len,
+                              svn_boolean_t overprovision)
 {
   svn_stringbuf_t *serialized = svn_temp_serializer__get(context);
 
   *data = serialized->data;
-  *data_len = serialized->blocksize;
+  *data_len = overprovision ? serialized->blocksize : serialized->len;
   ((dir_data_t *)serialized->data)->len = serialized->len;
 
   return SVN_NO_ERROR;
@@ -684,13 +702,14 @@ svn_fs_x__serialize_dir_entries(void **d
                                 void *in,
                                 apr_pool_t *pool)
 {
-  apr_array_header_t *dir = in;
+  svn_fs_x__dir_data_t *dir = in;
 
   /* serialize the dir content into a new serialization context
    * and return the serialized data */
   return return_serialized_dir_context(serialize_dir(dir, pool),
                                        data,
-                                       data_len);
+                                       data_len,
+                                       FALSE);
 }
 
 svn_error_t *
@@ -723,6 +742,20 @@ svn_fs_x__get_sharded_offset(void **out,
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_fs_x__extract_dir_filesize(void **out,
+                               const void *data,
+                               apr_size_t data_len,
+                               void *baton,
+                               apr_pool_t *pool)
+{
+  const dir_data_t *dir_data = data;
+
+  *(svn_filesize_t *)out = dir_data->txn_filesize;
+
+  return SVN_NO_ERROR;
+}
+
 /* Utility function that returns the lowest index of the first entry in
  * *ENTRIES that points to a dir entry with a name equal or larger than NAME.
  * If an exact match has been found, *FOUND will be set to TRUE. COUNT is
@@ -806,6 +839,10 @@ svn_fs_x__extract_dir_entry(void **out,
   const apr_uint32_t *lengths =
     svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->lengths);
 
+  /* Before we return, make sure we tell the caller this data is even still
+     relevant. */
+  b->out_of_date = dir_data->txn_filesize != b->txn_filesize;
+
   /* Special case: Early out for empty directories.
      That simplifies tests further down the road. */
   *out = NULL;
@@ -832,8 +869,9 @@ svn_fs_x__extract_dir_entry(void **out,
   if (found)
     b->hint = pos;
 
-  /* de-serialize that entry or return NULL, if no match has been found */
-  if (found)
+  /* de-serialize that entry or return NULL, if no match has been found.
+   * Be sure to check that the directory contents is still up-to-date. */
+  if (found && !b->out_of_date)
     {
       const svn_fs_x__dirent_t *source =
           svn_temp_deserializer__ptr(entries, (const void *const *)&entries[pos]);
@@ -846,8 +884,7 @@ svn_fs_x__extract_dir_entry(void **out,
       apr_size_t size = lengths[pos];
 
       /* copy & deserialize the entry */
-      svn_fs_x__dirent_t *new_entry = apr_palloc(pool, size);
-      memcpy(new_entry, source, size);
+      svn_fs_x__dirent_t *new_entry = apr_pmemdup(pool, source, size);
 
       svn_temp_deserializer__resolve(new_entry, (void **)&new_entry->name);
       *(svn_fs_x__dirent_t **)out = new_entry;
@@ -867,32 +904,34 @@ slowly_replace_dir_entry(void **data,
 {
   replace_baton_t *replace_baton = (replace_baton_t *)baton;
   dir_data_t *dir_data = (dir_data_t *)*data;
-  apr_array_header_t *dir;
+  svn_fs_x__dir_data_t *dir;
   int idx = -1;
   svn_fs_x__dirent_t *entry;
+  apr_array_header_t *entries;
 
   SVN_ERR(svn_fs_x__deserialize_dir_entries((void **)&dir,
                                             *data,
                                             dir_data->len,
                                             pool));
 
-  entry = svn_fs_x__find_dir_entry(dir, replace_baton->name, &idx);
+  entries = dir->entries;
+  entry = svn_fs_x__find_dir_entry(entries, replace_baton->name, &idx);
 
   /* Replacement or removal? */
   if (replace_baton->new_entry)
     {
       /* Replace ENTRY with / insert the NEW_ENTRY */
       if (entry)
-        APR_ARRAY_IDX(dir, idx, svn_fs_x__dirent_t *)
+        APR_ARRAY_IDX(entries, idx, svn_fs_x__dirent_t *)
           = replace_baton->new_entry;
       else
-        svn_sort__array_insert(dir, &replace_baton->new_entry, idx);
+        svn_sort__array_insert(entries, &replace_baton->new_entry, idx);
     }
   else
     {
       /* Remove the old ENTRY. */
       if (entry)
-        svn_sort__array_delete(dir, idx, 1);
+        svn_sort__array_delete(entries, idx, 1);
     }
 
   return svn_fs_x__serialize_dir_entries(data, data_len, dir, pool);
@@ -914,6 +953,12 @@ svn_fs_x__replace_dir_entry(void **data,
 
   svn_temp_serializer__context_t *context;
 
+  /* update the cached file length info.
+   * Because we are writing to the cache, it is fair to assume that the
+   * caller made sure that the current contents is consistent with the
+   * previous state of the directory file. */
+  dir_data->txn_filesize = replace_baton->txn_filesize;
+
   /* after quite a number of operations, let's re-pack everything.
    * This is to limit the number of wasted space as we cannot overwrite
    * existing data but must always append. */
@@ -986,9 +1031,7 @@ svn_fs_x__replace_dir_entry(void **data,
   serialize_dir_entry(context, &entries[pos], &length);
 
   /* return the updated serialized data */
-  SVN_ERR (return_serialized_dir_context(context,
-                                         data,
-                                         data_len));
+  SVN_ERR(return_serialized_dir_context(context, data, data_len, TRUE));
 
   /* since the previous call may have re-allocated the buffer, the lengths
    * pointer may no longer point to the entry in that buffer. Therefore,
@@ -1003,6 +1046,18 @@ svn_fs_x__replace_dir_entry(void **data,
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_fs_x__reset_txn_filesize(void **data,
+                             apr_size_t *data_len,
+                             void *baton,
+                             apr_pool_t *pool)
+{
+  dir_data_t *dir_data = (dir_data_t *)*data;
+  dir_data->txn_filesize = SVN_INVALID_FILESIZE;
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t  *
 svn_fs_x__serialize_rep_header(void **data,
                                apr_size_t *data_len,
@@ -1108,7 +1163,7 @@ svn_fs_x__serialize_changes(void **data,
 
   svn_temp_serializer__push(context,
                             (const void * const *)&changes.changes,
-                            changes.count * sizeof(**changes.changes));
+                            changes.count * sizeof(*changes.changes));
 
   for (i = 0; i < changes.count; ++i)
     serialize_change(context, &changes.changes[i]);
@@ -1155,3 +1210,57 @@ svn_fs_x__deserialize_changes(void **out
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_fs_x__read_changes_block(void **out,
+                             const void *data,
+                             apr_size_t data_len,
+                             void *baton,
+                             apr_pool_t *pool)
+{
+  int first;
+  int last;
+  int i;
+  enum { BLOCK_SIZE = 100 };
+  apr_array_header_t *array;
+
+  svn_fs_x__read_changes_block_baton_t *b = baton;
+  changes_data_t changes = *(const changes_data_t *)data;
+
+  /* Restrict range to the block requested by the BATON.
+   * Tell the caller whether we reached the end of the list. */
+  first = MIN(b->start, changes.count);
+  last = MIN(first + BLOCK_SIZE, changes.count);
+  *b->eol = last == changes.count;
+
+  /* de-serialize our auxiliary data structure */
+  svn_temp_deserializer__resolve(data, (void**)&changes.changes);
+
+  /* de-serialize each entry and add it to the array */
+  array = apr_array_make(pool, last - first, sizeof(svn_fs_x__change_t *));
+  for (i = first; i < last; ++i)
+    {
+      svn_fs_x__change_t *change;
+
+      /* Get a pointer to the in-cache change struct at offset I. */
+      svn_fs_x__change_t *cached_change = changes.changes[i];
+      svn_temp_deserializer__resolve(changes.changes,
+                                     (void**)&cached_change);
+
+      /* Duplicate that struct into the result POOL. */
+      change = apr_pmemdup(pool, cached_change, sizeof(*change));
+
+      /* fix-up of pointers within the struct */
+      svn_temp_deserializer__resolve(cached_change,
+                                     (void **)&change->path.data);
+      svn_temp_deserializer__resolve(cached_change,
+                                     (void **)&change->copyfrom_path);
+
+      /* Add the change to result. */
+      APR_ARRAY_PUSH(array, svn_fs_x__change_t *) = change;
+    }
+
+  /* done */
+  *out = array;
+
+  return SVN_NO_ERROR;
+}

Modified: subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.h
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.h?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.h (original)
+++ subversion/branches/authzperf/subversion/libsvn_fs_x/temp_serializer.h Fri Apr 29 18:38:53 2016
@@ -129,7 +129,7 @@ svn_fs_x__deserialize_node_revision(void
                                     apr_pool_t *result_pool);
 
 /**
- * Implements #svn_cache__serialize_func_t for a directory contents array
+ * Implements #svn_cache__serialize_func_t for a #svn_fs_x__dir_data_t
  */
 svn_error_t *
 svn_fs_x__serialize_dir_entries(void **data,
@@ -138,7 +138,7 @@ svn_fs_x__serialize_dir_entries(void **d
                                 apr_pool_t *pool);
 
 /**
- * Implements #svn_cache__deserialize_func_t for a directory contents array
+ * Implements #svn_cache__deserialize_func_t for a #svn_fs_x__dir_data_t
  */
 svn_error_t *
 svn_fs_x__deserialize_dir_entries(void **out,
@@ -158,6 +158,18 @@ svn_fs_x__get_sharded_offset(void **out,
                              apr_pool_t *pool);
 
 /**
+ * Implements #svn_cache__partial_getter_func_t.
+ * Set (svn_filesize_t) @a *out to the filesize info stored with the
+ * serialized directory in @a data of @a data_len.  @a baton is unused.
+ */
+svn_error_t *
+svn_fs_x__extract_dir_filesize(void **out,
+                               const void *data,
+                               apr_size_t data_len,
+                               void *baton,
+                               apr_pool_t *pool);
+
+/**
  * Baton type to be used with svn_fs_x__extract_dir_entry. */
 typedef struct svn_fs_x__ede_baton_t
 {
@@ -166,12 +178,24 @@ typedef struct svn_fs_x__ede_baton_t
 
   /* Lookup hint [in / out] */
   apr_size_t hint;
+
+  /** Current length of the in-txn in-disk representation of the directory.
+   * SVN_INVALID_FILESIZE if unknown. */
+  svn_filesize_t txn_filesize;
+
+  /** Will be set by the callback.  If FALSE, the cached data is out of date.
+   * We need this indicator because the svn_cache__t interface will always
+   * report the lookup as a success (FOUND==TRUE) if the generic lookup was
+   * successful -- regardless of what the entry extraction callback does. */
+  svn_boolean_t out_of_date;
 } svn_fs_x__ede_baton_t;
 
 /**
  * Implements #svn_cache__partial_getter_func_t for a single
  * #svn_fs_x__dirent_t within a serialized directory contents hash,
- * identified by its name (given in @a svn_fs_x__ede_baton_t @a *baton).
+ * identified by its name (in (svn_fs_x__ede_baton_t *) @a *baton).
+ * If the filesize specified in the baton does not match the cached
+ * value for this directory, @a *out will be NULL as well.
  */
 svn_error_t *
 svn_fs_x__extract_dir_entry(void **out,
@@ -184,7 +208,10 @@ svn_fs_x__extract_dir_entry(void **out,
  * Describes the change to be done to a directory: Set the entry
  * identify by @a name to the value @a new_entry. If the latter is
  * @c NULL, the entry shall be removed if it exists. Otherwise it
- * will be replaced or automatically added, respectively.
+ * will be replaced or automatically added, respectively.  The
+ * @a filesize allows readers to identify stale cache data (e.g.
+ * due to concurrent access to txns); writers use it to update the
+ * cached file size info.
  */
 typedef struct replace_baton_t
 {
@@ -193,6 +220,10 @@ typedef struct replace_baton_t
 
   /** directory entry to insert instead */
   svn_fs_x__dirent_t *new_entry;
+
+  /** Current length of the in-txn in-disk representation of the directory.
+   * SVN_INVALID_FILESIZE if unknown. */
+  svn_filesize_t txn_filesize;
 } replace_baton_t;
 
 /**
@@ -207,6 +238,17 @@ svn_fs_x__replace_dir_entry(void **data,
                             apr_pool_t *pool);
 
 /**
+ * Implements #svn_cache__partial_setter_func_t for a #svn_fs_x__dir_data_t
+ * at @a *data, resetting its txn_filesize field to SVN_INVALID_FILESIZE.
+ * &a baton should be NULL.
+ */
+svn_error_t *
+svn_fs_x__reset_txn_filesize(void **data,
+                             apr_size_t *data_len,
+                             void *baton,
+                             apr_pool_t *pool);
+
+/**
  * Implements #svn_cache__serialize_func_t for a #svn_fs_x__rep_header_t.
  */
 svn_error_t *
@@ -244,4 +286,28 @@ svn_fs_x__deserialize_changes(void **out
                               apr_size_t data_len,
                               apr_pool_t *result_pool);
 
+/* Baton type to be used with svn_fs_x__read_changes_block. */
+typedef struct svn_fs_x__read_changes_block_baton_t
+{
+  /* Deliver data starting from this index within the changes list. */
+  int start;
+
+  /* To be set by svn_fs_x__read_changes_block:
+     Did we deliver the last change in that list? */
+  svn_boolean_t *eol;
+} svn_fs_x__read_changes_block_baton_t;
+
+/**
+ * Implements #svn_cache__partial_getter_func_t, returning a number of
+ * #svn_fs_x__change_t * in an #apr_array_header_t.  The @a *baton of type
+ * 'svn_fs_x__read_changes_block_baton_t describes what the first index
+ * in that block should be.
+ */
+svn_error_t *
+svn_fs_x__read_changes_block(void **out,
+                             const void *data,
+                             apr_size_t data_len,
+                             void *baton,
+                             apr_pool_t *pool);
+
 #endif

Modified: subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.c Fri Apr 29 18:38:53 2016
@@ -25,6 +25,7 @@
 #include <assert.h>
 #include <apr_sha1.h>
 
+#include "svn_error_codes.h"
 #include "svn_hash.h"
 #include "svn_props.h"
 #include "svn_sorts.h"
@@ -42,6 +43,7 @@
 #include "rep-cache.h"
 #include "index.h"
 #include "batch_fsync.h"
+#include "revprops.h"
 
 #include "private/svn_fs_util.h"
 #include "private/svn_fspath.h"
@@ -280,17 +282,33 @@ with_some_lock_file(with_lock_baton_t *b
           ffd->has_write_lock = TRUE;
         }
 
-      /* nobody else will modify the repo state
-         => read HEAD & pack info once */
       if (baton->is_inner_most_lock)
         {
-          err = svn_fs_x__update_min_unpacked_rev(fs, pool);
+          /* Use a separate sub-pool for the actual function body and a few
+           * file accesses. So, the lock-pool only contains the file locks.
+           */
+          apr_pool_t *subpool = svn_pool_create(pool);
+
+          /* nobody else will modify the repo state
+             => read HEAD & pack info once */
+          err = svn_fs_x__update_min_unpacked_rev(fs, subpool);
           if (!err)
-            err = svn_fs_x__youngest_rev(&ffd->youngest_rev_cache, fs, pool);
-        }
+            err = svn_fs_x__youngest_rev(&ffd->youngest_rev_cache, fs,
+                                         subpool);
+
+          /* We performed a few file operations. Clean the pool. */
+          svn_pool_clear(subpool);
+
+          if (!err)
+            err = baton->body(baton->baton, subpool);
 
-      if (!err)
-        err = baton->body(baton->baton, pool);
+          svn_pool_destroy(subpool);
+        }
+      else
+        {
+          /* Nested lock level */
+          err = baton->body(baton->baton, pool);
+        }
     }
 
   if (baton->is_outer_most_lock)
@@ -859,16 +877,35 @@ unparse_dir_entry(svn_fs_x__dirent_t *di
                   svn_stream_t *stream,
                   apr_pool_t *scratch_pool)
 {
-  const char *val
-    = apr_psprintf(scratch_pool, "%s %s",
-                   (dirent->kind == svn_node_file) ? SVN_FS_X__KIND_FILE
-                                                   : SVN_FS_X__KIND_DIR,
-                   svn_fs_x__id_unparse(&dirent->id, scratch_pool)->data);
-
-  SVN_ERR(svn_stream_printf(stream, scratch_pool, "K %" APR_SIZE_T_FMT
-                            "\n%s\nV %" APR_SIZE_T_FMT "\n%s\n",
-                            strlen(dirent->name), dirent->name,
-                            strlen(val), val));
+  apr_size_t to_write;
+  apr_size_t name_len = strlen(dirent->name);
+
+  /* A buffer with sufficient space for 
+   * - entry name + 1 terminating NUL
+   * - 1 byte for the node kind
+   * - 2 numbers in 7b/8b encoding for the noderev-id
+   */
+  apr_byte_t *buffer = apr_palloc(scratch_pool,
+                                  name_len + 2 + 2 * SVN__MAX_ENCODED_UINT_LEN);
+
+  /* Now construct the value. */
+  apr_byte_t *p = buffer;
+
+  /* The entry name, terminated by NUL. */
+  memcpy(p, dirent->name, name_len + 1);
+  p += name_len + 1;
+
+  /* The entry type. */
+  p = svn__encode_uint(p, dirent->kind);
+
+  /* The ID. */
+  p = svn__encode_int(p, dirent->id.change_set);
+  p = svn__encode_uint(p, dirent->id.number);
+
+  /* Add the entry to the output stream. */
+  to_write = p - buffer;
+  SVN_ERR(svn_stream_write(stream, (const char *)buffer, &to_write));
+
   return SVN_NO_ERROR;
 }
 
@@ -879,8 +916,15 @@ unparse_dir_entries(apr_array_header_t *
                     svn_stream_t *stream,
                     apr_pool_t *scratch_pool)
 {
+  apr_byte_t buffer[SVN__MAX_ENCODED_UINT_LEN];
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
   int i;
+
+  /* Write the number of entries. */
+  apr_size_t to_write = svn__encode_uint(buffer, entries->nelts) - buffer;
+  SVN_ERR(svn_stream_write(stream, (const char *)buffer, &to_write));
+
+  /* Write all entries */
   for (i = 0; i < entries->nelts; ++i)
     {
       svn_fs_x__dirent_t *dirent;
@@ -890,9 +934,6 @@ unparse_dir_entries(apr_array_header_t *
       SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
     }
 
-  SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s\n",
-                            SVN_HASH_TERMINATOR));
-
   svn_pool_destroy(iterpool);
   return SVN_NO_ERROR;
 }
@@ -933,16 +974,6 @@ fold_change(apr_hash_t *changed_paths,
       /* This path already exists in the hash, so we have to merge
          this change into the already existing one. */
 
-      /* Sanity check: we should be talking about the same node
-         revision ID as our last change except where the last change
-         was a deletion. */
-      if (!svn_fs_x__id_eq(&old_change->noderev_id, &change->noderev_id)
-          && (old_change->change_kind != svn_fs_path_change_delete))
-        return svn_error_create
-          (SVN_ERR_FS_CORRUPT, NULL,
-           _("Invalid change ordering: new node revision ID "
-             "without delete"));
-
       /* Sanity check: an add, replacement, or reset must be the first
          thing to follow a deletion. */
       if ((old_change->change_kind == svn_fs_path_change_delete)
@@ -1176,61 +1207,6 @@ create_new_txn_noderev_from_rev(svn_fs_t
   return svn_fs_x__put_node_revision(fs, noderev, scratch_pool);
 }
 
-/* Read 'txn-current', return it in *TXN_NUMBER and write the next value
-   into 'txn-next' for FS.  Schedule fsyncs in BATCH.  Use SCRATCH_POOL
-   for temporaries. */
-static svn_error_t *
-get_and_txn_key(apr_uint64_t *txn_number,
-                svn_fs_t *fs,
-                svn_fs_x__batch_fsync_t *batch,
-                apr_pool_t *scratch_pool)
-{
-  const char *txn_current_path = svn_fs_x__path_txn_current(fs, scratch_pool);
-  const char *txn_next_path = svn_fs_x__path_txn_next(fs, scratch_pool);
-
-  apr_file_t *file;
-  char new_id_str[SVN_INT64_BUFFER_SIZE];
-
-  svn_stringbuf_t *buf;
-  SVN_ERR(svn_fs_x__read_content(&buf, txn_current_path, scratch_pool));
-
-  /* remove trailing newlines */
-  *txn_number = svn__base36toui64(NULL, buf->data);
-  if (*txn_number == 0)
-    ++(*txn_number);
-
-  /* Increment the key and add a trailing \n to the string so the
-     txn-current file has a newline in it. */
-  SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, txn_next_path,
-                                          scratch_pool));
-  SVN_ERR(svn_io_file_write_full(file, new_id_str,
-                                 svn__ui64tobase36(new_id_str, *txn_number+1),
-                                 NULL, scratch_pool));
-  SVN_ERR(svn_io_copy_perms(txn_current_path, txn_next_path, scratch_pool));
-
-  return SVN_NO_ERROR;
-}
-
-/* Move 'txn-next' into place as 'txn-current' for FS.  Schedule fsyncs
-   in BATCH.  Use SCRATCH_POOL for temporaries. */
-static svn_error_t *
-bump_txn_key(svn_fs_t *fs,
-             svn_fs_x__batch_fsync_t *batch,
-             apr_pool_t *scratch_pool)
-{
-  const char *txn_current_path = svn_fs_x__path_txn_current(fs, scratch_pool);
-  const char *txn_next_path = svn_fs_x__path_txn_next(fs, scratch_pool);
-
-  /* Increment the key and add a trailing \n to the string so the
-     txn-current file has a newline in it. */
-  SVN_ERR(svn_io_file_rename2(txn_next_path, txn_current_path, FALSE,
-                              scratch_pool));
-  SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, txn_current_path,
-                                         scratch_pool));
-
-  return SVN_NO_ERROR;
-}
-
 /* A structure used by get_and_increment_txn_key_body(). */
 typedef struct get_and_increment_txn_key_baton_t
 {
@@ -1246,14 +1222,57 @@ get_and_increment_txn_key_body(void *bat
                                apr_pool_t *scratch_pool)
 {
   get_and_increment_txn_key_baton_t *cb = baton;
-  svn_fs_x__batch_fsync_t *batch;
+  svn_fs_t *fs = cb->fs;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  const char *txn_current_path = svn_fs_x__path_txn_current(fs, scratch_pool);
+  char new_id_str[SVN_INT64_BUFFER_SIZE];
 
-  SVN_ERR(svn_fs_x__batch_fsync_create(&batch, scratch_pool));
-  SVN_ERR(get_and_txn_key(&cb->txn_number, cb->fs, batch, scratch_pool));
-  SVN_ERR(svn_fs_x__batch_fsync_run(batch, scratch_pool));
+  svn_stringbuf_t *buf;
+  SVN_ERR(svn_fs_x__read_content(&buf, txn_current_path, scratch_pool));
 
-  SVN_ERR(bump_txn_key(cb->fs, batch, scratch_pool));
-  SVN_ERR(svn_fs_x__batch_fsync_run(batch, scratch_pool));
+  /* Parse the txn number, stopping at the next non-digit.
+   *
+   * Note that an empty string is being interpreted as "0".
+   * This gives us implicit recovery if the file contents should be lost
+   * due to e.g. power failure.
+   */
+  cb->txn_number = svn__base36toui64(NULL, buf->data);
+  if (cb->txn_number == 0)
+    ++cb->txn_number;
+
+  /* Check for conflicts.  Those might happen if the server crashed and we
+   * had 'svnadmin recover' reset the txn counter.
+   *
+   * Once we found an unused txn id, claim it by creating the respective
+   * txn directory.
+   *
+   * Note that this is not racy because we hold the txn-current-lock.
+   */
+  while (TRUE)
+    {
+      const char *txn_dir;
+      svn_node_kind_t kind;
+      svn_pool_clear(iterpool);
+
+      txn_dir = svn_fs_x__path_txn_dir(fs, cb->txn_number, iterpool);
+      SVN_ERR(svn_io_check_path(txn_dir, &kind, iterpool));
+      if (kind == svn_node_none)
+        {
+          svn_io_dir_make(txn_dir, APR_OS_DEFAULT, iterpool);
+          break;
+        }
+
+      ++cb->txn_number;
+    }
+
+  /* Increment the key and add a trailing \n to the string so the
+     txn-current file has a newline in it. */
+  SVN_ERR(svn_io_write_atomic2(txn_current_path, new_id_str,
+                               svn__ui64tobase36(new_id_str,
+                                                 cb->txn_number + 1),
+                               txn_current_path, FALSE, scratch_pool));
+
+  svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
@@ -1269,38 +1288,21 @@ create_txn_dir(const char **id_p,
                apr_pool_t *result_pool,
                apr_pool_t *scratch_pool)
 {
-  const char *txn_dir;
-  svn_fs_x__data_t *ffd = fs->fsap_data;
-
-  /* If we recently committed a revision through FS, we will have a
-     pre-allocated txn-ID that we can just use. */
-  if (ffd->next_txn_id)
-    {
-      *txn_id = ffd->next_txn_id;
-
-      /* Not pre-allocated anymore. */
-      ffd->next_txn_id = 0;
-    }
-  else
-    {
-      get_and_increment_txn_key_baton_t cb;
-
-      /* Get the current transaction sequence value, which is a base-36
-        number, from the txn-current file, and write an
-        incremented value back out to the file.  Place the revision
-        number the transaction is based off into the transaction id. */
-      cb.fs = fs;
-      SVN_ERR(svn_fs_x__with_txn_current_lock(fs,
-                                              get_and_increment_txn_key_body,
-                                              &cb,
-                                              scratch_pool));
-      *txn_id = cb.txn_number;
-    }
+  get_and_increment_txn_key_baton_t cb;
 
+  /* Get the current transaction sequence value, which is a base-36
+    number, from the txn-current file, and write an
+    incremented value back out to the file.  Place the revision
+    number the transaction is based off into the transaction id. */
+  cb.fs = fs;
+  SVN_ERR(svn_fs_x__with_txn_current_lock(fs,
+                                          get_and_increment_txn_key_body,
+                                          &cb,
+                                          scratch_pool));
+  *txn_id = cb.txn_number;
   *id_p = svn_fs_x__txn_name(*txn_id, result_pool);
-  txn_dir = svn_fs_x__path_txn_dir(fs, *txn_id, scratch_pool);
 
-  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, scratch_pool);
+  return SVN_NO_ERROR;
 }
 
 /* Create a new transaction in filesystem FS, based on revision REV,
@@ -1362,15 +1364,16 @@ create_txn(svn_fs_txn_t **txn_p,
   return SVN_NO_ERROR;
 }
 
-/* Store the property list for transaction TXN_ID in PROPLIST.
-   Perform temporary allocations in SCRATCH_POOL. */
+/* Store the property list for transaction TXN_ID in *PROPLIST, allocated
+   from RESULT_POOL. Perform temporary allocations in SCRATCH_POOL. */
 static svn_error_t *
-get_txn_proplist(apr_hash_t *proplist,
+get_txn_proplist(apr_hash_t **proplist,
                  svn_fs_t *fs,
                  svn_fs_x__txn_id_t txn_id,
+                 apr_pool_t *result_pool,
                  apr_pool_t *scratch_pool)
 {
-  svn_stream_t *stream;
+  svn_stringbuf_t *content;
 
   /* Check for issue #3696. (When we find and fix the cause, we can change
    * this to an assertion.) */
@@ -1380,16 +1383,20 @@ get_txn_proplist(apr_hash_t *proplist,
                               "passed to get_txn_proplist()"));
 
   /* Open the transaction properties file. */
-  SVN_ERR(svn_stream_open_readonly(&stream,
+  SVN_ERR(svn_stringbuf_from_file2(&content,
                                    svn_fs_x__path_txn_props(fs, txn_id,
                                                             scratch_pool),
-                                   scratch_pool, scratch_pool));
+                                   result_pool));
 
   /* Read in the property list. */
-  SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR,
-                         scratch_pool));
+  SVN_ERR_W(svn_fs_x__parse_properties(proplist,
+                                   svn_stringbuf__morph_into_string(content),
+                                   result_pool),
+            apr_psprintf(scratch_pool,
+                         _("malformed property list in transaction '%s'"),
+                         svn_fs_x__path_txn_props(fs, txn_id, scratch_pool)));
 
-  return svn_stream_close(stream);
+  return SVN_NO_ERROR;
 }
 
 /* Save the property list PROPS as the revprops for transaction TXN_ID
@@ -1409,7 +1416,7 @@ set_txn_proplist(svn_fs_t *fs,
                                                         scratch_pool),
                                  svn_io_file_del_none,
                                  scratch_pool, scratch_pool));
-  SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, scratch_pool));
+  SVN_ERR(svn_fs_x__write_properties(stream, props, scratch_pool));
   SVN_ERR(svn_stream_close(stream));
 
   /* Replace the old file with the new one. */
@@ -1446,11 +1453,12 @@ svn_fs_x__change_txn_props(svn_fs_txn_t
                            apr_pool_t *scratch_pool)
 {
   fs_txn_data_t *ftd = txn->fsap_data;
-  apr_hash_t *txn_prop = apr_hash_make(scratch_pool);
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
+  apr_hash_t *txn_prop;
   int i;
   svn_error_t *err;
 
-  err = get_txn_proplist(txn_prop, txn->fs, ftd->txn_id, scratch_pool);
+  err = get_txn_proplist(&txn_prop, txn->fs, ftd->txn_id, subpool, subpool);
   /* Here - and here only - we need to deal with the possibility that the
      transaction property file doesn't yet exist.  The rest of the
      implementation assumes that the file exists, but we're called to set the
@@ -1467,15 +1475,16 @@ svn_fs_x__change_txn_props(svn_fs_txn_t
       if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
           && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
         svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
-                      svn_string_create("1", scratch_pool));
+                      svn_string_create("1", subpool));
 
       svn_hash_sets(txn_prop, prop->name, prop->value);
     }
 
   /* Create a new version of the file and write out the new props. */
   /* Open the transaction properties file. */
-  SVN_ERR(set_txn_proplist(txn->fs, ftd->txn_id, txn_prop, scratch_pool));
+  SVN_ERR(set_txn_proplist(txn->fs, ftd->txn_id, txn_prop, subpool));
 
+  svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
 }
 
@@ -1490,9 +1499,6 @@ svn_fs_x__get_txn(svn_fs_x__transaction_
   svn_fs_x__id_t root_id;
 
   txn = apr_pcalloc(pool, sizeof(*txn));
-  txn->proplist = apr_hash_make(pool);
-
-  SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
   svn_fs_x__init_txn_root(&root_id, txn_id);
 
   SVN_ERR(svn_fs_x__get_node_revision(&noderev, fs, &root_id, pool, pool));
@@ -1564,12 +1570,17 @@ allocate_item_index(apr_uint64_t *item_i
   SVN_ERR(svn_io_file_open(&file,
                             svn_fs_x__path_txn_item_index(fs, txn_id,
                                                           scratch_pool),
-                            APR_READ | APR_WRITE
-                            | APR_CREATE | APR_BUFFERED,
+                            APR_READ | APR_WRITE | APR_CREATE,
                             APR_OS_DEFAULT, scratch_pool));
   SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
                                   &bytes_read, &eof, scratch_pool));
-  if (bytes_read)
+
+  /* Item index file should be shorter than SVN_INT64_BUFFER_SIZE,
+     otherwise we truncate data. */
+  if (!eof)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Unexpected itemidx file length"));
+  else if (bytes_read)
     SVN_ERR(svn_cstring_atoui64(item_index, buffer));
   else
     *item_index = SVN_FS_X__ITEM_INDEX_FIRST_USER;
@@ -1783,12 +1794,16 @@ svn_fs_x__set_entry(svn_fs_t *fs,
                                        scratch_pool, scratch_pool);
   apr_file_t *file;
   svn_stream_t *out;
+  svn_filesize_t filesize;
   svn_fs_x__data_t *ffd = fs->fsap_data;
   apr_pool_t *subpool = svn_pool_create(scratch_pool);
+  const svn_fs_x__id_t *key = &(parent_noderev->noderev_id);
+  svn_fs_x__dirent_t entry;
 
   if (!rep || !svn_fs_x__is_txn(rep->id.change_set))
     {
       apr_array_header_t *entries;
+      svn_fs_x__dir_data_t dir_data;
 
       /* Before we can modify the directory, we need to dump its old
          contents into a mutable representation file. */
@@ -1800,8 +1815,6 @@ svn_fs_x__set_entry(svn_fs_t *fs,
       out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
       SVN_ERR(unparse_dir_entries(entries, out, subpool));
 
-      svn_pool_clear(subpool);
-
       /* Provide the parent with a data rep if it had none before
          (directories so far empty). */
       if (!rep)
@@ -1816,23 +1829,86 @@ svn_fs_x__set_entry(svn_fs_t *fs,
 
       /* Save noderev to disk. */
       SVN_ERR(svn_fs_x__put_node_revision(fs, parent_noderev, subpool));
+
+      /* Immediately populate the txn dir cache to avoid re-reading
+       * the file we just wrote. */
+
+      /* Flush APR buffers. */
+      SVN_ERR(svn_io_file_flush(file, subpool));
+
+      /* Obtain final file size to update txn_dir_cache. */
+      SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
+
+      /* Store in the cache. */
+      dir_data.entries = entries;
+      dir_data.txn_filesize = filesize;
+      SVN_ERR(svn_cache__set(ffd->dir_cache, key, &dir_data, subpool));
+
+      svn_pool_clear(subpool);
     }
   else
     {
+      svn_boolean_t found;
+      svn_filesize_t cached_filesize;
+
       /* The directory rep is already mutable, so just open it for append. */
       SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
-                               APR_OS_DEFAULT, scratch_pool));
-      out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
+                               APR_OS_DEFAULT, subpool));
+      out = svn_stream_from_aprfile2(file, TRUE, subpool);
+
+      /* If the cache contents is stale, drop it.
+       *
+       * Note that the directory file is append-only, i.e. if the size
+       * did not change, the contents didn't either. */
+
+      /* Get the file size that corresponds to the cached contents
+       * (if any). */
+      SVN_ERR(svn_cache__get_partial((void **)&cached_filesize, &found,
+                                     ffd->dir_cache, key,
+                                     svn_fs_x__extract_dir_filesize,
+                                     NULL, subpool));
+
+      /* File size info still matches?
+       * If not, we need to drop the cache entry. */
+      if (found)
+        {
+          SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
+
+          if (cached_filesize != filesize)
+            SVN_ERR(svn_cache__set(ffd->dir_cache, key, NULL, subpool));
+        }
     }
 
+  /* Append an incremental hash entry for the entry change.
+     A deletion is represented by an "unused" noderev-id. */
+  if (id)
+    entry.id = *id;
+  else
+    svn_fs_x__id_reset(&entry.id);
+
+  entry.name = name;
+  entry.kind = kind;
+
+  SVN_ERR(unparse_dir_entry(&entry, out, subpool));
+
+  /* Flush APR buffers. */
+  SVN_ERR(svn_io_file_flush(file, subpool));
+
+  /* Obtain final file size to update txn_dir_cache. */
+  SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
+
+  /* Close file. */
+  SVN_ERR(svn_io_file_close(file, subpool));
+  svn_pool_clear(subpool);
+
   /* update directory cache */
     {
-      /* build parameters: (name, new entry) pair */
-      const svn_fs_x__id_t *key = &(parent_noderev->noderev_id);
+      /* build parameters: name, new entry, new file size  */
       replace_baton_t baton;
 
       baton.name = name;
       baton.new_entry = NULL;
+      baton.txn_filesize = filesize;
 
       if (id)
         {
@@ -1847,25 +1923,7 @@ svn_fs_x__set_entry(svn_fs_t *fs,
                                      svn_fs_x__replace_dir_entry, &baton,
                                      subpool));
     }
-  svn_pool_clear(subpool);
-
-  /* Append an incremental hash entry for the entry change. */
-  if (id)
-    {
-      svn_fs_x__dirent_t entry;
-      entry.name = name;
-      entry.id = *id;
-      entry.kind = kind;
-
-      SVN_ERR(unparse_dir_entry(&entry, out, subpool));
-    }
-  else
-    {
-      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
-                                strlen(name), name));
-    }
 
-  SVN_ERR(svn_io_file_close(file, subpool));
   svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
 }
@@ -1874,7 +1932,6 @@ svn_error_t *
 svn_fs_x__add_change(svn_fs_t *fs,
                      svn_fs_x__txn_id_t txn_id,
                      const char *path,
-                     const svn_fs_x__id_t *id,
                      svn_fs_path_change_kind_t change_kind,
                      svn_boolean_t text_mod,
                      svn_boolean_t prop_mod,
@@ -1897,7 +1954,6 @@ svn_fs_x__add_change(svn_fs_t *fs,
 
   change.path.data = path;
   change.path.len = strlen(path);
-  change.noderev_id = *id;
   change.change_kind = change_kind;
   change.text_mod = text_mod;
   change.prop_mod = prop_mod;
@@ -2222,7 +2278,7 @@ rep_write_get_baton(rep_write_baton_t **
                                                        b->local_pool),
                               b->local_pool);
 
-  SVN_ERR(svn_fs_x__get_file_offset(&b->rep_offset, file, b->local_pool));
+  SVN_ERR(svn_io_file_get_offset(&b->rep_offset, file, b->local_pool));
 
   /* Get the base for this delta. */
   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->local_pool));
@@ -2245,8 +2301,7 @@ rep_write_get_baton(rep_write_baton_t **
                                      b->local_pool));
 
   /* Now determine the offset of the actual svndiff data. */
-  SVN_ERR(svn_fs_x__get_file_offset(&b->delta_start, file,
-                                    b->local_pool));
+  SVN_ERR(svn_io_file_get_offset(&b->delta_start, file, b->local_pool));
 
   /* Cleanup in case something goes wrong. */
   apr_pool_cleanup_register(b->local_pool, b, rep_write_cleanup,
@@ -2269,7 +2324,7 @@ rep_write_get_baton(rep_write_baton_t **
 }
 
 /* For REP->SHA1_CHECKSUM, try to find an already existing representation
-   in FS and return it in *OUT_REP.  If no such representation exists or
+   in FS and return it in *OLD_REP.  If no such representation exists or
    if rep sharing has been disabled for FS, NULL will be returned.  Since
    there may be new duplicate representations within the same uncommitted
    revision, those can be passed in REPS_HASH (maps a sha1 digest onto
@@ -2293,9 +2348,13 @@ get_shared_rep(svn_fs_x__representation_
   if (!ffd->rep_sharing_allowed)
     return SVN_NO_ERROR;
 
+  /* Can't look up if we don't know the key (happens for directories). */
+  if (!rep->has_sha1)
+    return SVN_NO_ERROR;
+
   /* Check and see if we already have a representation somewhere that's
      identical to the one we just wrote out.  Start with the hash lookup
-     because it is cheepest. */
+     because it is cheapest. */
   if (reps_hash)
     *old_rep = apr_hash_get(reps_hash,
                             rep->sha1_digest,
@@ -2365,10 +2424,46 @@ get_shared_rep(svn_fs_x__representation_
         }
     }
 
-  /* Add information that is missing in the cached data. */
-  if (*old_rep)
+  if (!*old_rep)
+    return SVN_NO_ERROR;
+
+  /* A simple guard against general rep-cache induced corruption. */
+  if ((*old_rep)->expanded_size != rep->expanded_size)
     {
-      /* Use the old rep for this content. */
+      /* Make the problem show up in the server log.
+
+         Because not sharing reps is always a safe option,
+         terminating the request would be inappropriate.
+       */
+      svn_checksum_t checksum;
+      checksum.digest = rep->sha1_digest;
+      checksum.kind = svn_checksum_sha1;
+
+      err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                              "Rep size %s mismatches rep-cache.db value %s "
+                              "for SHA1 %s.\n"
+                              "You should delete the rep-cache.db and "
+                              "verify the repository. The cached rep will "
+                              "not be shared.",
+                              apr_psprintf(scratch_pool,
+                                           "%" SVN_FILESIZE_T_FMT,
+                                           rep->expanded_size),
+                              apr_psprintf(scratch_pool,
+                                           "%" SVN_FILESIZE_T_FMT,
+                                           (*old_rep)->expanded_size),
+                              svn_checksum_to_cstring_display(&checksum,
+                                                              scratch_pool));
+
+      (fs->warning)(fs->warning_baton, err);
+      svn_error_clear(err);
+
+      /* Ignore the shared rep. */
+      *old_rep = NULL;
+    }
+  else
+    {
+      /* Add information that is missing in the cached data.
+         Use the old rep for this content. */
       memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
     }
 
@@ -2376,6 +2471,7 @@ get_shared_rep(svn_fs_x__representation_
 }
 
 /* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
+ * SHA1 results are only be set if SHA1_CTX is not NULL.
  * Use SCRATCH_POOL for temporary allocations.
  */
 static svn_error_t *
@@ -2388,10 +2484,12 @@ digests_final(svn_fs_x__representation_t
 
   SVN_ERR(svn_checksum_final(&checksum, md5_ctx, scratch_pool));
   memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
-  SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, scratch_pool));
-  rep->has_sha1 = checksum != NULL;
+  rep->has_sha1 = sha1_ctx != NULL;
   if (rep->has_sha1)
-    memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
+    {
+      SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, scratch_pool));
+      memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
+    }
 
   return SVN_NO_ERROR;
 }
@@ -2415,7 +2513,7 @@ rep_write_contents_close(void *baton)
   SVN_ERR(svn_stream_close(b->delta_stream));
 
   /* Determine the length of the svndiff data. */
-  SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool));
+  SVN_ERR(svn_io_file_get_offset(&offset, b->file, b->local_pool));
   rep->size = offset - b->delta_start;
 
   /* Fill in the rest of the representation field. */
@@ -2467,7 +2565,7 @@ rep_write_contents_close(void *baton)
       noderev_id.number = rep->id.number;
 
       entry.offset = b->rep_offset;
-      SVN_ERR(svn_fs_x__get_file_offset(&offset, b->file, b->local_pool));
+      SVN_ERR(svn_io_file_get_offset(&offset, b->file, b->local_pool));
       entry.size = offset - b->rep_offset;
       entry.type = SVN_FS_X__ITEM_TYPE_FILE_REP;
       entry.item_count = 1;
@@ -2567,7 +2665,7 @@ svn_fs_x__set_proplist(svn_fs_t *fs,
                            APR_WRITE | APR_CREATE | APR_TRUNCATE
                            | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
   out = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
-  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, scratch_pool));
+  SVN_ERR(svn_fs_x__write_properties(out, proplist, scratch_pool));
   SVN_ERR(svn_io_file_close(file, scratch_pool));
 
   /* Mark the node-rev's prop rep as mutable, if not already done. */
@@ -2595,6 +2693,8 @@ typedef struct write_container_baton_t
   apr_size_t size;
 
   svn_checksum_ctx_t *md5_ctx;
+
+  /* SHA1 calculation is optional. If not needed, this will be NULL. */
   svn_checksum_ctx_t *sha1_ctx;
 } write_container_baton_t;
 
@@ -2609,7 +2709,8 @@ write_container_handler(void *baton,
   write_container_baton_t *whb = baton;
 
   SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
-  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
+  if (whb->sha1_ctx)
+    SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
 
   SVN_ERR(svn_stream_write(whb->stream, data, len));
   whb->size += *len;
@@ -2631,7 +2732,7 @@ write_hash_to_stream(svn_stream_t *strea
                      apr_pool_t *scratch_pool)
 {
   apr_hash_t *hash = baton;
-  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, scratch_pool));
+  SVN_ERR(svn_fs_x__write_properties(stream, hash, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -2703,7 +2804,7 @@ write_container_delta_rep(svn_fs_x__repr
   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
   SVN_ERR(svn_fs_x__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
 
-  SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
+  SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
 
   /* Write out the rep header. */
   if (base_rep)
@@ -2724,7 +2825,7 @@ write_container_delta_rep(svn_fs_x__repr
                                                            scratch_pool),
                                   scratch_pool);
   SVN_ERR(svn_fs_x__write_rep_header(&header, file_stream, scratch_pool));
-  SVN_ERR(svn_fs_x__get_file_offset(&delta_start, file, scratch_pool));
+  SVN_ERR(svn_io_file_get_offset(&delta_start, file, scratch_pool));
 
   /* Prepare to write the svndiff data. */
   svn_txdelta_to_svndiff3(&diff_wh,
@@ -2739,7 +2840,8 @@ write_container_delta_rep(svn_fs_x__repr
                                         scratch_pool);
   whb->size = 0;
   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
-  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
+  if (item_type != SVN_FS_X__ITEM_TYPE_DIR_REP)
+    whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
 
   /* serialize the hash */
   stream = svn_stream_create(whb, scratch_pool);
@@ -2750,6 +2852,7 @@ write_container_delta_rep(svn_fs_x__repr
 
   /* Store the results. */
   SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
+  rep->expanded_size = whb->size;
 
   /* Check and see if we already have a representation somewhere that's
      identical to the one we just wrote out. */
@@ -2771,7 +2874,7 @@ write_container_delta_rep(svn_fs_x__repr
       svn_fs_x__id_t noderev_id;
 
       /* Write out our cosmetic end marker. */
-      SVN_ERR(svn_fs_x__get_file_offset(&rep_end, file, scratch_pool));
+      SVN_ERR(svn_io_file_get_offset(&rep_end, file, scratch_pool));
       SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
       SVN_ERR(svn_stream_close(file_stream));
 
@@ -2784,7 +2887,7 @@ write_container_delta_rep(svn_fs_x__repr
       noderev_id.number = rep->id.number;
 
       entry.offset = offset;
-      SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
+      SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
       entry.size = offset - entry.offset;
       entry.type = item_type;
       entry.item_count = 1;
@@ -2793,7 +2896,6 @@ write_container_delta_rep(svn_fs_x__repr
       SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, scratch_pool));
 
       /* update the representation */
-      rep->expanded_size = whb->size;
       rep->size = rep_end - delta_start;
     }
 
@@ -2884,6 +2986,9 @@ get_final_id(svn_fs_x__id_t *part,
    INITIAL_OFFSET is the offset of the proto-rev-file on entry to
    commit_body.
 
+   Collect the pair_cache_key_t of all directories written to the
+   committed cache in DIRECTORY_IDS.
+
    If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
    REPS_POOL) of each data rep that is new in this revision.
 
@@ -2895,6 +3000,10 @@ get_final_id(svn_fs_x__id_t *part,
    node-revision.  It is only controls additional sanity checking
    logic.
 
+   CHANGED_PATHS is the changed paths hash for the new revision.
+   The noderev-ids in it will be updated as soon as the respective
+   nodesrevs got their final IDs assigned.
+
    Temporary allocations are also from SCRATCH_POOL. */
 static svn_error_t *
 write_final_rev(svn_fs_x__id_t *new_id_p,
@@ -2903,10 +3012,12 @@ write_final_rev(svn_fs_x__id_t *new_id_p
                 svn_fs_t *fs,
                 const svn_fs_x__id_t *id,
                 apr_off_t initial_offset,
+                apr_array_header_t *directory_ids,
                 apr_array_header_t *reps_to_cache,
                 apr_hash_t *reps_hash,
                 apr_pool_t *reps_pool,
                 svn_boolean_t at_root,
+                apr_hash_t *changed_paths,
                 apr_pool_t *scratch_pool)
 {
   svn_fs_x__noderev_t *noderev;
@@ -2947,16 +3058,19 @@ write_final_rev(svn_fs_x__id_t *new_id_p
 
           svn_pool_clear(subpool);
           SVN_ERR(write_final_rev(&new_id, file, rev, fs, &dirent->id,
-                                  initial_offset, reps_to_cache, reps_hash,
-                                  reps_pool, FALSE, subpool));
-          if (   svn_fs_x__id_used(&new_id)
-              && (svn_fs_x__get_revnum(new_id.change_set) == rev))
+                                  initial_offset, directory_ids,
+                                  reps_to_cache, reps_hash,
+                                  reps_pool, FALSE, changed_paths, subpool));
+          if (new_id.change_set == change_set)
             dirent->id = new_id;
         }
 
       if (noderev->data_rep
           && ! svn_fs_x__is_revision(noderev->data_rep->id.change_set))
         {
+          svn_fs_x__pair_cache_key_t *key;
+          svn_fs_x__dir_data_t dir_data;
+
           /* Write out the contents of this directory as a text rep. */
           noderev->data_rep->id.change_set = change_set;
           SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
@@ -2965,6 +3079,23 @@ write_final_rev(svn_fs_x__id_t *new_id_p
                                             fs, txn_id, noderev, NULL,
                                             SVN_FS_X__ITEM_TYPE_DIR_REP,
                                             rev, scratch_pool));
+
+          /* Cache the new directory contents.  Otherwise, subsequent reads
+           * or commits will likely have to reconstruct, verify and parse
+           * it again. */
+          key = apr_array_push(directory_ids);
+          key->revision = noderev->data_rep->id.change_set;
+          key->second = noderev->data_rep->id.number;
+
+          /* Store directory contents under the new revision number but mark
+           * it as "stale" by setting the file length to 0.  Committed dirs
+           * will report -1, in-txn dirs will report > 0, so that this can
+           * never match.  We reset that to -1 after the commit is complete.
+           */
+          dir_data.entries = entries;
+          dir_data.txn_filesize = 0;
+
+          SVN_ERR(svn_cache__set(ffd->dir_cache, key, &dir_data, subpool));
         }
     }
   else
@@ -3009,7 +3140,7 @@ write_final_rev(svn_fs_x__id_t *new_id_p
   if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
     noderev->copyroot_rev = rev;
 
-  SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool));
+  SVN_ERR(svn_io_file_get_offset(&my_offset, file, scratch_pool));
 
   SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
                                 noderev->noderev_id.number, scratch_pool));
@@ -3068,7 +3199,7 @@ write_final_rev(svn_fs_x__id_t *new_id_p
   noderev_id.change_set = SVN_FS_X__INVALID_CHANGE_SET;
 
   entry.offset = my_offset;
-  SVN_ERR(svn_fs_x__get_file_offset(&my_offset, file, scratch_pool));
+  SVN_ERR(svn_io_file_get_offset(&my_offset, file, scratch_pool));
   entry.size = my_offset - entry.offset;
   entry.type = SVN_FS_X__ITEM_TYPE_NODEREV;
   entry.item_count = 1;
@@ -3102,7 +3233,7 @@ write_final_changed_path_info(apr_off_t
   svn_fs_x__id_t rev_item
     = {SVN_INVALID_REVNUM, SVN_FS_X__ITEM_INDEX_CHANGES};
 
-  SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
+  SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
 
   /* write to target file & calculate checksum */
   stream = svn_checksum__wrap_write_stream_fnv1a_32x4(&entry.fnv1_checksum,
@@ -3116,7 +3247,7 @@ write_final_changed_path_info(apr_off_t
 
   /* reference changes from the indexes */
   entry.offset = offset;
-  SVN_ERR(svn_fs_x__get_file_offset(&offset, file, scratch_pool));
+  SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
   entry.size = offset - entry.offset;
   entry.type = SVN_FS_X__ITEM_TYPE_CHANGES;
   entry.item_count = 1;
@@ -3264,7 +3395,6 @@ write_final_revprop(const char **path,
   svn_string_t date;
   svn_string_t *client_date;
   apr_file_t *file;
-  svn_stream_t *stream;
 
   SVN_ERR(svn_fs_x__txn_proplist(&props, txn, scratch_pool));
 
@@ -3291,12 +3421,9 @@ write_final_revprop(const char **path,
   /* Create a file at the final revprops location. */
   *path = svn_fs_x__path_revprops(txn->fs, revision, result_pool);
   SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, *path, scratch_pool));
-  SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, *path, scratch_pool));
 
   /* Write the new contents to the final revprops file. */
-  stream = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
-  SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, scratch_pool));
-  SVN_ERR(svn_stream_close(stream));
+  SVN_ERR(svn_fs_x__write_non_packed_revprops(file, props, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -3456,7 +3583,6 @@ write_next_file(svn_fs_t *fs,
 
   /* Create / open the 'next' file. */
   SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, path, scratch_pool));
-  SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, path, scratch_pool));
 
   /* Write its contents. */
   buf = apr_psprintf(scratch_pool, "%ld\n", revision);
@@ -3468,44 +3594,65 @@ write_next_file(svn_fs_t *fs,
   return SVN_NO_ERROR;
 }
 
-/* Baton type to be used with bump_ids. */
-typedef struct bump_ids_baton_t
-{
-  svn_fs_t *fs;
-  svn_revnum_t new_rev;
-  svn_fs_x__batch_fsync_t *batch;
-} bump_ids_baton_t;
-
-/* Bump the 'current' and 'txn-current' files in BATON->FS. */
+/* Bump the 'current' file in FS to NEW_REV.  Schedule fsyncs in BATCH.
+ * Use SCRATCH_POOL for temporary allocations. */
 static svn_error_t *
-bump_ids(void *baton,
-         apr_pool_t *scratch_pool)
+bump_current(svn_fs_t *fs,
+             svn_revnum_t new_rev,
+             svn_fs_x__batch_fsync_t *batch,
+             apr_pool_t *scratch_pool)
 {
-  bump_ids_baton_t *b = baton;
-  svn_fs_x__data_t *ffd = b->fs->fsap_data;
   const char *current_filename;
 
   /* Write the 'next' file. */
-  SVN_ERR(write_next_file(b->fs, b->new_rev, b->batch, scratch_pool));
-
-  /* Allocate a new txn id. */
-  SVN_ERR(get_and_txn_key(&ffd->next_txn_id, b->fs, b->batch, scratch_pool));
+  SVN_ERR(write_next_file(fs, new_rev, batch, scratch_pool));
 
   /* Commit all changes to disk. */
-  SVN_ERR(svn_fs_x__batch_fsync_run(b->batch, scratch_pool));
+  SVN_ERR(svn_fs_x__batch_fsync_run(batch, scratch_pool));
 
   /* Make the revision visible to all processes and threads. */
-  current_filename = svn_fs_x__path_current(b->fs, scratch_pool);
-  SVN_ERR(svn_io_file_rename2(svn_fs_x__path_next(b->fs, scratch_pool),
-                              current_filename, FALSE, scratch_pool));
-  SVN_ERR(svn_fs_x__batch_fsync_new_path(b->batch, current_filename,
-                                         scratch_pool));
-
-  /* Bump txn id. */
-  SVN_ERR(bump_txn_key(b->fs, b->batch, scratch_pool));
+  current_filename = svn_fs_x__path_current(fs, scratch_pool);
+  SVN_ERR(svn_fs_x__move_into_place(svn_fs_x__path_next(fs, scratch_pool),
+                                    current_filename, current_filename,
+                                    batch, scratch_pool));
 
   /* Make the new revision permanently visible. */
-  SVN_ERR(svn_fs_x__batch_fsync_run(b->batch, scratch_pool));
+  SVN_ERR(svn_fs_x__batch_fsync_run(batch, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Mark the directories cached in FS with the keys from DIRECTORY_IDS
+ * as "valid" now.  Use SCRATCH_POOL for temporaries. */
+static svn_error_t *
+promote_cached_directories(svn_fs_t *fs,
+                           apr_array_header_t *directory_ids,
+                           apr_pool_t *scratch_pool)
+{
+  svn_fs_x__data_t *ffd = fs->fsap_data;
+  apr_pool_t *iterpool;
+  int i;
+
+  if (!ffd->dir_cache)
+    return SVN_NO_ERROR;
+
+  iterpool = svn_pool_create(scratch_pool);
+  for (i = 0; i < directory_ids->nelts; ++i)
+    {
+      const svn_fs_x__pair_cache_key_t *key
+        = &APR_ARRAY_IDX(directory_ids, i, svn_fs_x__pair_cache_key_t);
+
+      svn_pool_clear(iterpool);
+
+      /* Currently, the entry for KEY - if it still exists - is marked
+       * as "stale" and would not be used.  Mark it as current for in-
+       * revison data. */
+      SVN_ERR(svn_cache__set_partial(ffd->dir_cache, key,
+                                     svn_fs_x__reset_txn_filesize, NULL,
+                                     iterpool));
+    }
+
+  svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
@@ -3538,7 +3685,8 @@ commit_body(void *baton,
   svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(cb->txn);
   apr_hash_t *changed_paths;
   svn_fs_x__batch_fsync_t *batch;
-  bump_ids_baton_t bump_ids_baton;
+  apr_array_header_t *directory_ids
+    = apr_array_make(scratch_pool, 4, sizeof(svn_fs_x__pair_cache_key_t));
 
   /* We perform a sequence of (potentially) large allocations.
      Keep the peak memory usage low by using a SUBPOOL and cleaning it
@@ -3594,14 +3742,15 @@ commit_body(void *baton,
      ### not complete for any reason the transaction will be lost. */
   SVN_ERR(get_writable_final_rev(&proto_file, cb->fs, txn_id, new_rev,
                                  batch, subpool));
-  SVN_ERR(svn_fs_x__get_file_offset(&initial_offset, proto_file, subpool));
+  SVN_ERR(svn_io_file_get_offset(&initial_offset, proto_file, subpool));
   svn_pool_clear(subpool);
 
   /* Write out all the node-revisions and directory contents. */
   svn_fs_x__init_txn_root(&root_id, txn_id);
   SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, &root_id,
-                          initial_offset, cb->reps_to_cache, cb->reps_hash,
-                          cb->reps_pool, TRUE, subpool));
+                          initial_offset, directory_ids, cb->reps_to_cache,
+                          cb->reps_hash, cb->reps_pool, TRUE, changed_paths,
+                          subpool));
   svn_pool_clear(subpool);
 
   /* Write the changed-path information. */
@@ -3634,16 +3783,8 @@ commit_body(void *baton,
   SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev,
                                                       subpool));
 
-  /* Bump 'current' and 'txn-current'.
-     The latter is a piggy-back allocation of a new txn ID such that
-     reusing this FS for multiple commits does not involve additional
-     fsync latencies.  If that txn ID goes to waste, it's not a big loss
-     because we've got 18 quintillion of them ... */
-  bump_ids_baton.fs = cb->fs;
-  bump_ids_baton.new_rev = new_rev;
-  bump_ids_baton.batch = batch;
-  SVN_ERR(svn_fs_x__with_txn_current_lock(cb->fs, bump_ids, &bump_ids_baton,
-                                          subpool));
+  /* Bump 'current'. */
+  SVN_ERR(bump_current(cb->fs, new_rev, batch, subpool));
 
   /* At this point the new revision is committed and globally visible
      so let the caller know it succeeded by giving it the new revision
@@ -3654,6 +3795,10 @@ commit_body(void *baton,
 
   ffd->youngest_rev_cache = new_rev;
 
+  /* Make the directory contents already cached for the new revision
+   * visible. */
+  SVN_ERR(promote_cached_directories(cb->fs, directory_ids, subpool));
+
   /* Remove this transaction directory. */
   SVN_ERR(svn_fs_x__purge_txn(cb->fs, cb->txn->id, subpool));
 
@@ -3825,10 +3970,8 @@ svn_fs_x__txn_proplist(apr_hash_t **tabl
                        svn_fs_txn_t *txn,
                        apr_pool_t *pool)
 {
-  apr_hash_t *proplist = apr_hash_make(pool);
-  SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_x__txn_get_id(txn),
-                           pool));
-  *table_p = proplist;
+  SVN_ERR(get_txn_proplist(table_p, txn->fs, svn_fs_x__txn_get_id(txn),
+                           pool, pool));
 
   return SVN_NO_ERROR;
 }

Modified: subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.h
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.h?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.h (original)
+++ subversion/branches/authzperf/subversion/libsvn_fs_x/transaction.h Fri Apr 29 18:38:53 2016
@@ -167,9 +167,9 @@ svn_fs_x__set_entry(svn_fs_t *fs,
                     apr_pool_t *scratch_pool);
 
 /* Add a change to the changes record for filesystem FS in transaction
-   TXN_ID.  Mark path PATH, having noderev-id ID, as changed according to
-   the type in CHANGE_KIND.  If the text representation was changed set
-   TEXT_MOD to TRUE, and likewise for PROP_MOD as well as MERGEINFO_MOD.
+   TXN_ID.  Mark path PATH as changed according to the type in
+   CHANGE_KIND.  If the text representation was changed set TEXT_MOD
+   to TRUE, and likewise for PROP_MOD as well as MERGEINFO_MOD.
    If this change was the result of a copy, set COPYFROM_REV and
    COPYFROM_PATH to the revision and path of the copy source, otherwise
    they should be set to SVN_INVALID_REVNUM and NULL.  Perform any
@@ -178,7 +178,6 @@ svn_error_t *
 svn_fs_x__add_change(svn_fs_t *fs,
                      svn_fs_x__txn_id_t txn_id,
                      const char *path,
-                     const svn_fs_x__id_t *id,
                      svn_fs_path_change_kind_t change_kind,
                      svn_boolean_t text_mod,
                      svn_boolean_t prop_mod,

Modified: subversion/branches/authzperf/subversion/libsvn_fs_x/tree.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_fs_x/tree.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_fs_x/tree.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_fs_x/tree.c Fri Apr 29 18:38:53 2016
@@ -224,18 +224,16 @@ parent_path_relpath(svn_fs_x__dag_path_t
 
 /* Add a change to the changes table in FS, keyed on transaction id
    TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
-   PATH (whose node revision id is--or was, in the case of a
-   deletion--NODEREV_ID), and optionally that TEXT_MODs, PROP_MODs or
-   MERGEINFO_MODs occurred.  If the change resulted from a copy,
-   COPYFROM_REV and COPYFROM_PATH specify under which revision and path
-   the node was copied from.  If this was not part of a copy, COPYFROM_REV
-   should be SVN_INVALID_REVNUM.  Use SCRATCH_POOL for temporary allocations.
+   PATH, and optionally that TEXT_MODs, PROP_MODs or MERGEINFO_MODs
+   occurred.  If the change resulted from a copy, COPYFROM_REV and
+   COPYFROM_PATH specify under which revision and path the node was
+   copied from.  If this was not part of a copy, COPYFROM_REV should
+   be SVN_INVALID_REVNUM.  Use SCRATCH_POOL for temporary allocations.
  */
 static svn_error_t *
 add_change(svn_fs_t *fs,
            svn_fs_x__txn_id_t txn_id,
            const char *path,
-           const svn_fs_x__id_t *noderev_id,
            svn_fs_path_change_kind_t change_kind,
            svn_boolean_t text_mod,
            svn_boolean_t prop_mod,
@@ -248,8 +246,7 @@ add_change(svn_fs_t *fs,
   return svn_fs_x__add_change(fs, txn_id,
                               svn_fs__canonicalize_abspath(path,
                                                            scratch_pool),
-                              noderev_id, change_kind,
-                              text_mod, prop_mod, mergeinfo_mod,
+                              change_kind, text_mod, prop_mod, mergeinfo_mod,
                               node_kind, copyfrom_rev, copyfrom_path,
                               scratch_pool);
 }
@@ -388,26 +385,6 @@ x_node_created_path(const char **created
 }
 
 
-/* Set *KIND_P to the type of node located at PATH under ROOT.
-   Perform temporary allocations in SCRATCH_POOL. */
-static svn_error_t *
-node_kind(svn_node_kind_t *kind_p,
-          svn_fs_root_t *root,
-          const char *path,
-          apr_pool_t *scratch_pool)
-{
-  dag_node_t *node;
-
-  /* Get the node id. */
-  SVN_ERR(svn_fs_x__get_temp_dag_node(&node, root, path, scratch_pool));
-
-  /* Use the node id to get the real kind. */
-  *kind_p = svn_fs_x__dag_node_kind(node);
-
-  return SVN_NO_ERROR;
-}
-
-
 /* Set *KIND_P to the type of node present at PATH under ROOT.  If
    PATH does not exist under ROOT, set *KIND_P to svn_node_none.  Use
    SCRATCH_POOL for temporary allocation. */
@@ -417,7 +394,16 @@ svn_fs_x__check_path(svn_node_kind_t *ki
                      const char *path,
                      apr_pool_t *scratch_pool)
 {
-  svn_error_t *err = node_kind(kind_p, root, path, scratch_pool);
+  dag_node_t *node;
+
+  /* Get the node id. */
+  svn_error_t *err = svn_fs_x__get_temp_dag_node(&node, root, path,
+                                                 scratch_pool);
+
+  /* Use the node id to get the real kind. */
+  if (!err)
+    *kind_p = svn_fs_x__dag_node_kind(node);
+
   if (err &&
       ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
        || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
@@ -584,7 +570,6 @@ x_change_node_prop(svn_fs_root_t *root,
 
   /* Make a record of this modification in the changes table. */
   SVN_ERR(add_change(root->fs, txn_id, path,
-                     svn_fs_x__dag_get_id(dag_path->node),
                      svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
                      svn_fs_x__dag_node_kind(dag_path->node),
                      SVN_INVALID_REVNUM, NULL, subpool));
@@ -1457,7 +1442,7 @@ x_make_dir(svn_fs_root_t *root,
   svn_fs_x__update_dag_cache(sub_dir);
 
   /* Make a record of this modification in the changes table. */
-  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir),
+  SVN_ERR(add_change(root->fs, txn_id, path,
                      svn_fs_path_change_add, FALSE, FALSE, FALSE,
                      svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
 
@@ -1517,7 +1502,6 @@ x_delete_node(svn_fs_root_t *root,
 
   /* Make a record of this modification in the changes table. */
   SVN_ERR(add_change(root->fs, txn_id, path,
-                     svn_fs_x__dag_get_id(dag_path->node),
                      svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
                      SVN_INVALID_REVNUM, NULL, subpool));
 
@@ -1654,8 +1638,7 @@ copy_helper(svn_fs_root_t *from_root,
       /* Make a record of this modification in the changes table. */
       SVN_ERR(svn_fs_x__get_dag_node(&new_node, to_root, to_path,
                                      scratch_pool, scratch_pool));
-      SVN_ERR(add_change(to_root->fs, txn_id, to_path,
-                         svn_fs_x__dag_get_id(new_node), kind, FALSE,
+      SVN_ERR(add_change(to_root->fs, txn_id, to_path, kind, FALSE,
                          FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
                          from_root->rev, from_canonpath, scratch_pool));
     }
@@ -1795,7 +1778,7 @@ x_make_file(svn_fs_root_t *root,
   svn_fs_x__update_dag_cache(child);
 
   /* Make a record of this modification in the changes table. */
-  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child),
+  SVN_ERR(add_change(root->fs, txn_id, path,
                      svn_fs_path_change_add, TRUE, FALSE, FALSE,
                      svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
 
@@ -2003,7 +1986,6 @@ apply_textdelta(void *baton,
 
   /* Make a record of this modification in the changes table. */
   return add_change(tb->root->fs, txn_id, tb->path,
-                    svn_fs_x__dag_get_id(tb->node),
                     svn_fs_path_change_modify, TRUE, FALSE, FALSE,
                     svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
 }
@@ -2144,7 +2126,6 @@ apply_text(void *baton,
 
   /* Make a record of this modification in the changes table. */
   return add_change(tb->root->fs, txn_id, tb->path,
-                    svn_fs_x__dag_get_id(tb->node),
                     svn_fs_path_change_modify, TRUE, FALSE, FALSE,
                     svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
 }
@@ -2200,23 +2181,18 @@ x_contents_changed(svn_boolean_t *change
       (SVN_ERR_FS_GENERAL, NULL,
        _("Cannot compare file contents between two different filesystems"));
 
-  /* Check that both paths are files. */
-  {
-    svn_node_kind_t kind;
-
-    SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool));
-    if (kind != svn_node_file)
-      return svn_error_createf
-        (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
-
-    SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool));
-    if (kind != svn_node_file)
-      return svn_error_createf
-        (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
-  }
-
   SVN_ERR(svn_fs_x__get_dag_node(&node1, root1, path1, subpool, subpool));
+  /* Make sure that path is file. */
+  if (svn_fs_x__dag_node_kind(node1) != svn_node_file)
+    return svn_error_createf
+      (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
+
   SVN_ERR(svn_fs_x__get_temp_dag_node(&node2, root2, path2, subpool));
+  /* Make sure that path is file. */
+  if (svn_fs_x__dag_node_kind(node2) != svn_node_file)
+    return svn_error_createf
+      (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
+
   SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
                                          strict, subpool));
 
@@ -2260,92 +2236,142 @@ x_get_file_delta_stream(svn_txdelta_stre
 
 /* Finding Changes */
 
-/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return
-   it in *RESULT_P.  Pass CONTEXT to the ID API object being created. */
+/* Implement changes_iterator_vtable_t.get for in-txn change lists.
+   There is no specific FSAP data type, a simple APR hash iterator
+   to the underlying collection is sufficient. */
 static svn_error_t *
-construct_fs_path_change(svn_fs_path_change2_t **result_p,
-                         svn_fs_x__id_context_t *context,
-                         svn_fs_x__change_t *change,
-                         apr_pool_t *result_pool)
+x_txn_changes_iterator_get(svn_fs_path_change3_t **change,
+                           svn_fs_path_change_iterator_t *iterator)
 {
-  const svn_fs_id_t *id
-    = svn_fs_x__id_create(context, &change->noderev_id, result_pool);
-  svn_fs_path_change2_t *result
-    = svn_fs__path_change_create_internal(id, change->change_kind,
-                                          result_pool);
+  apr_hash_index_t *hi = iterator->fsap_data;
 
-  result->text_mod = change->text_mod;
-  result->prop_mod = change->prop_mod;
-  result->node_kind = change->node_kind;
+  if (hi)
+    {
+      *change = apr_hash_this_val(hi);
+      iterator->fsap_data = apr_hash_next(hi);
+    }
+  else
+    {
+      *change = NULL;
+    }
 
-  result->copyfrom_known = change->copyfrom_known;
-  result->copyfrom_rev = change->copyfrom_rev;
-  result->copyfrom_path = change->copyfrom_path;
+  return SVN_NO_ERROR;
+}
+
+static changes_iterator_vtable_t txn_changes_iterator_vtable =
+{
+  x_txn_changes_iterator_get
+};
 
-  result->mergeinfo_mod = change->mergeinfo_mod;
+/* FSAP data structure for in-revision changes list iterators. */
+typedef struct fs_revision_changes_iterator_data_t
+{
+  /* Context that tells the lower layers from where to fetch the next
+     block of changes. */
+  svn_fs_x__changes_context_t *context;
+
+  /* Changes to send. */
+  apr_array_header_t *changes;
+
+  /* Current indexes within CHANGES. */
+  int idx;
 
-  *result_p = result;
+  /* A cleanable scratch pool in case we need one.
+     No further sub-pool creation necessary. */
+  apr_pool_t *scratch_pool;
+} fs_revision_changes_iterator_data_t;
+
+static svn_error_t *
+x_revision_changes_iterator_get(svn_fs_path_change3_t **change,
+                                svn_fs_path_change_iterator_t *iterator)
+{
+  fs_revision_changes_iterator_data_t *data = iterator->fsap_data;
+
+  /* If we exhausted our block of changes and did not reach the end of the
+     list, yet, fetch the next block.  Note that that block may be empty. */
+  if ((data->idx >= data->changes->nelts) && !data->context->eol)
+    {
+      apr_pool_t *changes_pool = data->changes->pool;
+
+      /* Drop old changes block, read new block. */
+      svn_pool_clear(changes_pool);
+      SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context,
+                                    changes_pool, data->scratch_pool));
+      data->idx = 0;
+
+      /* Immediately release any temporary data. */
+      svn_pool_clear(data->scratch_pool);
+    }
+
+  if (data->idx < data->changes->nelts)
+    {
+      *change = APR_ARRAY_IDX(data->changes, data->idx,
+                              svn_fs_x__change_t *);
+      ++data->idx;
+    }
+  else
+    {
+      *change = NULL;
+    }
 
   return SVN_NO_ERROR;
 }
 
-/* Set *CHANGED_PATHS_P to a newly allocated hash containing
-   descriptions of the paths changed under ROOT.  The hash is keyed
-   with const char * paths and has svn_fs_path_change2_t * values.  Use
-   POOL for all allocations. */
-static svn_error_t *
-x_paths_changed(apr_hash_t **changed_paths_p,
-                svn_fs_root_t *root,
-                apr_pool_t *pool)
+static changes_iterator_vtable_t rev_changes_iterator_vtable =
 {
-  apr_hash_t *changed_paths;
-  svn_fs_path_change2_t *path_change;
-  svn_fs_x__id_context_t *context
-    = svn_fs_x__id_create_context(root->fs, pool);
+  x_revision_changes_iterator_get
+};
 
+static svn_error_t *
+x_report_changes(svn_fs_path_change_iterator_t **iterator,
+                 svn_fs_root_t *root,
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
+{
+  svn_fs_path_change_iterator_t *result = apr_pcalloc(result_pool,
+                                                      sizeof(*result));
   if (root->is_txn_root)
     {
-      apr_hash_index_t *hi;
+      apr_hash_t *changed_paths;
       SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
                                           svn_fs_x__root_txn_id(root),
-                                          pool));
-      for (hi = apr_hash_first(pool, changed_paths);
-           hi;
-           hi = apr_hash_next(hi))
-        {
-          svn_fs_x__change_t *change = apr_hash_this_val(hi);
-          SVN_ERR(construct_fs_path_change(&path_change, context, change,
-                                           pool));
-          apr_hash_set(changed_paths,
-                       apr_hash_this_key(hi), apr_hash_this_key_len(hi),
-                       path_change);
-        }
+                                          result_pool));
+
+      result->fsap_data = apr_hash_first(result_pool, changed_paths);
+      result->vtable = &txn_changes_iterator_vtable;
     }
   else
     {
-      apr_array_header_t *changes;
-      int i;
-
-      SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool));
-
-      changed_paths = svn_hash__make(pool);
-      for (i = 0; i < changes->nelts; ++i)
-        {
-          svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i,
-                                                     svn_fs_x__change_t *);
-          SVN_ERR(construct_fs_path_change(&path_change, context, change,
-                                           pool));
-          apr_hash_set(changed_paths, change->path.data, change->path.len,
-                       path_change);
-        }
+      /* The block of changes that we retrieve need to live in a separately
+         cleanable pool. */
+      apr_pool_t *changes_pool = svn_pool_create(result_pool);
+
+      /* Our iteration context info. */
+      fs_revision_changes_iterator_data_t *data = apr_pcalloc(result_pool,
+                                                              sizeof(*data));
+
+      /* This pool must remain valid as long as ITERATOR lives but will
+         be used only for temporary allocations and will be cleaned up
+         frequently.  So, this must be a sub-pool of RESULT_POOL. */
+      data->scratch_pool = svn_pool_create(result_pool);
+
+      /* Fetch the first block of data. */
+      SVN_ERR(svn_fs_x__create_changes_context(&data->context,
+                                               root->fs, root->rev,
+                                               result_pool, scratch_pool));
+      SVN_ERR(svn_fs_x__get_changes(&data->changes, data->context,
+                                    changes_pool, scratch_pool));
+
+      /* Return the fully initialized object. */
+      result->fsap_data = data;
+      result->vtable = &rev_changes_iterator_vtable;
     }
 
-  *changed_paths_p = changed_paths;
+  *iterator = result;
 
   return SVN_NO_ERROR;
 }
 
-
 
 /* Our coolio opaque history object. */
 typedef struct fs_history_data_t
@@ -3174,7 +3200,8 @@ x_get_mergeinfo(svn_mergeinfo_catalog_t
 
 /* The vtable associated with root objects. */
 static root_vtable_t root_vtable = {
-  x_paths_changed,
+  NULL,
+  x_report_changes,
   svn_fs_x__check_path,
   x_node_history,
   x_node_id,