dougm 00/05/23 13:53:38
Added: src/modules/perl modperl_tipool.c modperl_tipool.h
Log:
"thread item pool" - generic logic broken out from modperl_interp.c so it can be
reused for other things, e.g. database connection handles
Revision Changes Path
1.1 modperl-2.0/src/modules/perl/modperl_tipool.c
Index: modperl_tipool.c
===================================================================
#ifdef USE_ITHREADS
#include "mod_perl.h"
/*
* tipool == "thread item pool"
* this module is intended to provide generic stuctures/functions
* for managing a "pool" of a given items (data structures) within a threaded
* process. at the moment, mod_perl uses this module to manage a pool
* of PerlInterpreter objects. it should be quite easy to reuse for
* other data, such as database connection handles and the like.
* while it is "generic" it is also tuned for Apache, making use of
* ap_pool_t and the like, and implementing start/max/{min,max}_spare/
* max_requests configuration.
* this is another "proof-of-concept", plenty of room for improvement here
*/
modperl_list_t *modperl_list_new(ap_pool_t *p)
{
modperl_list_t *listp =
(modperl_list_t *)ap_pcalloc(p, sizeof(*listp));
return listp;
}
modperl_list_t *modperl_list_last(modperl_list_t *list)
{
while (list->next) {
list = list->next;
}
return list;
}
modperl_list_t *modperl_list_first(modperl_list_t *list)
{
while (list->prev) {
list = list->prev;
}
return list;
}
modperl_list_t *modperl_list_append(modperl_list_t *list,
modperl_list_t *new_list)
{
modperl_list_t *last;
new_list->prev = new_list->next = NULL;
if (!list) {
return new_list;
}
last = modperl_list_last(list);
last->next = new_list;
new_list->prev = last;
return list;
}
modperl_list_t *modperl_list_prepend(modperl_list_t *list,
modperl_list_t *new_list)
{
new_list->prev = new_list->next = NULL;
if (!list) {
return new_list;
}
if (list->prev) {
list->prev->next = new_list;
new_list->prev = list->prev;
}
list->prev = new_list;
new_list->next = list;
return new_list;
}
modperl_list_t *modperl_list_remove(modperl_list_t *list,
modperl_list_t *rlist)
{
modperl_list_t *tmp = list;
while (tmp) {
if (tmp != rlist) {
tmp = tmp->next;
}
else {
if (tmp->prev) {
tmp->prev->next = tmp->next;
}
if (tmp->next) {
tmp->next->prev = tmp->prev;
}
if (list == tmp) {
list = list->next;
}
break;
}
}
#ifdef MP_TRACE
if (!tmp) {
/* should never happen */
MP_TRACE_i(MP_FUNC, "failed to find 0x%lx in list 0x%lx\n",
(unsigned long)rlist, (unsigned long)list);
}
#endif
return list;
}
modperl_list_t *modperl_list_remove_data(modperl_list_t *list,
void *data,
modperl_list_t **listp)
{
modperl_list_t *tmp = list;
while (tmp) {
if (tmp->data != data) {
tmp = tmp->next;
}
else {
*listp = tmp;
if (tmp->prev) {
tmp->prev->next = tmp->next;
}
if (tmp->next) {
tmp->next->prev = tmp->prev;
}
if (list == tmp) {
list = list->next;
}
break;
}
}
return list;
}
modperl_tipool_t *modperl_tipool_new(ap_pool_t *p,
modperl_tipool_config_t *cfg,
modperl_tipool_vtbl_t *func,
void *data)
{
modperl_tipool_t *tipool =
(modperl_tipool_t *)ap_pcalloc(p, sizeof(*tipool));
tipool->ap_pool = p;
tipool->cfg = cfg;
tipool->func = func;
tipool->data = data;
MUTEX_INIT(&tipool->tiplock);
COND_INIT(&tipool->available);
return tipool;
}
void modperl_tipool_init(modperl_tipool_t *tipool)
{
int i;
for (i=0; i<tipool->cfg->start; i++) {
void *item =
(*tipool->func->tipool_sgrow)(tipool, tipool->data);
modperl_tipool_add(tipool, item);
}
MP_TRACE_i(MP_FUNC, "start=%d, max=%d, min_spare=%d, max_spare=%d\n",
tipool->cfg->start, tipool->cfg->max,
tipool->cfg->min_spare, tipool->cfg->max_spare);
}
void modperl_tipool_destroy(modperl_tipool_t *tipool)
{
while (tipool->idle) {
if (tipool->func->tipool_destroy) {
(*tipool->func->tipool_destroy)(tipool, tipool->data,
tipool->idle->data);
}
tipool->size--;
tipool->idle = tipool->idle->next;
}
if (tipool->busy) {
MP_TRACE_i(MP_FUNC, "ERROR: %d items still in use\n",
tipool->in_use);
}
MUTEX_DESTROY(&tipool->tiplock);
COND_DESTROY(&tipool->available);
}
void modperl_tipool_add(modperl_tipool_t *tipool, void *data)
{
modperl_list_t *listp = modperl_list_new(tipool->ap_pool);
listp->data = data;
/* assuming tipool->tiplock has already been aquired */
tipool->idle = modperl_list_append(tipool->idle, listp);
tipool->size++;
MP_TRACE_i(MP_FUNC, "added 0x%lx (size=%d)\n",
(unsigned long)listp, tipool->size);
}
void modperl_tipool_remove(modperl_tipool_t *tipool, modperl_list_t *listp)
{
/* assuming tipool->tiplock has already been aquired */
tipool->idle = modperl_list_remove(tipool->idle, listp);
tipool->size--;
MP_TRACE_i(MP_FUNC, "removed 0x%lx (size=%d)\n",
(unsigned long)listp, tipool->size);
}
modperl_list_t *modperl_tipool_pop(modperl_tipool_t *tipool)
{
modperl_list_t *head;
modperl_tipool_lock(tipool);
if (tipool->size == tipool->in_use) {
if (tipool->size < tipool->cfg->max) {
MP_TRACE_i(MP_FUNC,
"no idle items, size %d < %d max\n",
tipool->size, tipool->cfg->max);
if (tipool->func->tipool_rgrow) {
void * item =
(*tipool->func->tipool_sgrow)(tipool, tipool->data);
modperl_tipool_add(tipool, item);
}
}
/* block until an item becomes available */
modperl_tipool_wait(tipool);
}
head = tipool->idle;
tipool->idle = modperl_list_remove(tipool->idle, head);
tipool->busy = modperl_list_append(tipool->busy, head);
tipool->in_use++;
/* XXX: this should never happen */
if (!head) {
MP_TRACE_i(MP_FUNC, "PANIC: no items available, %d of %d in use\n",
tipool->in_use, tipool->size);
abort();
}
modperl_tipool_unlock(tipool);
return head;
}
static void modperl_tipool_putback_base(modperl_tipool_t *tipool,
modperl_list_t *listp,
void *data,
int num_requests)
{
int max_spare, max_requests;
modperl_tipool_lock(tipool);
/* remove from busy list, add back to idle */
/* XXX: option to sort list, e.g. on num_requests */
if (listp) {
tipool->busy = modperl_list_remove(tipool->busy, listp);
}
else {
tipool->busy = modperl_list_remove_data(tipool->busy, data, &listp);
}
tipool->idle = modperl_list_prepend(tipool->idle, listp);
tipool->in_use--;
#ifdef MP_TRACE
if (!tipool->busy && tipool->func->tipool_dump) {
MP_TRACE_i(MP_FUNC, "all items idle:\n");
MP_TRACE_i_do((*tipool->func->tipool_dump)(tipool,
tipool->data,
tipool->idle));
}
#endif
MP_TRACE_i(MP_FUNC, "0x%lx now available (%d in use, %d running)\n",
(unsigned long)listp->data, tipool->in_use, tipool->size);
if (tipool->in_use == (tipool->cfg->max - 1)) {
/* hurry up, another thread may be blocking */
modperl_tipool_broadcast(tipool);
modperl_tipool_unlock(tipool);
return;
}
max_spare = (tipool->size > tipool->cfg->max_spare);
max_requests = ((num_requests > 0) &&
(num_requests > tipool->cfg->max_requests));
if (max_spare) {
MP_TRACE_i(MP_FUNC, "throttle down (max_spare=%d, %d running)\n",
tipool->cfg->max_spare, tipool->size);
}
else if (max_requests) {
MP_TRACE_i(MP_FUNC, "max requests %d reached\n",
tipool->cfg->max_requests);
}
/* XXX: this management should probably be happening elsewhere
* like in a thread spawned at startup
*/
if (max_spare || max_requests) {
modperl_tipool_remove(tipool, listp);
if (tipool->func->tipool_destroy) {
(*tipool->func->tipool_destroy)(tipool, tipool->data,
listp->data);
}
if (max_requests && (tipool->size < tipool->cfg->min_spare)) {
if (tipool->func->tipool_rgrow) {
void *item =
(*tipool->func->tipool_sgrow)(tipool,
tipool->data);
MP_TRACE_i(MP_FUNC, "new item: size %d < %d min_spare\n",
tipool->size, tipool->cfg->min_spare);
modperl_tipool_add(tipool, item);
}
}
}
modperl_tipool_unlock(tipool);
}
/* _data functions are so structures (e.g. modperl_interp_t) don't
* need to maintain a pointer back to the modperl_list_t
*/
void modperl_tipool_putback_data(modperl_tipool_t *tipool,
void *data,
int num_requests)
{
modperl_tipool_putback_base(tipool, NULL, data, num_requests);
}
void modperl_tipool_putback(modperl_tipool_t *tipool,
modperl_list_t *listp,
int num_requests)
{
modperl_tipool_putback_base(tipool, listp, NULL, num_requests);
}
#endif /* USE_ITHREADS */
1.1 modperl-2.0/src/modules/perl/modperl_tipool.h
Index: modperl_tipool.h
===================================================================
#ifndef MODPERL_TIPOOL_H
#ifdef USE_ITHREADS
modperl_list_t *modperl_list_new(ap_pool_t *p);
modperl_list_t *modperl_list_last(modperl_list_t *list);
modperl_list_t *modperl_list_first(modperl_list_t *list);
modperl_list_t *modperl_list_append(modperl_list_t *list,
modperl_list_t *new_list);
modperl_list_t *modperl_list_prepend(modperl_list_t *list,
modperl_list_t *new_list);
modperl_list_t *modperl_list_remove(modperl_list_t *list,
modperl_list_t *rlist);
modperl_tipool_t *modperl_tipool_new(ap_pool_t *p,
modperl_tipool_config_t *cfg,
modperl_tipool_vtbl_t *func,
void *data);
void modperl_tipool_init(modperl_tipool_t *tipool);
void modperl_tipool_destroy(modperl_tipool_t *tipool);
void modperl_tipool_add(modperl_tipool_t *tipool, void *data);
void modperl_tipool_remove(modperl_tipool_t *tipool, modperl_list_t *listp);
modperl_list_t *modperl_tipool_pop(modperl_tipool_t *tipool);
void modperl_tipool_putback(modperl_tipool_t *tipool,
modperl_list_t *listp,
int num_requests);
void modperl_tipool_putback_data(modperl_tipool_t *tipool, void *data,
int num_requests);
#define modperl_tipool_wait(tipool) \
while (tipool->size == tipool->in_use) { \
MP_TRACE_i(MP_FUNC, "waiting for available tipool item\n"); \
MP_TRACE_i(MP_FUNC, "(%d items in use, %d alive)\n", \
tipool->in_use, tipool->size); \
COND_WAIT(&tipool->available, &tipool->tiplock); \
}
#define modperl_tipool_broadcast(tipool) \
MP_TRACE_i(MP_FUNC, "broadcast available tipool item\n"); \
COND_SIGNAL(&tipool->available)
#define modperl_tipool_lock(tipool) \
MP_TRACE_i(MP_FUNC, "about to lock tipool\n"); \
MUTEX_LOCK(&tipool->tiplock); \
MP_TRACE_i(MP_FUNC, "aquired tipool lock\n")
#define modperl_tipool_unlock(tipool) \
MP_TRACE_i(MP_FUNC, "about to unlock tipool\n"); \
MUTEX_UNLOCK(&tipool->tiplock); \
MP_TRACE_i(MP_FUNC, "released tipool lock\n")
#endif /* USE_ITHREADS */
#endif /* MODPERL_TIPOOL_H */