/*
 * 
 * Copyright (c) 2005-2006 NFG Net Facilities Group BV support@nfg.nl
 *
 *
 * pool.c Management of process pool
 * 
 * 
 * TODO:
 *
 * general: statistics bookkeeping
 * 
 * 
 */

#include "dbmail.h"

#define P_SIZE 100000

static volatile Scoreboard_t *scoreboard;
static int shmid;
static int sb_lockfd;

extern volatile sig_atomic_t GeneralStopRequested;
extern ChildInfo_t childinfo;


static child_state_t state_new(void); 
static int set_lock(int type);
static pid_t reap_child(void);

/*
 *
 *
 * Scoreboard
 *
 *
 */

child_state_t state_new(void)
{
	child_state_t s;
	s.pid = -1;
	s.ctime = time(0);
	s.status = STATE_NOOP;
	s.count = 0;
	s.client = "none";
	return s;
}

int set_lock(int type)
{
	int result, serr;
	struct flock lock;
	lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLOCK */
	lock.l_start = 0;
	lock.l_whence = 0;
	lock.l_len = 1;
	result = fcntl(sb_lockfd, F_SETLK, &lock);
	if (result == -1) {
		serr = errno;
		switch (serr) {
			case EACCES:
			case EAGAIN:
			case EDEADLK:
				usleep(10);
				set_lock(type);
				break;
			default:
				// ignore the rest
				break;
		}
		errno = serr;
	}
	return result;
}

void scoreboard_new(serverConfig_t * conf)
{
	int serr;
	if ((shmid = shmget(IPC_PRIVATE, P_SIZE, 0644 | IPC_CREAT)) == -1) {
		serr = errno;
		trace(TRACE_FATAL, "%s,%s: shmget failed [%s]",
				__FILE__,__func__, 
				strerror(serr));
	}
	scoreboard = shmat(shmid, (void *) 0, 0);
	serr=errno;
	if (scoreboard == (Scoreboard_t *) (-1)) {
		trace(TRACE_FATAL, "%s,%s: scoreboard init failed [%s]",
		      __FILE__,__func__,
		      strerror(serr));
		scoreboard_delete();
	}
	scoreboard_lock_new();
	scoreboard->conf = conf;
	scoreboard_setup();
	scoreboard_conf_check();
}

char * scoreboard_lock_getfilename(void)
{
	return g_strdup_printf("%s_%d.LCK", SCOREBOARD_LOCK_FILE, getpid());
}

void scoreboard_lock_new(void)
{
	gchar *statefile = scoreboard_lock_getfilename();
	if ( (sb_lockfd = open(statefile,O_EXCL|O_RDWR|O_CREAT|O_TRUNC,0600)) < 0) {
		trace(TRACE_FATAL, "%s,%s, opening lockfile [%s] failed", 
		      __FILE__, __func__, statefile);
	}
	g_free(statefile);
}

void scoreboard_setup(void) {	
	int i;
	scoreboard_wrlck();
	for (i = 0; i < HARD_MAX_CHILDREN; i++)
		scoreboard->child[i] = state_new();
	scoreboard_unlck();
}

void scoreboard_conf_check(void)
{
	/* some sanity checks on boundaries */
	scoreboard_wrlck();
	if (scoreboard->conf->maxChildren > HARD_MAX_CHILDREN) {
		trace(TRACE_WARNING, "%s,%s: MAXCHILDREN too large. Decreasing to [%d]",
		      __FILE__,__func__,
		      HARD_MAX_CHILDREN);
		scoreboard->conf->maxChildren = HARD_MAX_CHILDREN;
	} else if (scoreboard->conf->maxChildren < scoreboard->conf->startChildren) {
		trace(TRACE_WARNING, "%s,%s: MAXCHILDREN too small. Increasing to NCHILDREN [%d]",
		      __FILE__,__func__,
		      scoreboard->conf->startChildren);
		scoreboard->conf->maxChildren = scoreboard->conf->startChildren;
	}

	if (scoreboard->conf->maxSpareChildren > scoreboard->conf->maxChildren) {
		trace(TRACE_WARNING, "%s,%s: MAXSPARECHILDREN too large. Decreasing to MAXCHILDREN [%d]",
		      __FILE__,__func__,
		      scoreboard->conf->maxChildren);
		scoreboard->conf->maxSpareChildren = scoreboard->conf->maxChildren;
	} else if (scoreboard->conf->maxSpareChildren < scoreboard->conf->minSpareChildren) {
		trace(TRACE_WARNING, "%s,%s: MAXSPARECHILDREN too small. Increasing to MINSPARECHILDREN [%d]",
		      __FILE__,__func__,
		      scoreboard->conf->minSpareChildren);
		scoreboard->conf->maxSpareChildren = scoreboard->conf->minSpareChildren;
	}
	scoreboard_unlck();
}

static unsigned scoreboard_cleanup(void)
{
	/* return the number of children still registered as being alive */
	unsigned count = 0;
	int i, status = 0;
	pid_t chpid = 0;
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		
		scoreboard_rdlck();
		chpid = scoreboard->child[i].pid;
		status = scoreboard->child[i].status;
		scoreboard_unlck();
		
		if (chpid <= 0)
			continue;
		
		count++;
		
		if (status == STATE_WAIT) {
			if (waitpid(chpid, NULL, WNOHANG | WUNTRACED) == chpid)
				scoreboard_release(chpid);			
		}
	}
	return count;
}


void scoreboard_release(pid_t pid)
{
	int key;
	key = getKey(pid);

	if (key == -1) 
		return;
	
	scoreboard_wrlck();
	scoreboard->child[key] = state_new();
	scoreboard_unlck();
}

void scoreboard_delete(void)
{
	gchar *statefile;
	if (shmdt((const void *)scoreboard) == -1)
		trace(TRACE_FATAL, "%s,%s: detach shared mem failed",
		      __FILE__, __func__);
	if (shmctl(shmid, IPC_RMID, NULL) == -1)
		trace(TRACE_FATAL, "%s,%s: delete shared mem segment failed",
		      __FILE__, __func__);
	
	statefile = scoreboard_lock_getfilename();
	if (unlink(statefile) == -1) 
		trace(TRACE_ERROR, "%s,%s: error deleting scoreboard lock file %s", 
		      __FILE__, __func__, 
		      statefile);
	g_free(statefile);
	
	return;
}

static void scoreboard_state(void)
{
	unsigned children = count_children();
	unsigned spares = count_spare_children();
	/* scoreboard */
	trace(TRACE_MESSAGE, "%s,%s: children [%d/%d], spares [%d (%d - %d)]",
	      __FILE__,__func__,
	      children, scoreboard->conf->maxChildren, spares,
	      scoreboard->conf->minSpareChildren,
	      scoreboard->conf->maxSpareChildren);
}


int count_spare_children(void)
{
	int i, count;
	count = 0;
	
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid > 0
		    && scoreboard->child[i].status == STATE_IDLE)
			count++;
	}
	scoreboard_unlck();
	return count;
}

int count_children(void)
{
	int i, count;
	count = 0;
	
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid > 0)
			count++;
	}
	scoreboard_unlck();
	
	return count;
}

pid_t get_idle_spare(void)
{
	int i;
	pid_t idlepid = (pid_t) -1;
	/* get the last-in-first-out idle process */	
	scoreboard_rdlck();
	for (i = scoreboard->conf->maxChildren - 1; i >= 0; i--) {
		if ((scoreboard->child[i].pid > 0) && (scoreboard->child[i].status == STATE_IDLE)) {
			idlepid = scoreboard->child[i].pid;
			break;
		}
	}
	scoreboard_unlck();
	
	return idlepid;
}

int getKey(pid_t pid)
{
	int i;
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid == pid) {
			scoreboard_unlck();
			return i;
		}
	}
	scoreboard_unlck();
	trace(TRACE_ERROR, "%s,%s: pid NOT found on scoreboard [%d]",
		       	__FILE__, __func__, pid);
	return -1;
}

/*
 *
 *
 * Child
 *
 *
 */

int child_register(void)
{
	int i;
	pid_t chpid = getpid();
	
	trace(TRACE_MESSAGE, "%s,%s: register child [%d]",
	      __FILE__, __func__, chpid);
	
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid == -1)
			break;
		if (scoreboard->child[i].pid == chpid) {
			trace(TRACE_ERROR, "%s,%s: child already registered.",
			      __FILE__, __func__);
			scoreboard_unlck();
			return -1;
		}
	}
	scoreboard_unlck();
	
	if (i == scoreboard->conf->maxChildren) {
		trace(TRACE_WARNING, "%s,%s: no empty slot found",
		      __FILE__, __func__);
		return -1;
	}
	
	scoreboard_wrlck();
	scoreboard->child[i].pid = chpid;
	scoreboard->child[i].status = STATE_IDLE;
	scoreboard_unlck();

	trace(TRACE_INFO, "%s,%s: initializing child_state [%d] using slot [%d]",
		__FILE__, __func__, chpid, i);
	return 0;
}

void child_reg_connected(void)
{
	int key;
	pid_t pid;
	
	pid = getpid();
	key = getKey(pid);
	
	if (key == -1) 
		trace(TRACE_FATAL, "%s:%s: fatal: unable to find this pid on the scoreboard", 
				__FILE__, __func__);
	
	scoreboard_wrlck();
	scoreboard->child[key].status = STATE_CONNECTED;
	scoreboard_unlck();
}

void child_reg_disconnected(void)
{
	int key;
	pid_t pid;
	
	pid = getpid();
	key = getKey(pid);

	if (key == -1) 
		trace(TRACE_FATAL, "%s:%s: fatal: unable to find this pid on the scoreboard", 
				__FILE__, __func__);
	
	scoreboard_wrlck();
	scoreboard->child[key].status = STATE_IDLE;
	scoreboard_unlck();
}

void child_unregister(void)
{
	/*
	 *
	 * Set the state for this slot to WAIT
	 * so the parent process can do a waitpid()
	 *
	 */
	int key;
	pid_t pid;
	
	pid = getpid();
	key = getKey(pid);
	
	if (key == -1)
		trace(TRACE_FATAL, "%s:%s: fatal: unable to find this pid on the scoreboard", 
				__FILE__, __func__);
	
	scoreboard_wrlck();
	scoreboard->child[key].status = STATE_WAIT;
	scoreboard_unlck();
}


/*
 *
 *
 * Server
 *
 *
 */

void manage_start_children(void)
{
	/* 
	 *
	 * startup the first batch of forked processes
	 *
	 */
	int i;
	for (i = 0; i < scoreboard->conf->startChildren; i++) {
		if (CreateChild(&childinfo) > -1)
			continue;
		manage_stop_children();
		trace(TRACE_FATAL, "%s,%s: could not create children.",
		      __FILE__, __func__);
		exit(0);
	}
}

void manage_stop_children(void)
{
	/* 
	 *
	 * cleanup all remaining forked processes
	 *
	 */
	int alive = 0;
	int i, cnt = 0;
	pid_t chpid;
	
	trace(TRACE_MESSAGE, "%s,%s: General stop requested. Killing children.. ",
			__FILE__,__func__);

	for (i=0; i < scoreboard->conf->maxChildren; i++) {
		scoreboard_rdlck();
		chpid = scoreboard->child[i].pid;
		scoreboard_unlck();
		
		if (chpid < 0)
			continue;
		if (kill(chpid, SIGTERM))
			trace(TRACE_ERROR, "%s,%s: %s", __FILE__, __func__, strerror(errno));
	}
	
	alive = scoreboard_cleanup();
	while (alive > 0 && cnt++ < 10) {
		alive = scoreboard_cleanup();
		sleep(1);
	}
	
	if (alive) {
		trace(TRACE_INFO, "%s,%s: [%d] children alive after SIGTERM, sending SIGKILL",
		      __FILE__,__func__, alive);

		for (i = 0; i < scoreboard->conf->maxChildren; i++) {
			scoreboard_rdlck();
			chpid = scoreboard->child[i].pid;
			scoreboard_unlck();
			
			if (chpid < 0)
				continue;
			kill(chpid, SIGKILL);;
			if (waitpid(chpid, NULL, WNOHANG | WUNTRACED) == chpid)
				scoreboard_release(chpid);
		}
	}
}

static pid_t reap_child(void)
{
	pid_t chpid=0;

	if ((chpid = get_idle_spare()) < 0)
		return 0; // no idle children

	if (kill(chpid, SIGTERM)) {
		trace(TRACE_ERROR, "%s,%s: %s", __FILE__, __func__, strerror(errno));
		return -1;
	}
		
	return 0;
}

void manage_spare_children(void)
{
	/* 
	 *
	 * manage spare children while running. One child more/less for each run.
	 *
	 */
	pid_t chpid = 0;
	int spares, children;
	int changes = 0;
	int minchildren, maxchildren;
	int minspares, maxspares;
	
	if (GeneralStopRequested)
		return;

	// cleanup
	scoreboard_cleanup();

	children = count_children();
	spares = count_spare_children();
	
	/* scale up */
	minchildren = scoreboard->conf->startChildren;
	maxchildren = scoreboard->conf->maxChildren;
	minspares = scoreboard->conf->minSpareChildren;
	maxspares = scoreboard->conf->maxSpareChildren;

	if ((children < minchildren || spares < minspares) && children < maxchildren)  {
		if ((chpid = CreateChild(&childinfo)) < 0) 
			return;
		changes++;
	}
	/* scale down */
	else if (children > minchildren && spares > maxspares) {
		reap_child();
		changes++;
	}

	if (changes)
		scoreboard_state();

	children = count_children();
}



syntax highlighted by Code2HTML, v. 0.9.1