Hi Bert, We only accept incoming prop changes if there were no prop changes at the directory.
-- Stefan^2. On Tue, Jan 14, 2014 at 11:39 PM, Bert Huijben <[email protected]> wrote: > How do we now check that property changes don't conflict? > > Before this patch we assumed that the client code would do the > verification work. Are property changes now just overwritten? > > Or is your patch limited to one round of property changes? > > I think changes like this need more documentation than just mentioning > that it is a scalability issue that can be fixed 'easily'. There were quite > a few design decisions behind this limitation when I looked at it a few > years ago. > > This might break some of the promises that make the 'incomplete' handling > capable of resuming an update. Both the status of the properties and > entries of the directory are tied to that. > > Bert > ------------------------------ > From: [email protected] > Sent: 14-1-2014 23:09 > To: [email protected] > Subject: svn commit: r1558224 - in > /subversion/trunk/subversion:libsvn_fs_fs/tree.c libsvn_fs_x/tree.c > tests/libsvn_fs/fs-test.c > > Author: stefan2 > Date: Tue Jan 14 22:09:01 2014 > New Revision: 1558224 > > URL: http://svn.apache.org/r1558224 > Log: > For FSFS and FSX, fix a major merge scalability issue described here: > http://svn.haxx.se/dev/archive-2014-01/0057.shtml > > The bottom line is that we allow incoming prop changes on directories > that have no addition nor deletion of entries relative to the TXN > base revision. This patch updates existing tests and provided a > specific new one. > > * subversion/libsvn_fs_fs/tree.c > (compare_dir_structure): A new utility function checking that all > dirents are still the same, although > possibly at different revisions. > (merge): An dir prop change in a txn should not conflict with > simply node upgrades within that directory. > > * subversion/libsvn_fs_x/tree.c > (compare_dir_structure, > merge): Similar implementation as for FSFS with a slight adaptation > due to different directory container types. > > * subversion/tests/libsvn_fs/fs-test.c > (unordered_txn_dirprops): Update expected results. > (dir_prop_merge): New test. > (test_compat_version): Register the new test. > > Modified: > subversion/trunk/subversion/libsvn_fs_fs/tree.c > subversion/trunk/subversion/libsvn_fs_x/tree.c > subversion/trunk/subversion/tests/libsvn_fs/fs-test.c > > Modified: subversion/trunk/subversion/libsvn_fs_fs/tree.c > URL: > http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/tree.c?rev=1558224&r1=1558223&r2=1558224&view=diff > > ============================================================================== > --- subversion/trunk/subversion/libsvn_fs_fs/tree.c (original) > +++ subversion/trunk/subversion/libsvn_fs_fs/tree.c Tue Jan 14 22:09:01 > 2014 > @@ -1654,6 +1654,53 @@ conflict_err(svn_stringbuf_t *conflict_p > _("Conflict at '%s'"), path); > } > > +/* Compare the directory representations at nodes LHS and RHS and set > + * *CHANGED to TRUE, if at least one entry has been added or removed them. > + * Use POOL for temporary allocations. > + */ > +static svn_error_t * > +compare_dir_structure(svn_boolean_t *changed, > + dag_node_t *lhs, > + dag_node_t *rhs, > + apr_pool_t *pool) > +{ > + apr_array_header_t *lhs_entries; > + apr_array_header_t *rhs_entries; > + int i; > + > + SVN_ERR(svn_fs_fs__dag_dir_entries(&lhs_entries, lhs, pool)); > + SVN_ERR(svn_fs_fs__dag_dir_entries(&rhs_entries, rhs, pool)); > + > + /* different number of entries -> some addition / removal */ > + if (lhs_entries->nelts != rhs_entries->nelts) > + { > + *changed = TRUE; > + return SVN_NO_ERROR; > + } > + > + /* Since directories are sorted by name, we can simply compare their > + entries one-by-one without binary lookup etc. */ > + for (i = 0; i < lhs_entries->nelts; ++i) > + { > + svn_fs_dirent_t *lhs_entry > + = APR_ARRAY_IDX(lhs_entries, i, svn_fs_dirent_t *); > + svn_fs_dirent_t *rhs_entry > + = APR_ARRAY_IDX(rhs_entries, i, svn_fs_dirent_t *); > + > + if (strcmp(lhs_entry->name, rhs_entry->name) > + || !svn_fs_fs__id_part_eq(svn_fs_fs__id_node_id(lhs_entry->id), > + svn_fs_fs__id_node_id(rhs_entry->id)) > + || !svn_fs_fs__id_part_eq(svn_fs_fs__id_copy_id(lhs_entry->id), > + svn_fs_fs__id_copy_id(rhs_entry->id))) > + { > + *changed = TRUE; > + return SVN_NO_ERROR; > + } > + } > + > + *changed = FALSE; > + return SVN_NO_ERROR; > +} > > /* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR > * and TARGET must be distinct node revisions. TARGET_PATH should > @@ -1828,10 +1875,23 @@ merge(svn_stringbuf_t *conflict_p, > it doesn't do a brute-force comparison on textual contents, so > it won't do that here either. Checking to see if the propkey > atoms are `equal' is enough. */ > - if (! svn_fs_fs__noderev_same_rep_key(tgt_nr->prop_rep, > anc_nr->prop_rep)) > - return conflict_err(conflict_p, target_path); > if (! svn_fs_fs__noderev_same_rep_key(src_nr->prop_rep, > anc_nr->prop_rep)) > return conflict_err(conflict_p, target_path); > + > + /* The directory entries got changed in the repository but the > directory > + properties did not. */ > + if (! svn_fs_fs__noderev_same_rep_key(tgt_nr->prop_rep, > anc_nr->prop_rep)) > + { > + /* There is an incoming prop change for this directory. > + We will accept it only if the directory changes were mere > updates > + to its entries, i.e. there were no additions or removals. > + Those could cause update problems to the working copy. */ > + svn_boolean_t changed; > + SVN_ERR(compare_dir_structure(&changed, source, ancestor, pool)); > + > + if (changed) > + return conflict_err(conflict_p, target_path); > + } > } > > /* ### todo: it would be more efficient to simply check for a NULL > > Modified: subversion/trunk/subversion/libsvn_fs_x/tree.c > URL: > http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/tree.c?rev=1558224&r1=1558223&r2=1558224&view=diff > > ============================================================================== > --- subversion/trunk/subversion/libsvn_fs_x/tree.c (original) > +++ subversion/trunk/subversion/libsvn_fs_x/tree.c Tue Jan 14 22:09:01 2014 > @@ -1630,6 +1630,56 @@ conflict_err(svn_stringbuf_t *conflict_p > _("Conflict at '%s'"), path); > } > > +/* Compare the directory representations at nodes LHS and RHS and set > + * *CHANGED to TRUE, if at least one entry has been added or removed them. > + * Use POOL for temporary allocations. > + */ > +static svn_error_t * > +compare_dir_structure(svn_boolean_t *changed, > + dag_node_t *lhs, > + dag_node_t *rhs, > + apr_pool_t *pool) > +{ > + apr_hash_t *lhs_entries; > + apr_hash_t *rhs_entries; > + apr_hash_index_t *hi; > + > + SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, pool)); > + SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, pool)); > + > + /* different number of entries -> some addition / removal */ > + if (apr_hash_count(lhs_entries) != apr_hash_count(rhs_entries)) > + { > + *changed = TRUE; > + return SVN_NO_ERROR; > + } > + > + /* Since the number of dirents is the same, we simply need to do a > + one-sided comparison. */ > + for (hi = apr_hash_first(pool, lhs_entries); hi; hi = apr_hash_next(hi)) > + { > + svn_fs_dirent_t *lhs_entry; > + svn_fs_dirent_t *rhs_entry; > + const char *name; > + apr_ssize_t klen; > + > + apr_hash_this(hi, (const void **)&name, &klen, (void **)&lhs_entry); > + rhs_entry = apr_hash_get(rhs_entries, name, klen); > + > + if (!rhs_entry > + || !svn_fs_x__id_part_eq(svn_fs_x__id_node_id(lhs_entry->id), > + svn_fs_x__id_node_id(rhs_entry->id)) > + || !svn_fs_x__id_part_eq(svn_fs_x__id_copy_id(lhs_entry->id), > + svn_fs_x__id_copy_id(rhs_entry->id))) > + { > + *changed = TRUE; > + return SVN_NO_ERROR; > + } > + } > + > + *changed = FALSE; > + return SVN_NO_ERROR; > +} > > /* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR > * and TARGET must be distinct node revisions. TARGET_PATH should > @@ -1803,10 +1853,23 @@ merge(svn_stringbuf_t *conflict_p, > it doesn't do a brute-force comparison on textual contents, so > it won't do that here either. Checking to see if the propkey > atoms are `equal' is enough. */ > - if (! svn_fs_x__noderev_same_rep_key(tgt_nr->prop_rep, > anc_nr->prop_rep)) > - return conflict_err(conflict_p, target_path); > if (! svn_fs_x__noderev_same_rep_key(src_nr->prop_rep, > anc_nr->prop_rep)) > return conflict_err(conflict_p, target_path); > + > + /* The directory entries got changed in the repository but the > directory > + properties did not. */ > + if (! svn_fs_x__noderev_same_rep_key(tgt_nr->prop_rep, > anc_nr->prop_rep)) > + { > + /* There is an incoming prop change for this directory. > + We will accept it only if the directory changes were mere > updates > + to its entries, i.e. there were no additions or removals. > + Those could cause update problems to the working copy. */ > + svn_boolean_t changed; > + SVN_ERR(compare_dir_structure(&changed, source, ancestor, pool)); > + > + if (changed) > + return conflict_err(conflict_p, target_path); > + } > } > > /* ### todo: it would be more efficient to simply check for a NULL > > Modified: subversion/trunk/subversion/tests/libsvn_fs/fs-test.c > URL: > http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_fs/fs-test.c?rev=1558224&r1=1558223&r2=1558224&view=diff > > ============================================================================== > --- subversion/trunk/subversion/tests/libsvn_fs/fs-test.c (original) > +++ subversion/trunk/subversion/tests/libsvn_fs/fs-test.c Tue Jan 14 > 22:09:01 2014 > @@ -4582,6 +4582,7 @@ unordered_txn_dirprops(const svn_test_op > svn_fs_root_t *txn_root, *txn_root2; > svn_string_t pval; > svn_revnum_t new_rev, not_rev; > + svn_boolean_t is_bdb = strcmp(opts->fs_type, "bdb") == 0; > > /* This is a regression test for issue #2751. */ > > @@ -4638,10 +4639,21 @@ unordered_txn_dirprops(const svn_test_op > /* Commit the first one first. */ > SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool)); > > - /* Then commit the second -- but expect an conflict because the > - directory wasn't up-to-date, which is required for propchanges. */ > - SVN_ERR(test_commit_txn(¬_rev, txn2, "/A/B", pool)); > - SVN_ERR(svn_fs_abort_txn(txn2, pool)); > + /* Some backends are clever then others. */ > + if (is_bdb) > + { > + /* Then commit the second -- but expect an conflict because the > + directory wasn't up-to-date, which is required for propchanges. > */ > + SVN_ERR(test_commit_txn(¬_rev, txn2, "/A/B", pool)); > + SVN_ERR(svn_fs_abort_txn(txn2, pool)); > + } > + else > + { > + /* Then commit the second -- there will be no conflict despite the > + directory being out-of-data because the properties as well as the > + directory structure (list of nodes) was up-to-date. */ > + SVN_ERR(test_commit_txn(¬_rev, txn2, NULL, pool)); > + } > > return SVN_NO_ERROR; > } > @@ -5222,6 +5234,80 @@ test_compat_version(const svn_test_opts_ > return SVN_NO_ERROR; > } > > +static svn_error_t * > +dir_prop_merge(const svn_test_opts_t *opts, > + apr_pool_t *pool) > +{ > + svn_fs_t *fs; > + svn_revnum_t head_rev; > + svn_fs_root_t *root; > + svn_fs_txn_t *txn, *mid_txn, *top_txn, *sub_txn, *c_txn; > + svn_boolean_t is_bdb = strcmp(opts->fs_type, "bdb") == 0; > + > + /* Create test repository. */ > + SVN_ERR(svn_test__create_fs(&fs, "test-dir_prop-merge", opts, pool)); > + > + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); > + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); > + > + /* Create and verify the greek tree. */ > + SVN_ERR(svn_test__create_greek_tree(root, pool)); > + SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool)); > + > + /* Start concurrent transactions */ > + > + /* 1st: modify a mid-level directory */ > + SVN_ERR(svn_fs_begin_txn2(&mid_txn, fs, head_rev, 0, pool)); > + SVN_ERR(svn_fs_txn_root(&root, mid_txn, pool)); > + SVN_ERR(svn_fs_change_node_prop(root, "A/D", "test-prop", > + svn_string_create("val1", pool), pool)); > + svn_fs_close_root(root); > + > + /* 2st: modify a top-level directory */ > + SVN_ERR(svn_fs_begin_txn2(&top_txn, fs, head_rev, 0, pool)); > + SVN_ERR(svn_fs_txn_root(&root, top_txn, pool)); > + SVN_ERR(svn_fs_change_node_prop(root, "A", "test-prop", > + svn_string_create("val2", pool), pool)); > + svn_fs_close_root(root); > + > + SVN_ERR(svn_fs_begin_txn2(&sub_txn, fs, head_rev, 0, pool)); > + SVN_ERR(svn_fs_txn_root(&root, sub_txn, pool)); > + SVN_ERR(svn_fs_change_node_prop(root, "A/D/G", "test-prop", > + svn_string_create("val3", pool), pool)); > + svn_fs_close_root(root); > + > + /* 3rd: modify a conflicting change to the mid-level directory */ > + SVN_ERR(svn_fs_begin_txn2(&c_txn, fs, head_rev, 0, pool)); > + SVN_ERR(svn_fs_txn_root(&root, c_txn, pool)); > + SVN_ERR(svn_fs_change_node_prop(root, "A/D", "test-prop", > + svn_string_create("valX", pool), pool)); > + svn_fs_close_root(root); > + > + /* Prop changes to the same node should conflict */ > + SVN_ERR(test_commit_txn(&head_rev, mid_txn, NULL, pool)); > + SVN_ERR(test_commit_txn(&head_rev, c_txn, "/A/D", pool)); > + SVN_ERR(svn_fs_abort_txn(c_txn, pool)); > + > + /* Changes in a sub-tree should not conflict with prop changes to some > + parent directory but some backends are clever then others. */ > + if (is_bdb) > + { > + SVN_ERR(test_commit_txn(&head_rev, top_txn, "/A", pool)); > + SVN_ERR(svn_fs_abort_txn(top_txn, pool)); > + } > + else > + { > + SVN_ERR(test_commit_txn(&head_rev, top_txn, NULL, pool)); > + } > + > + /* The inverted case is not that trivial to handle. Hence, conflict. > + Depending on the checking order, the reported conflict path differs. > */ > + SVN_ERR(test_commit_txn(&head_rev, sub_txn, is_bdb ? "/A/D" : "/A", > pool)); > + SVN_ERR(svn_fs_abort_txn(sub_txn, pool)); > + > + return SVN_NO_ERROR; > +} > + > > /* > ------------------------------------------------------------------------ */ > > @@ -5315,5 +5401,7 @@ struct svn_test_descriptor_t test_funcs[ > "commit timestamp"), > SVN_TEST_OPTS_PASS(test_compat_version, > "test svn_fs__compatible_version"), > + SVN_TEST_OPTS_PASS(dir_prop_merge, > + "test merge directory properties"), > SVN_TEST_NULL > }; > > >

