[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>
This commit is contained in:
committed by
David S. Miller
parent
9f9afec482
commit
43effa1e57
@ -191,19 +191,25 @@ int unregister_qdisc(struct Qdisc_ops *qops)
|
||||
(root qdisc, all its children, children of children etc.)
|
||||
*/
|
||||
|
||||
static struct Qdisc *__qdisc_lookup(struct net_device *dev, u32 handle)
|
||||
{
|
||||
struct Qdisc *q;
|
||||
|
||||
list_for_each_entry(q, &dev->qdisc_list, list) {
|
||||
if (q->handle == handle)
|
||||
return q;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)
|
||||
{
|
||||
struct Qdisc *q;
|
||||
|
||||
read_lock(&qdisc_tree_lock);
|
||||
list_for_each_entry(q, &dev->qdisc_list, list) {
|
||||
if (q->handle == handle) {
|
||||
read_unlock(&qdisc_tree_lock);
|
||||
return q;
|
||||
}
|
||||
}
|
||||
q = __qdisc_lookup(dev, handle);
|
||||
read_unlock(&qdisc_tree_lock);
|
||||
return NULL;
|
||||
return q;
|
||||
}
|
||||
|
||||
static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid)
|
||||
@ -348,6 +354,26 @@ dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc)
|
||||
return oqdisc;
|
||||
}
|
||||
|
||||
void qdisc_tree_decrease_qlen(struct Qdisc *sch, unsigned int n)
|
||||
{
|
||||
struct Qdisc_class_ops *cops;
|
||||
unsigned long cl;
|
||||
u32 parentid;
|
||||
|
||||
if (n == 0)
|
||||
return;
|
||||
while ((parentid = sch->parent)) {
|
||||
sch = __qdisc_lookup(sch->dev, TC_H_MAJ(parentid));
|
||||
cops = sch->ops->cl_ops;
|
||||
if (cops->qlen_notify) {
|
||||
cl = cops->get(sch, parentid);
|
||||
cops->qlen_notify(sch, cl);
|
||||
cops->put(sch, cl);
|
||||
}
|
||||
sch->q.qlen -= n;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(qdisc_tree_decrease_qlen);
|
||||
|
||||
/* Graft qdisc "new" to class "classid" of qdisc "parent" or
|
||||
to device "dev".
|
||||
|
Reference in New Issue
Block a user