This patch integrates insert_rule() to the sole caller
classifier_replace().  This makes classifer_replace() more symmetric
with classifier_remove(), and makes it easier to change the following:

- Rules invisible to the lookups are no longer inserted to any of the
  index cmaps or tries.
- subtable's 'n_rules' member is eliminated.

Signed-off-by: Jarno Rajahalme <jrajaha...@nicira.com>
---
 lib/classifier-private.h |    5 +-
 lib/classifier.c         |  365 ++++++++++++++++++++++++----------------------
 lib/rculist.h            |   13 +-
 tests/test-classifier.c  |    2 +-
 4 files changed, 197 insertions(+), 188 deletions(-)

diff --git a/lib/classifier-private.h b/lib/classifier-private.h
index a00c001..441aaff 100644
--- a/lib/classifier-private.h
+++ b/lib/classifier-private.h
@@ -31,11 +31,12 @@ struct cls_subtable {
     struct cmap_node cmap_node; /* Within struct classifier 'subtables_map'. */
 
     /* The fields are only used by writers. */
-    int n_rules OVS_GUARDED;                /* Number of rules, including
-                                             * duplicates. */
     int max_priority OVS_GUARDED;  /* Max priority of any rule in subtable. */
     unsigned int max_count OVS_GUARDED;     /* Count of max_priority rules. */
 
+    /* Identical, but lower priority rules are not inserted to any of the
+     * following data structures. */
+
     /* These fields are accessed by readers who care about wildcarding. */
     const tag_type tag;       /* Tag generated from mask for partitioning. */
     const uint8_t n_indices;                   /* How many indices to use. */
diff --git a/lib/classifier.c b/lib/classifier.c
index 82aaf3e..0e985c3 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -44,11 +44,11 @@ cls_match_alloc(struct cls_rule *rule)
         = xmalloc(sizeof *cls_match - sizeof cls_match->flow.inline_values
                   + MINIFLOW_VALUES_SIZE(count));
 
+    rculist_init(&cls_match->list);
     *CONST_CAST(const struct cls_rule **, &cls_match->cls_rule) = rule;
     *CONST_CAST(int *, &cls_match->priority) = rule->priority;
     miniflow_clone_inline(CONST_CAST(struct miniflow *, &cls_match->flow),
                           &rule->match.flow, count);
-    rule->cls_match = cls_match;
 
     return cls_match;
 }
@@ -60,9 +60,6 @@ static struct cls_subtable *insert_subtable(struct classifier 
*cls,
     OVS_REQUIRES(cls->mutex);
 static void destroy_subtable(struct classifier *cls, struct cls_subtable *)
     OVS_REQUIRES(cls->mutex);
-static struct cls_match *insert_rule(struct classifier *cls,
-                                     struct cls_subtable *, struct cls_rule *)
-    OVS_REQUIRES(cls->mutex);
 
 static const struct cls_match *find_match_wc(const struct cls_subtable *,
                                              const struct flow *,
@@ -388,11 +385,7 @@ trie_init(struct classifier *cls, int trie_idx, const 
struct mf_field *field)
             struct cls_match *head;
 
             CMAP_FOR_EACH (head, cmap_node, &subtable->rules) {
-                struct cls_match *match;
-
-                FOR_EACH_RULE_IN_LIST_PROTECTED (match, head) {
-                    trie_insert(trie, match->cls_rule, plen);
-                }
+                trie_insert(trie, head->cls_rule, plen);
             }
         }
         /* Initialize subtable's prefix length on this field.  This will
@@ -466,47 +459,85 @@ static inline ovs_be32 minimatch_get_ports(const struct 
minimatch *match)
         & MINIFLOW_GET_BE32(&match->mask.masks, tp_src);
 }
 
+static void
+subtable_replace_head_rule(struct classifier *cls OVS_UNUSED,
+                           struct cls_subtable *subtable,
+                           struct cls_match *head, struct cls_match *new,
+                           uint32_t hash, uint32_t ihash[CLS_MAX_INDICES])
+    OVS_REQUIRES(cls->mutex)
+{
+    /* Rule's data is already in the tries. */
+
+    new->partition = head->partition; /* Steal partition, if any. */
+    head->partition = NULL;
+
+    for (int i = 0; i < subtable->n_indices; i++) {
+        cmap_replace(&subtable->indices[i], &head->index_nodes[i],
+                     &new->index_nodes[i], ihash[i]);
+    }
+    cmap_replace(&subtable->rules, &head->cmap_node, &new->cmap_node, hash);
+}
+
 /* Inserts 'rule' into 'cls'.  Until 'rule' is removed from 'cls', the caller
  * must not modify or free it.
  *
  * If 'cls' already contains an identical rule (including wildcards, values of
  * fixed fields, and priority), replaces the old rule by 'rule' and returns the
  * rule that was replaced.  The caller takes ownership of the returned rule and
- * is thus responsible for destroying it with cls_rule_destroy(), freeing the
- * memory block in which it resides, etc., as necessary.
+ * is thus responsible for destroying it with cls_rule_destroy(), after RCU
+ * grace period has passed (see ovsrcu_postpone()).
  *
  * Returns NULL if 'cls' does not contain a rule with an identical key, after
  * inserting the new rule.  In this case, no rules are displaced by the new
  * rule, even rules that cannot have any effect because the new rule matches a
- * superset of their flows and has higher priority. */
+ * superset of their flows and has higher priority.
+ *
+ * As the readers are operating concurrently with the modifications, a
+ * concurrent reader may or may not see the new rule, depending on how the
+ * concurrent events overlap with each other.
+ *
+ * The new rule is first added to the segment indices, so the readers may find
+ * the rule in the indices before the rule is visible in the subtables 'rules'
+ * map.  This may result in us losing the opportunity to quit lookups earlier,
+ * resulting in sub-optimal wildcarding.  This will be fixed by forthcoming
+ * revalidation always scheduled after flow table changes.
+ *
+ * The subtable's max priority is updated only after the rule is inserted, so
+ * the concurrent readers may not see the rule, as the updated priority ordered
+ * subtable list will only be visible after the subtable's max priority is
+ * updated. */
 const struct cls_rule *
 classifier_replace(struct classifier *cls, struct cls_rule *rule)
     OVS_EXCLUDED(cls->mutex)
 {
-    struct cls_match *old_rule;
     struct cls_subtable *subtable;
-    const struct cls_rule *old_cls_rule = NULL;
+    struct cls_match *head;
+    int i;
+    uint32_t basis = 0, hash, ihash[CLS_MAX_INDICES];
+    uint8_t prev_be32ofs = 0;
+    size_t n_rules = 0;
+    struct cls_match *new = cls_match_alloc(rule);
 
     ovs_mutex_lock(&cls->mutex);
+    rule->cls_match = new;
+
     subtable = find_subtable(cls, &rule->match.mask);
     if (!subtable) {
         subtable = insert_subtable(cls, &rule->match.mask);
     }
 
-    old_rule = insert_rule(cls, subtable, rule);
-    if (!old_rule) {
-        old_cls_rule = NULL;
-
-        rule->cls_match->partition = NULL;
-        if (minimask_get_metadata_mask(&rule->match.mask) == OVS_BE64_MAX) {
-            ovs_be64 metadata = miniflow_get_metadata(&rule->match.flow);
-            rule->cls_match->partition = create_partition(cls, subtable,
-                                                          metadata);
-        }
-
-        cls->n_rules++;
+    /* Compute hashes in segments. */
+    for (i = 0; i < subtable->n_indices; i++) {
+        ihash[i] = minimatch_hash_range(&rule->match, prev_be32ofs,
+                                        subtable->index_ofs[i], &basis);
+        prev_be32ofs = subtable->index_ofs[i];
+    }
+    hash = minimatch_hash_range(&rule->match, prev_be32ofs, FLOW_U32S, &basis);
 
-        for (int i = 0; i < cls->n_tries; i++) {
+    head = find_equal(subtable, &rule->match.flow, hash);
+    if (!head) {
+        /* Tries. */
+        for (i = 0; i < cls->n_tries; i++) {
             if (subtable->trie_plen[i]) {
                 trie_insert(&cls->tries[i], rule, subtable->trie_plen[i]);
             }
@@ -523,17 +554,83 @@ classifier_replace(struct classifier *cls, struct 
cls_rule *rule)
             trie_insert_prefix(&subtable->ports_trie, &masked_ports,
                                subtable->ports_mask_len);
         }
-    } else {
-        old_cls_rule = old_rule->cls_rule;
-        rule->cls_match->partition = old_rule->partition;
-        CONST_CAST(struct cls_rule *, old_cls_rule)->cls_match = NULL;
 
-        /* 'old_rule' contains a cmap_node, which may not be freed
-         * immediately. */
-        ovsrcu_postpone(free, old_rule);
+        new->partition = NULL;
+        if (minimask_get_metadata_mask(&rule->match.mask) == OVS_BE64_MAX) {
+            ovs_be64 metadata = miniflow_get_metadata(&rule->match.flow);
+
+            new->partition = create_partition(cls, subtable, metadata);
+        }
+
+        /* Make rule visible to lookups. */
+        for (i = 0; i < subtable->n_indices; i++) {
+            cmap_insert(&subtable->indices[i], &new->index_nodes[i], ihash[i]);
+        }
+        n_rules = cmap_insert(&subtable->rules, &new->cmap_node, hash);
+
+    } else {   /* Equal rules exist in the classifier already. */
+        struct cls_match *iter;
+        struct cls_rule *old = NULL;
+
+        /* Scan the list for the insertion point that will keep the list in
+         * order of decreasing priority. */
+        FOR_EACH_RULE_IN_LIST_PROTECTED (iter, head) {
+            if (rule->priority >= iter->priority) {
+                if (rule->priority == iter->priority) {
+                    old = CONST_CAST(struct cls_rule *, iter->cls_rule);
+                }
+                break;
+            }
+        }
+
+        /* 'iter' now at the insertion point or NULL it at end. */
+        if (old) {
+            rculist_replace(&new->list, &iter->list);
+        } else {
+            if (!iter) {
+                rculist_push_back(&head->list, &new->list);
+            } else {
+                /* Insert 'rule' right before 'iter'. */
+                rculist_insert(&iter->list, &new->list);
+            }
+        }
+
+        /* Replace the existing head in data structures, if rule is the new
+         * head. */
+        if (iter == head) {
+            subtable_replace_head_rule(cls, subtable, head, new, hash, ihash);
+        }
+
+        if (old) {
+            ovsrcu_postpone(free, iter);
+            old->cls_match = NULL;
+
+            /* No change in subtable's max priority or max count. */
+
+            /* Return displaced rule.  Caller is responsible for keeping it
+             * around until all threads quiesce. */
+            ovs_mutex_unlock(&cls->mutex);
+            return old;
+        }
     }
+
+    /* Update 'subtable's 'max_priority' and 'max_count', if necessary. */
+    if (n_rules == 1) {
+        subtable->max_priority = rule->priority;
+        subtable->max_count = 1;
+        pvector_insert(&cls->subtables, subtable, rule->priority);
+    } else if (rule->priority == subtable->max_priority) {
+        ++subtable->max_count;
+    } else if (rule->priority > subtable->max_priority) {
+        subtable->max_priority = rule->priority;
+        subtable->max_count = 1;
+        pvector_change_priority(&cls->subtables, subtable, rule->priority);
+    }
+
+    /* Nothing was replaced. */
+    cls->n_rules++;
     ovs_mutex_unlock(&cls->mutex);
-    return old_cls_rule;
+    return NULL;
 }
 
 /* Inserts 'rule' into 'cls'.  Until 'rule' is removed from 'cls', the caller
@@ -563,11 +660,13 @@ classifier_remove(struct classifier *cls, const struct 
cls_rule *rule)
 {
     struct cls_partition *partition;
     struct cls_match *cls_match;
-    struct cls_match *head;
     struct cls_subtable *subtable;
+    struct cls_match *prev;
+    struct cls_match *next;
     int i;
     uint32_t basis = 0, hash, ihash[CLS_MAX_INDICES];
     uint8_t prev_be32ofs = 0;
+    size_t n_rules;
 
     ovs_mutex_lock(&cls->mutex);
     cls_match = rule->cls_match;
@@ -575,10 +674,43 @@ classifier_remove(struct classifier *cls, const struct 
cls_rule *rule)
         rule = NULL;
         goto unlock; /* Already removed. */
     }
+    /* Mark as removed. */
+    CONST_CAST(struct cls_rule *, rule)->cls_match = NULL;
+
+    INIT_CONTAINER(prev, rculist_back_protected(&cls_match->list), list);
+    INIT_CONTAINER(next, rculist_next(&cls_match->list), list);
+
+    /* Remove from the list of equal rules. */
+    rculist_remove(&cls_match->list);
+
+    /* Check if this is NOT a head rule. */
+    if (prev->priority > rule->priority) {
+        /* Not the highest priority rule, no need to check subtable's
+         * 'max_priority'. */
+        goto free;
+    }
 
     subtable = find_subtable(cls, &rule->match.mask);
     ovs_assert(subtable);
 
+    for (i = 0; i < subtable->n_indices; i++) {
+        ihash[i] = minimatch_hash_range(&rule->match, prev_be32ofs,
+                                        subtable->index_ofs[i], &basis);
+        prev_be32ofs = subtable->index_ofs[i];
+    }
+    hash = minimatch_hash_range(&rule->match, prev_be32ofs, FLOW_U32S, &basis);
+
+    /* Head rule.  Check if 'next' is an identical, lower-priority rule that
+     * will replace 'rule' in the data structures. */
+    if (next->priority < rule->priority) {
+        subtable_replace_head_rule(cls, subtable, cls_match, next, hash,
+                                   ihash);
+        goto check_priority;
+    }
+
+    /* 'rule' is last of the kind in the classifier, must remove from all the
+     * data structures. */
+
     if (subtable->ports_mask_len) {
         ovs_be32 masked_ports = minimatch_get_ports(&rule->match);
 
@@ -593,26 +725,10 @@ classifier_remove(struct classifier *cls, const struct 
cls_rule *rule)
 
     /* Remove rule node from indices. */
     for (i = 0; i < subtable->n_indices; i++) {
-        ihash[i] = minimatch_hash_range(&rule->match, prev_be32ofs,
-                                        subtable->index_ofs[i], &basis);
         cmap_remove(&subtable->indices[i], &cls_match->index_nodes[i],
                     ihash[i]);
-        prev_be32ofs = subtable->index_ofs[i];
-    }
-    hash = minimatch_hash_range(&rule->match, prev_be32ofs, FLOW_U32S, &basis);
-
-    head = find_equal(subtable, &rule->match.flow, hash);
-    if (head != cls_match) {
-        rculist_remove(&cls_match->list);
-    } else if (rculist_is_empty(&cls_match->list)) {
-        cmap_remove(&subtable->rules, &cls_match->cmap_node, hash);
-    } else {
-        struct cls_match *next = next_rule_in_list_protected(cls_match);
-
-        rculist_remove(&cls_match->list);
-        cmap_replace(&subtable->rules, &cls_match->cmap_node,
-                     &next->cmap_node, hash);
     }
+    n_rules = cmap_remove(&subtable->rules, &cls_match->cmap_node, hash);
 
     partition = cls_match->partition;
     if (partition) {
@@ -625,30 +741,31 @@ classifier_remove(struct classifier *cls, const struct 
cls_rule *rule)
         }
     }
 
-    if (--subtable->n_rules == 0) {
+    if (n_rules == 0) {
         destroy_subtable(cls, subtable);
-    } else if (subtable->max_priority == cls_match->priority
-               && --subtable->max_count == 0) {
-        /* Find the new 'max_priority' and 'max_count'. */
-        struct cls_match *head;
-        int max_priority = INT_MIN;
+    } else {
+check_priority:
+        if (subtable->max_priority == rule->priority
+            && --subtable->max_count == 0) {
+            /* Find the new 'max_priority' and 'max_count'. */
+            struct cls_match *head;
+            int max_priority = INT_MIN;
 
-        CMAP_FOR_EACH (head, cmap_node, &subtable->rules) {
-            if (head->priority > max_priority) {
-                max_priority = head->priority;
-                subtable->max_count = 1;
-            } else if (head->priority == max_priority) {
-                ++subtable->max_count;
+            CMAP_FOR_EACH (head, cmap_node, &subtable->rules) {
+                if (head->priority > max_priority) {
+                    max_priority = head->priority;
+                    subtable->max_count = 1;
+                } else if (head->priority == max_priority) {
+                    ++subtable->max_count;
+                }
             }
+            subtable->max_priority = max_priority;
+            pvector_change_priority(&cls->subtables, subtable, max_priority);
         }
-        subtable->max_priority = max_priority;
-        pvector_change_priority(&cls->subtables, subtable, max_priority);
     }
-
-    cls->n_rules--;
-
+free:
     ovsrcu_postpone(free, cls_match);
-    CONST_CAST(struct cls_rule *, rule)->cls_match = NULL;
+    cls->n_rules--;
 unlock:
     ovs_mutex_unlock(&cls->mutex);
 
@@ -1359,112 +1476,6 @@ find_equal(const struct cls_subtable *subtable, const 
struct miniflow *flow,
     }
     return NULL;
 }
-
-/*
- * As the readers are operating concurrently with the modifications, a
- * concurrent reader may or may not see the new rule, depending on how
- * the concurrent events overlap with each other.  This is no
- * different from the former locked behavior, but there the visibility
- * of the new rule only depended on the timing of the locking
- * functions.
- *
- * The new rule is first added to the segment indices, so the readers
- * may find the rule in the indices before the rule is visible in the
- * subtables 'rules' map.  This may result in us losing the
- * opportunity to quit lookups earlier, resulting in sub-optimal
- * wildcarding.  This will be fixed by forthcoming revalidation always
- * scheduled after flow table changes.
- *
- * Similar behavior may happen due to us removing the overlapping rule
- * (if any) from the indices only after the new rule has been added.
- *
- * The subtable's max priority is updated only after the rule is
- * inserted, so the concurrent readers may not see the rule, as the
- * updated priority ordered subtable list will only be visible after
- * the subtable's max priority is updated.
- *
- * Similarly, the classifier's partitions for new rules are updated by
- * the caller after this function, so the readers may keep skipping
- * the subtable until they see the updated partitions.
- */
-static struct cls_match *
-insert_rule(struct classifier *cls, struct cls_subtable *subtable,
-            struct cls_rule *new_rule)
-    OVS_REQUIRES(cls->mutex)
-{
-    struct cls_match *old = NULL;
-    struct cls_match *new = cls_match_alloc(new_rule);
-    struct cls_match *head;
-    int i;
-    uint32_t basis = 0, hash, ihash[CLS_MAX_INDICES];
-    uint8_t prev_be32ofs = 0;
-
-    /* Add new node to segment indices. */
-    for (i = 0; i < subtable->n_indices; i++) {
-        ihash[i] = minimatch_hash_range(&new_rule->match, prev_be32ofs,
-                                        subtable->index_ofs[i], &basis);
-        cmap_insert(&subtable->indices[i], &new->index_nodes[i], ihash[i]);
-        prev_be32ofs = subtable->index_ofs[i];
-    }
-    hash = minimatch_hash_range(&new_rule->match, prev_be32ofs, FLOW_U32S,
-                                &basis);
-    head = find_equal(subtable, &new_rule->match.flow, hash);
-    if (!head) {
-        cmap_insert(&subtable->rules, &new->cmap_node, hash);
-        rculist_init(&new->list);
-        goto out;
-    } else {
-        /* Scan the list for the insertion point that will keep the list in
-         * order of decreasing priority. */
-        struct cls_match *rule;
-
-        FOR_EACH_RULE_IN_LIST_PROTECTED (rule, head) {
-            if (new->priority >= rule->priority) {
-                if (rule == head) {
-                    /* 'new' is the new highest-priority flow in the list. */
-                    cmap_replace(&subtable->rules, &rule->cmap_node,
-                                 &new->cmap_node, hash);
-                }
-
-                if (new->priority == rule->priority) {
-                    rculist_replace(&new->list, &rule->list);
-                    old = rule;
-                } else {
-                    rculist_insert(&rule->list, &new->list);
-                }
-                goto out;
-            }
-        }
-
-        /* Insert 'new' at the end of the list. */
-        rculist_push_back(&head->list, &new->list);
-    }
-
- out:
-    if (!old) {
-        subtable->n_rules++;
-
-        /* Rule was added, not replaced.  Update 'subtable's 'max_priority'
-         * and 'max_count', if necessary. */
-        if (subtable->n_rules == 1) {
-            subtable->max_priority = new->priority;
-            subtable->max_count = 1;
-            pvector_insert(&cls->subtables, subtable, new->priority);
-        } else if (subtable->max_priority == new->priority) {
-            ++subtable->max_count;
-        } else if (new->priority > subtable->max_priority) {
-            subtable->max_priority = new->priority;
-            subtable->max_count = 1;
-            pvector_change_priority(&cls->subtables, subtable, new->priority);
-        }
-    } else {
-        /* Remove old node from indices. */
-        for (i = 0; i < subtable->n_indices; i++) {
-            cmap_remove(&subtable->indices[i], &old->index_nodes[i], ihash[i]);
-        }
-    }
-    return old;
-}
 
 /* A longest-prefix match tree. */
 
diff --git a/lib/rculist.h b/lib/rculist.h
index 2e926de..f597d84 100644
--- a/lib/rculist.h
+++ b/lib/rculist.h
@@ -250,7 +250,8 @@ rculist_move(struct rculist *dst, struct rculist *src)
 }
 
 /* Removes 'elem' from its list and returns the element that followed it.
- * Undefined behavior if 'elem' is not in a list.
+ * Has no effect when 'elem' is initialized, but not in a list.
+ * Undefined behavior if 'elem' is not initialized.
  *
  * Afterward, 'elem' is not linked to from the list any more, but still links
  * to the nodes in the list, and may still be referenced by other threads until
@@ -318,16 +319,12 @@ rculist_front(const struct rculist *list)
 }
 
 /* Returns the back element in 'list_'.
- * Undefined behavior if 'list_' is empty. */
+ * Returns the 'list_' itself, if 'list_' is empty. */
 static inline struct rculist *
-rculist_back_protected(const struct rculist *list_)
+rculist_back_protected(const struct rculist *list)
     OVS_NO_THREAD_SAFETY_ANALYSIS
 {
-    struct rculist *list = CONST_CAST(struct rculist *, list_);
-
-    ovs_assert(!rculist_is_empty(list));
-
-    return list->prev;
+    return CONST_CAST(struct rculist *, list)->prev;
 }
 
 /* Returns the number of elements in 'list'.
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index 2848d01..d42de06 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -546,7 +546,7 @@ check_tables(const struct classifier *cls, int n_tables, 
int n_rules,
 
         ovs_mutex_lock(&cls->mutex);
         assert(trie_verify(&table->ports_trie, 0, table->ports_mask_len)
-               == (table->ports_mask_len ? table->n_rules : 0));
+               == (table->ports_mask_len ? cmap_count(&table->rules) : 0));
         ovs_mutex_unlock(&cls->mutex);
 
         found_tables++;
-- 
1.7.10.4

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

Reply via email to