Subject: Re: high input packet rate can lead to process starvation
To: None <tad@entrisphere.com>
From: YAMAMOTO Takashi <yamt@mwd.biglobe.ne.jp>
List: tech-net
Date: 09/23/2006 15:54:21
--NextPart-20060923152343-0172400
Content-Type: Text/Plain; charset=us-ascii

> As long as there are ip packets in the queue, ipintr() will continue 
> happily processing them.
> 
> This is really a problem not just with the ipintrq, but with all of the 
> software-interrupt protocol input routines.
> 
> My solution (this is a hack) is to put a time-limit on how long ipintr() 
> is allowed to run.  If it runs longer than this without emptying the 
> queue, I set a global "ipdisabled" flag and start a callout ticking that 
> will wakeup, clear the flag, and setsoftnet() (code attached).  In 
> addition the ethernet driver drops all packets as long as ipdisabled is 
> true.  (See the attachment for code)

it reminds me the attached code.
(i thought i've filed a PR but couldn't find a number.)
the idea was let a thread process the queue when it's somewhat busy.

YAMAMOTO Takashi

--NextPart-20060923152343-0172400
Content-Type: Text/Plain; charset=us-ascii
Content-Disposition: attachment; filename="netisr.c"


#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/proc.h>

#include <net/if.h>
#include <net/netisr.h>

#if 1
/* XXX should be per netisr */
#define NETISR_COUNTER_DEFINE(name)   \
struct evcnt netisr_stat_##name       \
    = EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "netisr", #name); \
EVCNT_ATTACH_STATIC(netisr_stat_##name)
#define NETISR_COUNTER_INCL(name)     (netisr_stat_##name).ev_count++
#else
#define NETISR_COUNTER_DEFINE(name)   /* nothing */
#define NETISR_COUNTER_INCL(name)     /* nothing */
#endif

NETISR_COUNTER_DEFINE(sched);
NETISR_COUNTER_DEFINE(thread);
NETISR_COUNTER_DEFINE(intr);
NETISR_COUNTER_DEFINE(transit);
NETISR_COUNTER_DEFINE(yield);
NETISR_COUNTER_DEFINE(sleep);
NETISR_COUNTER_DEFINE(throttle);

static void netisr_create_thread(void *);
static void netisr_thread(void *);
static __inline void netisr_schedule(struct netisr *);
static __inline void netisr_wakeup_thread(struct netisr *);
static __inline boolean_t netisr_empty(const struct netisr *) __unused;
static __inline struct mbuf *netisr_dequeue(struct netisr *);

int netisr_dothrottle = 1;
int netisr_maxrun = 10240;

static __inline boolean_t
netisr_empty(const struct netisr *ni)
{

	return ni->ni_queue.ifq_len == 0;
}

static __inline void
netisr_wakeup_thread(struct netisr *ni)
{

	if (ni->ni_threadsleeping) {
		wakeup(ni);
	}
}

static __inline void
netisr_schedule(struct netisr *ni)
{

	NETISR_COUNTER_INCL(sched);

	if (__predict_false(ni->ni_usethread)) {
		netisr_wakeup_thread(ni);
	} else {
		schednetisr(ni->ni_isr);
	}
}

/*
 * netisr_enqueue:
 *
 * => called at splnet.
 */

int
netisr_enqueue(struct netisr *ni, struct mbuf *m)
{
	struct ifqueue *q = &ni->ni_queue;

	simple_lock(&ni->ni_lock);
	if (IF_QFULL(q) || ni->ni_throttle) {
		IF_DROP(q);
		if (!ni->ni_throttle && netisr_dothrottle) {
			ni->ni_throttle = TRUE;
			NETISR_COUNTER_INCL(throttle);
		}
		simple_unlock(&ni->ni_lock);
		m_freem(m);
		return ENOBUFS;
	}

	if (netisr_empty(ni)) {
		netisr_schedule(ni);
	}

	IF_ENQUEUE(q, m);

	simple_unlock(&ni->ni_lock);

	return 0;
}

/*
 * netisr_dequeue:
 *
 * => called with ni_lock locked.
 */

static __inline struct mbuf *
netisr_dequeue(struct netisr *ni)
{
	struct mbuf *m;

	IF_DEQUEUE(&ni->ni_queue, m);
	if (m == NULL) {
		ni->ni_throttle = FALSE;
	}

	return m;
}

void
netisr_init(struct netisr *ni, const char *name, int isr,
    void (*handler)(struct mbuf *), int maxqlen)
{
	struct ifqueue *q;

	q = &ni->ni_queue;
	memset(q, 0, sizeof(*q));
	q->ifq_maxlen = maxqlen;

	ni->ni_isr = isr;
	ni->ni_name = name;
	ni->ni_handler = handler;
	ni->ni_maxrun = 64; /* XXX */
	ni->ni_minrun = 16; /* XXX */
	ni->ni_usethread = FALSE;

	simple_lock_init(&ni->ni_lock);

	kthread_create(netisr_create_thread, ni);
}

static void
netisr_create_thread(void *arg)
{
	struct netisr *ni = arg;
	int error;

	error = kthread_create1(netisr_thread, ni, &ni->ni_proc, ni->ni_name);
	if (error) {
		panic("netisr_create_thread: %s: %d", ni->ni_name, error);
	}
}

/*
 * netisr_run:
 *
 * => called at splsoftnet.
 */

void
netisr_run(struct netisr *ni)
{
//	int rest = ni->ni_maxrun;
	int rest = netisr_maxrun;
	int s;

	while ((rest--) > 0) {
		struct mbuf *m;

		s = splnet();
		simple_lock(&ni->ni_lock);
		m = netisr_dequeue(ni);
		simple_unlock(&ni->ni_lock);
		splx(s);
		if (m == NULL) {
			return;
		}

		NETISR_COUNTER_INCL(intr);

		ni->ni_handler(m);
	}

	NETISR_COUNTER_INCL(transit);
	s = splnet();
	simple_lock(&ni->ni_lock);
	ni->ni_usethread = TRUE;
	netisr_wakeup_thread(ni);
	simple_unlock(&ni->ni_lock);
	splx(s);
}

static void
netisr_thread(void *arg)
{
	struct netisr *ni = arg;
	int s;

	s = splnet();
	simple_lock(&ni->ni_lock);
again:
	while (!ni->ni_usethread) {
		ni->ni_threadsleeping = TRUE;
		ltsleep(ni, PUSER, ni->ni_name, 0, &ni->ni_lock);
	}
	ni->ni_threadsleeping = FALSE;
	simple_unlock(&ni->ni_lock);
	splx(s);

	while (/* CONSTCOND */ 1) {
		struct mbuf *m;

		s = splnet();
		simple_lock(&ni->ni_lock);
		m = netisr_dequeue(ni);
		if (m == NULL) {
			break;
		}
		simple_unlock(&ni->ni_lock);
		splx(s);

		NETISR_COUNTER_INCL(thread);

		s = splsoftnet();
		ni->ni_handler(m);
		splx(s);

		if ((curcpu()->ci_schedstate.spc_flags & SPCF_SHOULDYIELD)) {
			NETISR_COUNTER_INCL(yield);
			preempt(1);
		}
	}

	NETISR_COUNTER_INCL(yield);
	ni->ni_usethread = FALSE;
	ni->ni_throttle = FALSE;
	goto again;
}

--NextPart-20060923152343-0172400--