static void *
vwe_thread(void *priv)
{
	struct epoll_event ev[NEEV], *ep;
	struct sess *sp;
	char junk;
	double now, deadline;
	int dotimer, i, n;
	struct vwe *vwe;

	CAST_OBJ_NOTNULL(vwe, priv, VWE_MAGIC);

	THR_SetName("cache-epoll");

	vwe->epfd = epoll_create(1);
	assert(vwe->epfd >= 0);

	vwe_modadd(vwe, vwe->pipes[0], vwe->pipes, EPOLL_CTL_ADD);
	vwe_modadd(vwe, vwe->timer_pipes[0], vwe->timer_pipes, EPOLL_CTL_ADD);

	while (1) {
		dotimer = 0;
		n = epoll_wait(vwe->epfd, ev, NEEV, -1);
		now = VTIM_real();
		for (ep = ev, i = 0; i < n; i++, ep++) {
			if (ep->data.ptr == vwe->timer_pipes &&
			    (ep->events == EPOLLIN || ep->events == EPOLLPRI))
			{
				assert(read(vwe->timer_pipes[0], &junk, 1));
				dotimer = 1;
			} else
				vwe_eev(vwe, ep, now);
		}
		if (!dotimer)
			continue;

		/* check for timeouts */
		deadline = now - cache_param->timeout_idle;
		for (;;) {
			sp = VTAILQ_FIRST(&vwe->sesshead);
			if (sp == NULL)
				break;
			if (sp->t_idle > deadline)
				break;
			VTAILQ_REMOVE(&vwe->sesshead, sp, list);
			// XXX: not yet VTCP_linger(sp->fd, 0);
			SES_Delete(sp, SC_RX_TIMEOUT, now);
		}
	}
	return (NULL);
}
static void
vwe_eev(struct vwe *vwe, const struct epoll_event *ep)
{
	struct sess *ss[NEEV], *sp;
	int i, j;

	AN(ep->data.ptr);
	if (ep->data.ptr == vwe->pipes) {
		if (ep->events & EPOLLIN || ep->events & EPOLLPRI) {
			j = 0;
			i = read(vwe->pipes[0], ss, sizeof ss);
			if (i == -1 && errno == EAGAIN)
				return;
			while (i >= sizeof ss[0]) {
				CHECK_OBJ_NOTNULL(ss[j], SESS_MAGIC);
				assert(ss[j]->fd >= 0);
				AZ(ss[j]->obj);
				VTAILQ_INSERT_TAIL(&vwe->sesshead, ss[j], list);
				vwe_cond_modadd(vwe, ss[j]->fd, ss[j]);
				j++;
				i -= sizeof ss[0];
			}
			assert(i == 0);
		}
	} else {
		CAST_OBJ_NOTNULL(sp, ep->data.ptr, SESS_MAGIC);
		if (ep->events & EPOLLIN || ep->events & EPOLLPRI) {
			i = HTC_Rx(sp->htc);
			if (i == 0) {
				vwe_modadd(vwe, sp->fd, sp, EPOLL_CTL_MOD);
				return;	/* more needed */
			}
			VTAILQ_REMOVE(&vwe->sesshead, sp, list);
			SES_Handle(sp, i);
		} else if (ep->events & EPOLLERR) {
			VTAILQ_REMOVE(&vwe->sesshead, sp, list);
			SES_Delete(sp, "ERR");
		} else if (ep->events & EPOLLHUP) {
			VTAILQ_REMOVE(&vwe->sesshead, sp, list);
			SES_Delete(sp, "HUP");
		} else if (ep->events & EPOLLRDHUP) {
			VTAILQ_REMOVE(&vwe->sesshead, sp, list);
			SES_Delete(sp, "RHUP");
		}
	}
}