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