/*
*
* 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