/*
 *	ZMailer 2.99.16+ Scheduler "threads" routines
 *
 *	Copyright Matti Aarnio <mea@nic.funet.fi> 1995
 *
 *
 *	These "threads" are for book-keeping of information
 *	regarding schedulable recipient vertices
 */

#include "scheduler.h"
#include "prototypes.h"
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>

/*
   Each vertex arrives into SOME thread, which each belong of
   SOME thread-group, among which groups the transport channel
   programs started for any member thread can be shared among
   their members.
*/


struct thread      *thread_head = NULL;
struct thread      *thread_tail = NULL;

static struct threadgroup *thrg_root   = NULL;
static int idleprocs = 0;

struct threadgroup *
create_threadgroup(cep, wc, wh, withhost)
struct config_entry *cep;
struct web *wc, *wh;
int withhost;
{
	struct threadgroup *thgp;

	/* Create a thread-group and link it into group-ring */

	thgp = (struct threadgroup*)malloc(sizeof(*thgp));
	if (!thgp) return NULL;
	memset(thgp,0,sizeof(*thgp));

	thgp->cep      = cep;
	thgp->withhost = withhost;
	thgp->wchan    = wc;
	thgp->whost    = wh;

	wc->linkcnt += 1;
	wh->linkcnt += 1;

	if (thrg_root == NULL) {
	  thrg_root = thgp;
	  thgp->next = thgp;
	  thgp->prev = thgp;
	} else {
	  thgp->next       = thrg_root->next;
	  thgp->prev       = thrg_root->next->prev;
	  thgp->prev->next = thgp;
	  thgp->next->prev = thgp;
	}

	return thgp;
}

void
delete_threadgroup(thgp)
struct threadgroup *thgp;
{
	struct threadgroup *tgp;

	/* Time to say good-bye to this group, delete it.
	   We shall not have any threads under us, nor
	   idle processes!				  */

if (verbose) printf("delete_threadgroup(%s/%d/%s)\n",
		    thgp->wchan->name,thgp->withhost,thgp->whost->name);

	if (thgp->idleproc != NULL ||
	    thgp->threads > 0) abort();
	if (thrg_root == NULL) abort();


	/* Are we last to keep these web links ? */

	thgp->wchan->linkcnt -= 1;
	if (thgp->wchan->linkcnt <= 0)
	  unweb(L_CHANNEL,thgp->wchan);

	thgp->whost->linkcnt -= 1;
	if (thgp->whost->linkcnt <= 0)
	  unweb(L_HOST,thgp->whost);

	/* Unlink this thread-group from the ring */
	tgp              = thgp->next;
	thgp->prev->next = thgp->next;
	tgp->prev        = thgp->prev;

	/* are we at the ring root pointer ? */
	if (thrg_root == thgp)
	  thrg_root = tgp;
	if (tgp == thgp) {
	  /* We were the only one! */
	  thrg_root = NULL;
	}
	free(thgp);
}


static void _thread_timechain_unlink(thr)
struct thread *thr;
{
	if (thr->prevtr != NULL)
	  thr->prevtr->nexttr = thr->nexttr;
	if (thr->nexttr != NULL)
	  thr->nexttr->prevtr = thr->prevtr;

	if (thread_head == thr)
	  thread_head = thr->nexttr;
	if (thread_tail == thr)
	  thread_tail = thr->prevtr;

	thr->nexttr = NULL;
	thr->prevtr = NULL;
}

static void _thread_timechain_append(thr)
struct thread *thr;
{
	if (thread_head == NULL) {
	  thread_head = thr;
	  thread_tail = thr;
	  thr->nexttr = NULL;
	  thr->prevtr = NULL;
	  return;
	}
	thread_tail->nexttr = thr;
	thr->nexttr = NULL;
	thr->prevtr = thread_tail;
	thread_tail = thr;
}

struct thread *
create_thread(thgrp, vtx)
struct threadgroup *thgrp;
struct vertex *vtx;
{
	/* Create a thread-block, link in the group pointer,
	   and link the thread into thread-ring, plus APPEND
	   to the thread time-chain				*/

	struct thread *thr;
	thr = (struct thread *)emalloc(sizeof(*thr));
	if (!thr) return NULL;

	memset(thr,0,sizeof(*thr));

	thr->thgrp = thgrp;
	thr->nextthg = thr;
	thr->prevthg = thr;

	if (thgrp->thread == NULL) {
	  thgrp->thread = thr;
	} else {
	  thr->nextthg = thgrp->thread->nextthg;
	  thr->prevthg = thgrp->thread->nextthg->prevthg;
	  thgrp->thread->nextthg->prevthg = thr;
	  thgrp->thread->nextthg          = thr;
	}
	thgrp->threads += 1;
	thr->vertices   = vtx;
	thr->lastvertex = vtx;
	thr->jobs       = 1;
	vtx->thread     = thr;
	if (verbose) {
	  thr->channel    = strdup(vtx->orig[L_CHANNEL]->name);
	  thr->host       = strdup(vtx->orig[L_HOST   ]->name);
	}

	_thread_timechain_append(thr);

	return thr;
}

void
delete_thread(thr)
struct thread *thr;
{
	/* Unlink this thread from thread-chain, and thread
	   group.  Decrement thread-group count		    */

	struct threadgroup *thg;

	if (verbose) {
	  printf("delete_thread(%s/%s)\n",thr->channel,thr->host);
	  free(thr->channel);
	  free(thr->host);
	}

	if (thr->jobs > 0) {
	  if (verbose)
	    printf(" DELETE_THREAD() WITH JOBS=%d\n",thr->jobs);
	  abort(); /* Delete only when no vertices */
	}

	/* Unlink us from the thread time-chain */
	_thread_timechain_unlink(thr);

	/* ... and now from thread-group-ring */
	thr->nextthg->prevthg = thr->prevthg;
	thr->prevthg->nextthg = thr->nextthg;

	thg = thr->thgrp;

	/* If we are the ring hookup, connect another */
	if (thg->thread == thr)
	  thg->thread = thr->nextthg;

	thg->threads -= 1;
	/* If threads count goes zero.. */
	if (thg->threads == 0) {
	  thg->thread = NULL;	/* Oops.. we were last!		*/
	  if (thr->proc &&
	      thr->proc->thread == thr) { /* There is a process, idle it! */
	    thr->proc->thread = NULL;
	    thr->proc->vertex = NULL;
	    thr->proc->next = thg->idleproc;
	    thg->idleproc   = thr->proc;
	    thg->idlecnt   += 1;
	    ++idleprocs;
	  }
	} else {
	  /* Some threads left						*/
	  /* If there is a process, pick another job..  It may be that
	     the process' thread is no longer in a ring!		*/
	  if (thr->proc &&
	      thr->proc->thread == thr) {
	    /* Set it into a new thread in our ring */
	    thr->proc->vertex = thg->thread->vertices;
	    thr->proc->thread = thg->thread;
	    pick_next_vertex(thr->proc);
	  }
	}

	free(thr);
}

static void _thread_linkfront(thr,ap,vp)
struct thread *thr;
struct vertex *ap, *vp;
{
	/* Link the VP in front of AP */
	vp->previtem = ap->previtem;
	if (ap->previtem != NULL)
	  ap->previtem->nextitem = vp;
	ap->previtem = vp;
	vp->nextitem = ap;
	if (ap == thr->vertices)
	  thr->vertices = vp;
}


/* the  _thread_linktail()  links a vertex into thread */
static void _thread_linktail(thr,vp)
struct thread *thr;
struct vertex *vp;
{
	if (thr->vertices != NULL) {
	  thr->lastvertex->nextitem = vp;
	  vp->previtem    = thr->lastvertex;
	} else {
	  thr->vertices   = vp;
	  vp->previtem    = NULL;
	}
	vp->nextitem    = NULL;
	vp->thread      = thr;
	thr->lastvertex = vp;
}

void thread_linkin(vp,cep,cfgid)
struct vertex *vp;
struct config_entry *cep;
int cfgid;
{
	struct threadgroup *thg;
	struct thread *thr;

	/* int matched = 0; */
	int thg_once = 1;

	struct web *wc = vp->orig[L_CHANNEL];
	struct web *wh = vp->orig[L_HOST];

if (verbose) printf("thread_linkin([%s/%s],...,%d)\n",wc->name,wh->name,cfgid);

	/* char const *vp_chan = wc->name; */
	/* char const *vp_host = wh->name; */

	if (thrg_root == NULL) {
	  thg = create_threadgroup(cep, wc, wh, cep->withhost);
	}
	/*
	 *  Search for matching config-entry, AND matching channel,
	 *  AND matching host (depending how the thread-group formation
	 *  is allowed to happen..)
	 *
	 */
	for (thg = thrg_root;
	     thg_once || thg != thrg_root;
	     thg = thg->next) {

	  int thr_once;

	  thg_once = 0;

	  if (thg->cep != cep)	/* Config entries don't match */
	    continue;

	  if (thg->wchan != wc)	/* Channels don't match */
	    continue;

	  if (thg->withhost) {
	    if (thg->whost != wh) /* Tough, must have host match! */
	      continue;
	  }
	  
	  /* The config-entry matches, we have changes to match group */

	  thr_once = 1;
	  for (thr = thg->thread;
	       thr != NULL && (thr_once || thr != thg->thread);
	       thr = thr->nextthg) {

	    struct vertex *vt = thr->vertices;

	    thr_once = 0;

	    if (!vt) abort();	/* No vertices ?? */

	    /* no need ? (Channels within a group are identical..) */
	    if (wc != vt->orig[L_CHANNEL])  abort();

	    /* Nice! What about host ? */
	    if (wh != vt->orig[L_HOST])     continue;
	    
	    /* We have matching channel, AND matching host */

	    /* Link the vertex into this thread! */
if (verbose) printf(" ... on thg=0x%lx[%s/%d/%s] added into existing thread [%s/%s] thr->jobs=%d\n",
		    (long)thg,cep->channel,thg->withhost,cep->host,
		    wc->name,wh->name,thr->jobs+1);

	    _thread_linktail(thr,vp);
	    thr->jobs += 1;
	    thread_start(thr);

	    return;
	  }
	  /* No matching thread, however this GROUP matches (or does it?) */

if (verbose) printf(" ... on thg=0x%lx[%s/%d/%s] created a new thread [%s/%s]\n",
		    (long)thg,cep->channel,thg->withhost,cep->host,
		    wc->name,wh->name);

	  /* Add a new thread into this group */
	  thr = create_thread(thg,vp);
	  thread_start(thr);
	  return;
	}

	/* Add a new group - and its thread .. */
	thg = create_threadgroup(cep, wc, wh, cep->withhost);
	thr = create_thread(thg,vp);

if (verbose) printf(" ... on thg=0x%lx[%s/%d/%s] created a new thread group, and thread [%s/%s]\n",
		    (long)thg,cep->channel,thg->withhost,cep->host,
		    wc->name,wh->name);

	thread_start(thr);
}

struct web *
web_findcreate(flag, s)
int flag;
u_char *s;
{
	struct spblk *spl;
	register u_char *cp;
	struct web *wp;

	for (cp = s; *cp != '\0'; ++cp)
	  if (isascii(*cp) && isupper(*cp))
	    *cp = tolower(*cp);

	spl = sp_lookup(symbol(s), spt_mesh[flag]);
	if (spl == NULL || (wp = (struct web *)spl->data) == NULL) {
	  /* Not found, create it */
	  wp = (struct web *)emalloc(sizeof (struct web));
	  memset((void*)wp, 0, sizeof (struct web));
	  sp_install(symbol(s), (u_char *)wp, 0, spt_mesh[flag]);
	  wp->name     = strsave((char *)s);
	  wp->kids     = 0;
	  wp->link     = NULL;
	  wp->linkcnt  = 0;
	}
	if (spl != NULL)
	  wp = (struct web*)spl->data;

	return wp;
}


/*
 * Deallocate a web entry (host or channel vertex header structure).
 */

void
unweb(flag, wp)
	int flag;
	struct web *wp;
{
	struct spblk *spl;

	wp->link = wp->lastlink = NULL;
	if (wp->kids != 0)
	  return;		/* too early to actually remove it */
	if (wp->linkcnt > 0)	/* Yet objects holding it */
	  return;
	spl = sp_lookup(symbol((u_char *)wp->name), spt_mesh[flag]);
	if (spl != NULL)
	  sp_delete(spl, spt_mesh[flag]);
	free(wp->name);
	free((char *)wp);
}


/*
 * unthread(vtx) -- detach this vertex from its thread
 */
void
unthread(vtx)
struct vertex *vtx;
{
	struct thread *thr = vtx->thread;

	thr->jobs -= 1;

	if (vtx->previtem != NULL)
	  vtx->previtem->nextitem = vtx->nextitem;
	if (vtx->nextitem != NULL)
	  vtx->nextitem->previtem = vtx->previtem;


	if (thr && thr->vertices == vtx)
	  thr->vertices   = vtx->nextitem;
	if (thr && thr->lastvertex == vtx)
	  thr->lastvertex = vtx->previtem;

	vtx->nextitem = NULL;
	vtx->previtem = NULL;
}


/*
 * Detach the vertex from its chains
 */
void
web_disentangle(vp)
	struct vertex *vp;
{
	/* If it was in processing, remove process node binding.
	   We do this only when we have reaped the channel program. */

	if (vp->proc != NULL) {
	  if (vp->proc->vertex == vp) {
	    /*
	       vp->proc->vertex = vp->nextitem;
	       if (vp->proc->vertex != NULL)
	       vp->proc->vertex->proc = vp->proc;
	     */
	    pick_next_vertex(vp->proc);
	  }
	  vp->proc = NULL;
	}

	unthread(vp);

	/* The thread can now be EMPTY! */

	if (vp->thread->vertices == NULL)
	  delete_thread(vp->thread);
}

void
thread_vertex_shuffle(thr)
struct thread *thr;
{
	register struct vertex *vp;
	register int n, i, ni;
	static u_int           ur_size = 0;
	static struct vertex **ur_arr  = NULL;

	/* Randomize the order of vertices in processing  */

	/* 1) count the number of elts -- changed so it will be done
	      while creating the storage array.. */

	/* 2) Create storage array for the vertex re-arrange */
	if (ur_size == 0) {
	  ur_size = 100;
	  ur_arr = (struct vertex **)
	    emalloc(ur_size * sizeof (struct vertex *));
	}
	/* 3) Store the vertices into a re-arrange array */
	for (i = 0, vp = thr->vertices; vp != NULL; ++i, vp = vp->nextitem) {
	  if (i >= ur_size) {
	    ur_size *= 2;
	    ur_arr = (struct vertex **)realloc((char *)ur_arr,
					       ur_size *
					       sizeof (struct vertex *));
	  }
	  ur_arr[i] = vp;
	}
	n = i; /* Save the count */
	/* 4) re-arrange pointers */
	for (i = 0; i < n; ++i) {
	  ni = ranny(n-1);
	  vp = ur_arr[i];
	  ur_arr[i] = ur_arr[ni];
	  ur_arr[ni] = vp;
	}
	/* 5) Relink previtem/nextitem pointers */
	for (i = 0; i < n; ++i) {
	  if (i > 0)
	    ur_arr[i]->previtem = ur_arr[i-1];
	  if (i < (n-1))
	    ur_arr[i]->nextitem = ur_arr[i+1];
	  /* 5b) Clear vertex proc pointer */
	  ur_arr[i]->proc = NULL;
	}
	ur_arr[  0]->previtem = NULL;
	ur_arr[n-1]->nextitem = NULL;
	/* 6) Finish the re-arrangement by saving the head,
	      and tail pointers */
	thr->vertices   = ur_arr[  0];
	thr->lastvertex = ur_arr[n-1];
}



/*
 * thread_start() -- start the thread, if:
 *   - if the thread is not already running
 *   - thread-group has idle processor (feed immediately)
 *   - if no resource limits are exceeded for starting it
 *
 * Return non-zero, if did start something.
 */

int
thread_start(thr)
struct thread *thr;
{
	int rc;
	struct vertex      *vp  = thr->vertices;
	struct threadgroup *thg = thr->thgrp;

	if (verbose)
	  fprintf(stderr,"thread_start(thr=%s/%d/%s) (dt=%d/%d)",
		  vp->orig[L_CHANNEL]->name, thg->withhost,
		  vp->orig[L_HOST]->name,
		  (int)(thr->wakeup-now),(int)(vp->wakeup-now));

	if (thr->proc != NULL &&
	    thr->proc->thread == thr) {
	  if (verbose) fprintf(stderr," -- already running\n");
	  return 0; /* Already running */
	} else
	  if (verbose) fprintf(stderr,"\n");

	if (thg->idleproc != NULL) {
	  struct procinfo *proc;

	  /* Idle processor(s) exists, take the first of them */
	  thr->proc     = thg->idleproc;
	  proc          = thr->proc;
	  thg->idleproc = proc->next;
	  thg->idlecnt -= 1;
	  --idleprocs;
	  proc->next    = NULL;

	  proc->thread  = thr;
	  proc->vertex  = thr->vertices;
	  /* Thread-groups are made such that here at thread_start() we
	     can always switch over in between threads */
	  proc->ch = thr->vertices->orig[L_CHANNEL];

	  /* Get rid of the old host web */
	  proc->ho->kids -= 1;
	  if (proc->ho->kids == 0 && proc->ho->link == NULL)
	    unweb(L_HOST,proc->ho);

	  /* Move the kid to this host web */
	  proc->ho = thr->vertices->orig[L_HOST];
	  proc->ho->kids += 1;

	  /* Clean vertices 'proc'-pointers,
	     Randomize the order of thread vertices */
	  thread_vertex_shuffle(thr);

	  /* Its idle process, feed it! */
	  feed_child(proc);
	  return 1;
	}

	/* Check resource limits - MaxTa, MaxChan, MaxThrGrpTa */
	/* If resources are exceeded, reschedule.. */

	vp = thr->vertices;

	if (numkids >= vp->ce.maxkids)
	  vp->ce.pending = SIZE_L;
	else if (vp->orig[L_CHANNEL]->kids >= vp->ce.maxkidChannel)
	  vp->ce.pending = L_CHANNEL;
	else if (thg->transporters >= vp->ce.maxkidThreads)
	  vp->ce.pending = L_HOST;
	else
	  vp->ce.pending = 0;

	if (vp->ce.pending) {
	  if (verbose)
	    printf("%s: (%d %dC %dT) >= (%d %dC %dT)\n",
		   vp->ce.command,
		   numkids,
		   vp->orig[L_CHANNEL]->kids,
		   thg->transporters,
		   vp->ce.maxkids,
		   vp->ce.maxkidChannel,
		   vp->ce.maxkidThreads);
	  /*
	   * Would go over limit.  Rescheduling for the next
	   * (single) interval works ok in many situation.
	   * However when the scheduler is very busy one can
	   * run into systemic problems with some set of messages
	   * blocking another set of messages.  The only way
	   * around that is a roundrobin scheme, implemented
	   * by the fifo nature of the thread scheduling.
	   */
	  reschedule(vp, 0, -1);
	  return 0;
	}

	/* Now we are ready to start a new child to run our bits */
	
	/* Clean vertices 'proc'-pointers,
	   Randomize the order of thread vertices */
	thread_vertex_shuffle(thr);

	rc = start_child(thr->vertices,
			 thr->vertices->orig[L_CHANNEL],
			 thr->vertices->orig[L_HOST]);

	if (rc)
	  thg->transporters += 1;

	return rc;
}


/*
 * pick_next_vertex() -- pick next free to process vertex in this thread
 *   - vertex proc pointer is non-null
 *   - if the thread exhausts, pick another thread (check our scheduling vars)
 *     if the thread has vertices, but they are marked (proc != NULL),
 *     reschedule the thread, and pick another thread (if exist)
 *   - if all of the threads exhaust, move the process into idle pool
 */
void
pick_next_vertex(proc)
struct procinfo *proc;
{
	struct thread      *thr, *thr0;
	struct threadgroup *thg;
	struct vertex *vtx;

	thr  = proc->thread;

if (verbose) printf("pick_next_vertex(proc->tofd=%d, thr=0x%lx)\n",
		    proc->tofd, (long)thr);

	if (thr == NULL) return; /* WE ARE IDLE! */

	thr0 = thr;
	thg  = thr->thgrp;
	vtx  = proc->vertex;

	/* Ok, if that one was/is busy/marked off, try next */
	while (vtx) {

	  /* Is the current one in processing ? */
	  if (vtx->proc == NULL) {
	    proc->vertex = vtx;
	    return; /* No, it is eligible! */
	  }

	  /* Pick next */
	  vtx = vtx->nextitem;
	}
	/* Umm.. All vertices on this thread used! */

	/* Pick another thread in our ring */
	thr = thr->nextthg;

	time(&now);

	/* the threads are in a ring.. */
	for ( ;thr != thr0; thr = thr->nextthg) {

	  if (thr->proc &&
	      thr->proc->thread == thr)
	    continue; /* in processing, don't touch! */

	  if (thr->wakeup > now && thr->attempts > 0)
	    continue; /* wakeup in future, unless first time around! */

	  thr->attempts += 1;

	  /* Ok, this thread isn't busy, pick the first vertex */

	  /* Get rid of the old host web */
	  proc->ho->kids -= 1;
	  if (proc->ho->kids == 0 && proc->ho->link == NULL)
	    unweb(L_HOST,proc->ho);

	  /* Clean vertices 'proc'-pointers,
	     Randomize the order of thread vertices */
	  thread_vertex_shuffle(thr);

	  proc->vertex = thr->vertices;
	  proc->thread = thr;
	  proc->ch = thr->vertices->orig[L_CHANNEL];

	  /* Move the kid to the new host web */
	  proc->ho = thr->vertices->orig[L_HOST];
	  proc->ho->kids += 1;
	  thr->proc = proc;
	  return; /* It is eligible to run! */
	}

	/* No free threads/vertices here, idle the process */
	proc->thread = NULL;
	proc->vertex = NULL;
	proc->next = thg->idleproc;
	thg->idleproc = proc;
	thg->idlecnt += 1;
	++idleprocs;
}

/*
 * The  thread_reschedule()  updates threads time-chain to match the
 * new value of wakeup for the  doagenda()  to latter use.
 */

void
thread_reschedule(thr)
struct thread *thr;
{
	/* XXX: Don't worry right now about  thread_reschedule().
	        Propably no need, as reschedule() does most of it (all?) */
}


/*
 * reschedule() operates WITHIN a thread, and moves vertices into
 * appropriately latter position in the queues.
 *
 */

#define SALARM(N)  (verbose ? printf("alarm(%d) = %d\n", (N),alarm(N)):alarm(N))

void
reschedule(vp, factor, index)
	struct vertex *vp;
	int factor, index;
{
	int skew;
	struct vertex *ap = NULL, *pap = NULL;
	struct thread *thr = vp->thread;

#if 0 /* Hmm.. The reschedule() is called only when we have a reason
	 to call it, doesn't it ?  */
	if (thr->proc &&
	    thr->proc->thread == thr) return; /* IN PROCESSING! */
#endif

	/* find out when to retry */
	time(&now);

	if (verbose)
	  printf("reschedule %lx now %d expiry in %d attempts %d factor %d inum %d (%s/%s: %s)\n",
		 (u_long)vp, (int)now,
		 (int)((vp->ce.expiry > 0) ? (vp->ce.expiry - now) : -999),
		 vp->attempts,
		 factor, (int)(vp->cfp->id),
		 vp->orig[L_CHANNEL]->name,
		 vp->orig[L_HOST]->name,
		 vp->cfp->mid);
	/* if we are already scheduled for the future, don't reschedule */
	if (vp->wakeup > now) {
	  if (verbose)
	    printf("prescheduled\n");
	  return;
	} else if (vp->wakeup < now-7200 /* more than 2h .. */ )
	  vp->wakeup = now;

	if (vp->ce.nretries <= 0) {
	  if (verbose)
	    printf("ce.retries = %d\n", vp->ce.nretries);
	  return;
	}

	if (vp->ce.expiry > 0 && vp->ce.expiry <= now && vp->attempts > 0) {
	  if (verbose)
	    printf("ce.expiry = %d, %d attempts\n",
		   (int)(vp->ce.expiry), vp->attempts);
	  expire(vp, index);
	  return;
	}

	/* unlink from the list of scheduled vertices */
	unthread(vp);
	/* NOW THE THREAD CAN BE WITHOUT ANY VERTEX !
	   WE MUST LINK IT BACK!                      */

	if (factor == -1) {
	  if (vp->retryindex >= vp->ce.nretries)
	    vp->retryindex = ranny(vp->ce.nretries-1);

	  /*
	   * clamp retry time to a predictable interval so we
	   * eventually bunch up deliveries.
	   */
	  skew = vp->wakeup % vp->ce.interval;
	  if (skew <= vp->ce.interval / 2)
	    skew = - (skew + (vp->ce.skew - 1));
	  else
	    skew = skew + (vp->ce.skew - 1);
	  skew = skew / vp->ce.skew; /* want int div truncation */

	  vp->wakeup += (skew +
			 vp->ce.retries[vp->retryindex] * vp->ce.interval);
	  vp->retryindex++;
	} else if (factor < -1) {
	  vp->wakeup = -factor;
	} else
	  vp->wakeup += factor * vp->ce.interval;

	/* XX: change this to a mod expression */
	if (vp->wakeup < now)
	  vp->wakeup = ((((now - vp->wakeup) / vp->ce.interval)+1)
			* vp->ce.interval);

	if (vp->ce.expiry > 0 && vp->ce.expiry <= vp->wakeup
	    && vp->attempts > 0) {
	  if (verbose)
	    printf("ce.expiry = %d, %d attempts\n",
		   (int)(vp->ce.expiry), vp->attempts);
	  /* XXX: Do we expire correctly ?
	          Now this vertex is not on any thread! */
	  expire(vp, index);

	  /* The thread can now be EMPTY! */
	  if (thr->vertices == NULL)
	    delete_thread(thr);

	  return;
	}

	if (verbose)
	  printf("wakeup %d pending %d\n", (int)(vp->wakeup), vp->ce.pending);

	/* link it back in at the right spot */
	for (ap = thr->vertices, pap = NULL ; ap != NULL; ap = ap->nextitem) {
	  if (ap->wakeup > vp->wakeup) {

	    /* Link the VP in front of AP */
	    _thread_linkfront(thr,ap,vp);
	    thr->jobs += 1;

	    break;
	  }
	  pap = ap;
	}
	if (verbose)
	  printf("ap %lx pap %lx curitem %lx\n", (long)ap, (long)pap, (long)thr->vertices);

	if (ap == NULL) {
	  /* append to list */
	  _thread_linktail(thr,vp);
	  thr->jobs += 1;
	}

	/* Now set thread wakeup value same as first vertex wakeup */
	thr->wakeup = thr->vertices->wakeup;

#if 0
	if (thr->wakeup > now  &&
	    (thr->wakeup - sweepinterval) <= now)
	  SALARM((u_int)(thr->wakeup - now));
	else
	  SALARM(sweepinterval/2);
#endif
	thread_reschedule(thr);
}


/*
 * With the  idle_cleanup()  we clean up idle processes, that have
 * been idle for too long (idlemax=nnnns)
 *
 * Because during its progress the thread-groups can disappear,
 * (as is one of its mandates) this code looks a bit peculiar..
 */
int
idle_cleanup()
{
	/* global: time_t now */
	struct threadgroup *thg = thrg_root;
	int thg_once = 1;
	int freecount = 0;

if (verbose) printf("idle_cleanup()\n");

	if (idleprocs == 0) return 0; /* If no idle ones, no cleanup.. */

	while (thrg_root != NULL && (thg_once || thg != thrg_root)) {

	  struct threadgroup *thgn = thg->next;
	  thg_once = 0;

	  if (thg->idlecnt > 0) {

	    struct procinfo *p, **pp;
	    p  =  thg->idleproc;
	    pp = &thg->idleproc;

	    while (p != NULL) {
	      if (thg->cep->idlemax + p->hungertime < now) {
		/* It is old enough -- ancient, one might say.. */

		/* Close the command channel, let it die itself.
		   Rest of the cleanup happens via mux() service. */
if (verbose) printf("idle_cleanup() killing processor on tofd=%d\n",p->tofd);
		write(p->tofd,"\n",1);
		close(p->tofd);
		p->tofd = -1;
		thg->idlecnt -= 1;
		idleprocs    -= 1;
		++freecount;
		thg->transporters -= 1;

		/* now remove this entry from the chains */
		*pp = p->next;
		p = p->next;
	      } else {
		/* Move to the next possible idle process */
		pp = &p->next;
		p = p->next;
	      }
	    }
	    if (thg->threads == 0 && thg->idlecnt == 0) {
	      /* No threads, no idle processes! Delete it! */
	      delete_threadgroup(thg);
	    }
	  }
	  /* Next one in thread rings */
	  thg = thgn;
	}
	return freecount;
}


void thread_report(fp)
FILE *fp;
{
	struct threadgroup *thg;
	int thg_once = 1;

	if (thrg_root == NULL) {
	  fprintf(fp,"No threads/processes.\n");
	  return;
	}
	for (thg = thrg_root;
	     thg_once || thg != thrg_root;
	     thg = thg->next) {
	  int cnt, procs;
	  struct procinfo *p = thg->idleproc;
	  struct thread *thr = thg->thread;
	  int thr_once = 1;

	  thg_once = 0;
	  fprintf(fp,"%s/%s/%d\t",
		  thg->wchan->name, thg->cep->host, thg->withhost);
	  cnt   = 0;
	  procs = 0;
	  thr_once = 1;
	  for (; thr && (thr_once || thr != thg->thread); thr = thr->nexttr) {
	    ++cnt;
	    if (thr->proc != NULL &&
		thr->proc->thread == thr) ++procs;
	    thr_once = 0;
	  }
	  fprintf(fp,"Threads: %d/%d ",thg->threads,cnt);
	  fprintf(fp,"Procs: %d/%d ",thg->transporters,procs);
	  cnt = 0;
	  for (;p != 0; p = p->next) ++cnt;
	  fprintf(fp,"Idle-procs: %d/%d\n",thg->idlecnt,cnt);
	  
	}
}
