diff options
author | Patrick McHardy <kaber@trash.net> | 2006-11-29 20:35:48 -0500 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-12-03 00:31:42 -0500 |
commit | 43effa1e57fc4635e0301b27d78f9d83afe78974 (patch) | |
tree | dc4a22fdaad074a547438050fb428f32b1cb2ded | |
parent | 9f9afec48221fe4a19f84a9341f5b304bf7d7783 (diff) |
[NET_SCHED]: Fix endless loops caused by inaccurate qlen counters (part 1)
There are multiple problems related to qlen adjustment that can lead
to an upper qdisc getting out of sync with the real number of packets
queued, leading to endless dequeueing attempts by the upper layer code.
All qdiscs must maintain an accurate q.qlen counter. There are basically
two groups of operations affecting the qlen: operations that propagate
down the tree (enqueue, dequeue, requeue, drop, reset) beginning at the
root qdisc and operations only affecting a subtree or single qdisc
(change, graft, delete class). Since qlen changes during operations from
the second group don't propagate to ancestor qdiscs, their qlen values
become desynchronized.
This patch adds a function to propagate qlen changes up the qdisc tree,
optionally calling a callback function to perform qdisc-internal
maintenance when the child qdisc becomes empty. The follow-up patches
will convert all qdiscs to use this function where necessary.
Noticed by Timo Steinbach <tsteinbach@astaro.com>.
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/sch_generic.h | 2 | ||||
-rw-r--r-- | net/sched/sch_api.c | 38 |
2 files changed, 34 insertions, 6 deletions
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index b37572db12ab..82086392735a 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h | |||
@@ -60,6 +60,7 @@ struct Qdisc_class_ops | |||
60 | int (*graft)(struct Qdisc *, unsigned long cl, | 60 | int (*graft)(struct Qdisc *, unsigned long cl, |
61 | struct Qdisc *, struct Qdisc **); | 61 | struct Qdisc *, struct Qdisc **); |
62 | struct Qdisc * (*leaf)(struct Qdisc *, unsigned long cl); | 62 | struct Qdisc * (*leaf)(struct Qdisc *, unsigned long cl); |
63 | void (*qlen_notify)(struct Qdisc *, unsigned long); | ||
63 | 64 | ||
64 | /* Class manipulation routines */ | 65 | /* Class manipulation routines */ |
65 | unsigned long (*get)(struct Qdisc *, u32 classid); | 66 | unsigned long (*get)(struct Qdisc *, u32 classid); |
@@ -172,6 +173,7 @@ extern void dev_activate(struct net_device *dev); | |||
172 | extern void dev_deactivate(struct net_device *dev); | 173 | extern void dev_deactivate(struct net_device *dev); |
173 | extern void qdisc_reset(struct Qdisc *qdisc); | 174 | extern void qdisc_reset(struct Qdisc *qdisc); |
174 | extern void qdisc_destroy(struct Qdisc *qdisc); | 175 | extern void qdisc_destroy(struct Qdisc *qdisc); |
176 | extern void qdisc_tree_decrease_qlen(struct Qdisc *qdisc, unsigned int n); | ||
175 | extern struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops); | 177 | extern struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops); |
176 | extern struct Qdisc *qdisc_create_dflt(struct net_device *dev, | 178 | extern struct Qdisc *qdisc_create_dflt(struct net_device *dev, |
177 | struct Qdisc_ops *ops, u32 parentid); | 179 | struct Qdisc_ops *ops, u32 parentid); |
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index d99802f3023d..05a93da6f057 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c | |||
@@ -191,21 +191,27 @@ int unregister_qdisc(struct Qdisc_ops *qops) | |||
191 | (root qdisc, all its children, children of children etc.) | 191 | (root qdisc, all its children, children of children etc.) |
192 | */ | 192 | */ |
193 | 193 | ||
194 | struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle) | 194 | static struct Qdisc *__qdisc_lookup(struct net_device *dev, u32 handle) |
195 | { | 195 | { |
196 | struct Qdisc *q; | 196 | struct Qdisc *q; |
197 | 197 | ||
198 | read_lock(&qdisc_tree_lock); | ||
199 | list_for_each_entry(q, &dev->qdisc_list, list) { | 198 | list_for_each_entry(q, &dev->qdisc_list, list) { |
200 | if (q->handle == handle) { | 199 | if (q->handle == handle) |
201 | read_unlock(&qdisc_tree_lock); | ||
202 | return q; | 200 | return q; |
203 | } | ||
204 | } | 201 | } |
205 | read_unlock(&qdisc_tree_lock); | ||
206 | return NULL; | 202 | return NULL; |
207 | } | 203 | } |
208 | 204 | ||
205 | struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle) | ||
206 | { | ||
207 | struct Qdisc *q; | ||
208 | |||
209 | read_lock(&qdisc_tree_lock); | ||
210 | q = __qdisc_lookup(dev, handle); | ||
211 | read_unlock(&qdisc_tree_lock); | ||
212 | return q; | ||
213 | } | ||
214 | |||
209 | static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid) | 215 | static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid) |
210 | { | 216 | { |
211 | unsigned long cl; | 217 | unsigned long cl; |
@@ -348,6 +354,26 @@ dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc) | |||
348 | return oqdisc; | 354 | return oqdisc; |
349 | } | 355 | } |
350 | 356 | ||
357 | void qdisc_tree_decrease_qlen(struct Qdisc *sch, unsigned int n) | ||
358 | { | ||
359 | struct Qdisc_class_ops *cops; | ||
360 | unsigned long cl; | ||
361 | u32 parentid; | ||
362 | |||
363 | if (n == 0) | ||
364 | return; | ||
365 | while ((parentid = sch->parent)) { | ||
366 | sch = __qdisc_lookup(sch->dev, TC_H_MAJ(parentid)); | ||
367 | cops = sch->ops->cl_ops; | ||
368 | if (cops->qlen_notify) { | ||
369 | cl = cops->get(sch, parentid); | ||
370 | cops->qlen_notify(sch, cl); | ||
371 | cops->put(sch, cl); | ||
372 | } | ||
373 | sch->q.qlen -= n; | ||
374 | } | ||
375 | } | ||
376 | EXPORT_SYMBOL(qdisc_tree_decrease_qlen); | ||
351 | 377 | ||
352 | /* Graft qdisc "new" to class "classid" of qdisc "parent" or | 378 | /* Graft qdisc "new" to class "classid" of qdisc "parent" or |
353 | to device "dev". | 379 | to device "dev". |