> On Jun 10, 2015, at 4:36 PM, Ben Pfaff <b...@nicira.com> wrote:
> 
> On Tue, Jun 09, 2015 at 05:24:11PM -0700, Jarno Rajahalme wrote:
>> The traversal of the list of identical rules from the lookup threads
>> is fragile in the list head is removed during the list traversal.
> 
> fragile "if" the list head…?

Fixed.

>> This patch simplifies the implementation of that list by making the
>> list NULL terminated, singly linked RCU-protected list.  By having the
>> NULL at the end there is no longer a possiblity of missing the point
>> when the list wraps around.  This is significant when there can be
>> multiple elements with the same priority in the list.
>> 
>> This change also decreases the size of the struct cls_match back
>> pre-'visibility' attribute size.
>> 
>> Signed-off-by: Jarno Rajahalme <jrajaha...@nicira.com>
> 
> The comment on cls_match_remove() seems somewhat inconsistent with the
> implementation.  The comment says:
> 
>   ...If 'prev' is NULL, then the 'rule' is a list head, and the 'rule'
>   is only poisoned after all threads have quiesced.
> 
> which to my mind says that when (whether?) 'rule' is poisoned depends on
> whether it is a list head.  But the implementation does a postponed
> poison regardless of whether it is a list head.  (However, in practice,
> cls_match_remove()'s only caller never passes it a list head.)
> 


> Here is a suggestion for a comment improvement.  While I was writing it,
> though, it came to mind that it while it says that the list is sorted by
> priority, it doesn't say how it is sort by version (is it?).
> 

The is list is first sorted by decreasing priority, and equal priority rules 
are in practice sorted by cls_rule->version, even though we never actually use 
the cls_rule->version when inserting in to the list:

- Lookups rely on the invariant that at most one of the duplicates is visible 
in their version, so they stop at highest priority match that is visible to 
them.
- Since all rules that have not been marked for removal in any version are 
visible in version CLS_MAX_VERSION, at most one of the otherwise duplicate 
rules may be not marked for removal.
- We always insert a new rule after rules of equal priority that have not been 
marked for removal to find the potentially displaced rule.
- It follows that the rule not marked for removal (if any) is always the last 
of the identical rules with the same priority.
- It further follows that since the tables version number is monotonically 
increasing, the duplicate rules with the same priority in the list are in 
increasing version order.
  - The version number is in the cls_rule, not in cls_match, and is not 
actually considered by cls_match.

The later patch adding versioned testing to test-classifier verifies this.

While looking into clarifying this I found that in some rather improbable but 
still theoretically possible cases we need to consider the original version 
value (cls_rule->version) in the visibility check as well. I’ll introduce a 
separate patch that adds this to cls_match as well, and simplifies the details 
quite a bit.

> diff --git a/lib/classifier-private.h b/lib/classifier-private.h
> index 01b31e0..9ebb52f 100644
> --- a/lib/classifier-private.h
> +++ b/lib/classifier-private.h
> @@ -65,14 +65,16 @@ struct cls_partition {
>     struct tag_tracker tracker; /* Tracks the bits in 'tags'. */
> };
> 
> -/* Internal representation of a rule in a "struct cls_subtable". */
> +/* Internal representation of a rule in a "struct cls_subtable".
> + *
> + * The 'next' member is an element in a singly linked, null-terminated list.
> + * This list links together identical "cls_match"es in order of decreasing
> + * priority.  The classifier code maintains the invariant that at most one 
> rule
> + * of a given priority is visible for any given lookup version.
> + */
> struct cls_match {
>     /* Accessed by everybody. */
> -    OVSRCU_TYPE(struct cls_match *) next; /* Identical "cls_match"es in the
> -                                           * order of decreasing priority.  
> At
> -                                           * most one rule of a given 
> priority
> -                                           * may be visible to any given 
> lookup
> -                                           * version. */
> +    OVSRCU_TYPE(struct cls_match *) next; /* Equal, lower-priority matches. 
> */
>     OVSRCU_TYPE(struct cls_conjunction_set *) conj_set;
> 
>     /* Accessed only by writers. */
> 

Applied.


> This code reminds me of my favorite idiom for singly linked lists, which
> is to iterate using a pointer-to-pointer rather than a single pointer.
> That way you can use a single variable instead of two of them, which
> sometimes seems cleaner.  Like this:
> 
>    struct slist {
>        struct list *next;
>    } *head;
> 
> ...
> 
>    for (struct slist *iter = &head; *head != NULL; head = &(*head)->next) {
>        ...work with *iter or assign to temporary variable...
>    }
> 
> The nice thing about this is that it's very clean to insert before the
> current position, without any conditionals, e.g.:
> 
>    new_item->next = (*iter)->next;
>    *iter = new_item;
> 
> or to remove the item at the current position:
> 
>    struct slist *victim = *iter;
>    *iter = (*iter)->next;
>    free(victim);
> 
> Dunno if this idiom makes any code cleaner here; up to you.
> 

I’ll leave this for a future exercise, maybe it will be useful also elsewhere 
to have a generic singly linked RCU list.

> In next_visible_rule_in_list(),
>    do {
>        rule = cls_match_next(rule);
>        if (!rule) {
>            /* We have reached the head of the list, stop. */
>            break;
>        }
>    } while (!cls_match_visible_in_version(rule, version));
> could use the "while" clause more completely:
>    do {
>        rule = cls_match_next(rule);
>    } while (rule && !cls_match_visible_in_version(rule, version));

How did I not see this? Thanks!

I removed the postponed poisoning for the same reason we are doing it for the 
rculist, so I applied this to master with the following incremental:

---
 lib/classifier-private.h |   71 ++++++++++++++++++----------------------------
 lib/classifier.c         |    7 +++--
 2 files changed, 31 insertions(+), 47 deletions(-)

diff --git a/lib/classifier-private.h b/lib/classifier-private.h
index 0f982ad..a540371 100644
--- a/lib/classifier-private.h
+++ b/lib/classifier-private.h
@@ -65,14 +65,16 @@ struct cls_partition {
     struct tag_tracker tracker; /* Tracks the bits in 'tags'. */
 };
 
-/* Internal representation of a rule in a "struct cls_subtable". */
+/* Internal representation of a rule in a "struct cls_subtable".
+ *
+ * The 'next' member is an element in a singly linked, null-terminated list.
+ * This list links together identical "cls_match"es in order of decreasing
+ * priority.  The classifier code maintains the invariant that at most one rule
+ * of a given priority is visible for any given lookup version.
+ */
 struct cls_match {
     /* Accessed by everybody. */
-    OVSRCU_TYPE(struct cls_match *) next; /* Identical "cls_match"es in the
-                                           * order of decreasing priority.  At
-                                           * most one rule of a given priority
-                                           * may be visible to any given lookup
-                                           * version. */
+    OVSRCU_TYPE(struct cls_match *) next; /* Equal, lower-priority matches. */
     OVSRCU_TYPE(struct cls_conjunction_set *) conj_set;
 
     /* Accessed only by writers. */
@@ -104,6 +106,9 @@ struct cls_match {
     /* 'flow' must be the last field. */
 };
 
+/* Must be RCU postponed. */
+void cls_match_free_cb(struct cls_match *);
+
 static inline void
 cls_match_set_visibility(struct cls_match *rule, long long version)
 {
@@ -155,56 +160,39 @@ cls_match_next_protected(const struct cls_match *rule)
     return ovsrcu_get_protected(struct cls_match *, &rule->next);
 }
 
-/* Puts 'rule' in the position between 'prev' and 'next'. If 'prev' == NULL,
+/* Puts 'rule' in the position between 'prev' and 'next'.  If 'prev' == NULL,
  * then the 'rule' is the new list head, and if 'next' == NULL, the rule is the
- * new list tail. */
+ * new list tail.
+ * If there are any nodes between 'prev' and 'next', they are dropped from the
+ * list. */
 static inline void
 cls_match_insert(struct cls_match *prev, struct cls_match *next,
                  struct cls_match *rule)
 {
-    if (!prev) {
-        /* 'rule' is the new head. */
-        ovsrcu_set_hidden(&rule->next, next);
-    } else {
-        /* 'rule' is new node after 'prev' */
-        ovsrcu_set_hidden(&rule->next, cls_match_next_protected(prev));
+    ovsrcu_set_hidden(&rule->next, next);
+
+    if (prev) {
         ovsrcu_set(&prev->next, rule);
     }
 }
 
-void cls_match_poison(struct cls_match *);
-
 /* Puts 'new_rule' in the position of 'old_rule', which is the next node after
- * 'prev'. If 'prev' == NULL, then the 'rule' is the new list head.
+ * 'prev'. If 'prev' == NULL, then the 'new_rule' is the new list head.
  *
- * Returns the replaced cls_match, which is not linked to any more, but still
- * links to the later rules, and may still be referenced by other threads until
- * all other threads quiesce.  The returned rule may not be re-inserted,
- * re-initialized, or deleted until after all other threads have quiesced (use
- * ovsrcu_postpone). */
+ * The replaced cls_match still links to the later rules, and may still be
+ * referenced by other threads until all other threads quiesce.  The replaced
+ * rule may not be re-inserted, re-initialized, or deleted until after all
+ * other threads have quiesced (use ovsrcu_postpone). */
 static inline void
 cls_match_replace(struct cls_match *prev,
                   struct cls_match *old_rule, struct cls_match *new_rule)
 {
-    if (prev == NULL) {
-        /* 'new_rule' replaces the old head. */
-        ovsrcu_set_hidden(&new_rule->next, cls_match_next_protected(old_rule));
-    } else {
-        /* 'new_rule' replaces an existing non-head node. */
-        struct cls_match *next = cls_match_next_protected(old_rule);
-
-        ovsrcu_set_hidden(&new_rule->next, next);
-        ovsrcu_set(&prev->next, new_rule);
-    }
-
-#ifndef NDEBUG
-    ovsrcu_postpone(cls_match_poison, old_rule);
-#endif
+    cls_match_insert(prev, cls_match_next_protected(old_rule), new_rule);
 }
 
 /* Removes 'rule' following 'prev' from the list. If 'prev' is NULL, then the
- * 'rule' is a list head, and the 'rule' is only poisoned after all threads
- * have quiesced.
+ * 'rule' is a list head, and the caller is responsible for maintaining its
+ * list head pointer (if any).
  *
  * Afterward, the removed rule is not linked to any more, but still links to
  * the following rules, and may still be referenced by other threads until all
@@ -216,13 +204,8 @@ static inline void
 cls_match_remove(struct cls_match *prev, struct cls_match *rule)
 {
     if (prev) {
-        struct cls_match *next = cls_match_next_protected(rule);
-
-        ovsrcu_set(&prev->next, next);
+        ovsrcu_set(&prev->next, cls_match_next_protected(rule));
     }
-#ifndef NDEBUG
-    ovsrcu_postpone(cls_match_poison, rule);
-#endif
 }
 
 #define CLS_MATCH_FOR_EACH(ITER, HEAD)                              \
diff --git a/lib/classifier.c b/lib/classifier.c
index fad384c..28121bf 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -719,7 +719,7 @@ classifier_replace(struct classifier *cls, const struct 
cls_rule *rule,
                     ovsrcu_postpone(free, conj_set);
                 }
 
-                ovsrcu_postpone(free, iter);
+                ovsrcu_postpone(cls_match_free_cb, iter);
                 old->cls_match = NULL;
 
                 /* No change in subtable's max priority or max count. */
@@ -925,7 +925,7 @@ check_priority:
     if (conj_set) {
         ovsrcu_postpone(free, conj_set);
     }
-    ovsrcu_postpone(free, rule);
+    ovsrcu_postpone(cls_match_free_cb, rule);
     cls->n_rules--;
 
     return cls_rule;
@@ -2364,7 +2364,8 @@ trie_remove_prefix(rcu_trie_ptr *root, const ovs_be32 
*prefix, int mlen)
 #define CLS_MATCH_POISON (struct cls_match *)(UINTPTR_MAX / 0xf * 0xb)
 
 void
-cls_match_poison(struct cls_match *rule)
+cls_match_free_cb(struct cls_match *rule)
 {
     ovsrcu_set_hidden(&rule->next, CLS_MATCH_POISON);
+    free(rule);
 }

> 
> Acked-by: Ben Pfaff <b...@nicira.com>

Thanks, pushed to master,

  Jarno

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to