On Sat, Aug 12, 2017 at 09:50 +0300, Atanas Vladimirov wrote:
> Hi,
> 
> My home server (-current) freeze with kernel panic and I have to reboot it
> because no keyboard or IPMI working.
> 
> panic: kernel diagnostic assertion "TAILQ_EMPTY(&cl->cl_actc)" failed: file
> "/usr/src/sys/net/hfsc.c", line 767
> Stopped at    db_enter+0x5:   popq    %rbp
>     TID           PID    UID    PRFLAGS    PFLAGS    CPU    COMMAND
> *395466          8691      0        0x2         0      0    pfctl
> 
> I managed to take a screenshot (send as attachment).
> 
> Recently a add the following to pf.conf to test flows:
> 
> ....
> ### Queueing
> 
>  queue hfs-fq on $ExtIf flows 1024 bandwidth 40M max 45M quantum 400 qlimit
> 1000 default
> ....
> 

Hi,

I've finally managed to figure out what's going on.

Turns out that when hfsc_deq_begin would fail to dequeue from the
fq_codel queue (that's normal) it would mark the class as passive.
Except it won't.  The correct procedure is to do what _deq_commit
does which *effectively* boils down to this one line diff:

diff --git sys/net/hfsc.c sys/net/hfsc.c
index 3c5b6f6ef78..2b349e3cb52 100644
--- sys/net/hfsc.c
+++ sys/net/hfsc.c
@@ -883,10 +883,11 @@ hfsc_deq_begin(struct ifqueue *ifq, void **cookiep)
 
        m = hfsc_class_deq_begin(cl, &free_ml);
        ifq_mfreeml(ifq, &free_ml);
        if (m == NULL) {
                /* the class becomes passive */
+               hfsc_update_vf(cl, 0, cur_time);
                hfsc_set_passive(hif, cl);
                return (NULL);
        }
 
        hif->hif_microtime = cur_time;


What hfsc_update_vf does in this case (when queue is empty) is
it removes the class from it's parents active child class list.
Which is precisely what that KASSERT asserts during purging:
there must be no active child classes if the queue is empty.

However this doesn't sit very well with me since it's not 100%
complete and I'd like to factor out the whole procedure (not
just what it boils down to) out of hfsc_deq_commit into a
separate function hfsc_update_sc (sc for the service curve).

I believe this makes it more obvious what's going on and at
the same time I'm adding (or rather reverting) this bit:

    -           m0 = hfsc_class_deq_begin(cl, &free_ml);
    -           ifq_mfreeml(ifq, &free_ml);
    +           KASSERT(cl->cl_qops == pfq_hfsc_ops);
    +           m0 = MBUF_LIST_FIRST(&cl->cl_q.q);

This is done for clarity reasons: the realtime curve can't
be used with fq_codel since it needs to know what packet will
come next and in case of fq_codel we don't know that before
we attempt to dequeue.  Initially I decided to use the newly
established API here as well, but now I think it's largely
detrimental to the overall understanding of what's going on
so I'm back-pedalling on it (plus it saves me passing the ifq
pointer for the freelist that we don't need -- it was there
only to satisfy the API).

OK?

diff --git sys/net/hfsc.c sys/net/hfsc.c
index 3c5b6f6ef78..12504267dc5 100644
--- sys/net/hfsc.c
+++ sys/net/hfsc.c
@@ -206,10 +206,11 @@ int                        hfsc_class_destroy(struct 
hfsc_if *,
 struct hfsc_class      *hfsc_nextclass(struct hfsc_class *);
 
 void            hfsc_cl_purge(struct hfsc_if *, struct hfsc_class *,
                     struct mbuf_list *);
 
+void            hfsc_update_sc(struct hfsc_if *, struct hfsc_class *, int);
 void            hfsc_deferred(void *);
 void            hfsc_update_cfmin(struct hfsc_class *);
 void            hfsc_set_active(struct hfsc_if *, struct hfsc_class *, int);
 void            hfsc_set_passive(struct hfsc_if *, struct hfsc_class *);
 void            hfsc_init_ed(struct hfsc_if *, struct hfsc_class *, int);
@@ -882,12 +883,11 @@ hfsc_deq_begin(struct ifqueue *ifq, void **cookiep)
        }
 
        m = hfsc_class_deq_begin(cl, &free_ml);
        ifq_mfreeml(ifq, &free_ml);
        if (m == NULL) {
-               /* the class becomes passive */
-               hfsc_set_passive(hif, cl);
+               hfsc_update_sc(hif, cl, 0);
                return (NULL);
        }
 
        hif->hif_microtime = cur_time;
        *cookiep = cl;
@@ -895,39 +895,45 @@ hfsc_deq_begin(struct ifqueue *ifq, void **cookiep)
 }
 
 void
 hfsc_deq_commit(struct ifqueue *ifq, struct mbuf *m, void *cookie)
 {
-       struct mbuf_list free_ml = MBUF_LIST_INITIALIZER();
        struct hfsc_if *hif = ifq->ifq_q;
        struct hfsc_class *cl = cookie;
-       struct mbuf *m0;
+
+       hfsc_class_deq_commit(cl, m);
+       hfsc_update_sc(hif, cl, m->m_pkthdr.len);
+
+       PKTCNTR_INC(&cl->cl_stats.xmit_cnt, m->m_pkthdr.len);
+}
+
+void
+hfsc_update_sc(struct hfsc_if *hif, struct hfsc_class *cl, int len)
+{
        int next_len, realtime = 0;
        u_int64_t cur_time = hif->hif_microtime;
 
        /* check if the class was scheduled by real-time criteria */
        if (cl->cl_rsc != NULL)
                realtime = (cl->cl_e <= cur_time);
 
-       hfsc_class_deq_commit(cl, m);
-
-       PKTCNTR_INC(&cl->cl_stats.xmit_cnt, m->m_pkthdr.len);
-
-       hfsc_update_vf(cl, m->m_pkthdr.len, cur_time);
+       hfsc_update_vf(cl, len, cur_time);
        if (realtime)
-               cl->cl_cumul += m->m_pkthdr.len;
+               cl->cl_cumul += len;
 
        if (hfsc_class_qlength(cl) > 0) {
                /*
                 * Realtime queue needs to look into the future and make
                 * calculations based on that. This is the reason it can't
                 * be used with an external queue manager.
                 */
                if (cl->cl_rsc != NULL) {
+                       struct mbuf *m0;
+
                        /* update ed */
-                       m0 = hfsc_class_deq_begin(cl, &free_ml);
-                       ifq_mfreeml(ifq, &free_ml);
+                       KASSERT(cl->cl_qops == pfq_hfsc_ops);
+                       m0 = MBUF_LIST_FIRST(&cl->cl_q.q);
                        next_len = m0->m_pkthdr.len;
 
                        if (realtime)
                                hfsc_update_ed(hif, cl, next_len);
                        else

Reply via email to