aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2006-11-29 20:35:48 -0500
committerDavid S. Miller <davem@sunset.davemloft.net>2006-12-03 00:31:42 -0500
commit43effa1e57fc4635e0301b27d78f9d83afe78974 (patch)
treedc4a22fdaad074a547438050fb428f32b1cb2ded
parent9f9afec48221fe4a19f84a9341f5b304bf7d7783 (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.h2
-rw-r--r--net/sched/sch_api.c38
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);
172extern void dev_deactivate(struct net_device *dev); 173extern void dev_deactivate(struct net_device *dev);
173extern void qdisc_reset(struct Qdisc *qdisc); 174extern void qdisc_reset(struct Qdisc *qdisc);
174extern void qdisc_destroy(struct Qdisc *qdisc); 175extern void qdisc_destroy(struct Qdisc *qdisc);
176extern void qdisc_tree_decrease_qlen(struct Qdisc *qdisc, unsigned int n);
175extern struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops); 177extern struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops);
176extern struct Qdisc *qdisc_create_dflt(struct net_device *dev, 178extern 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
194struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle) 194static 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
205struct 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
209static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid) 215static 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
357void 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}
376EXPORT_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".