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