/* Copyright (c) 2003, WebThing Ltd Author: Nick Kew This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* mod_pg_pool: manage a pool of PgSQL connections. EXPORTS: PGconn* pgpool_open(server_rec*) - retrieve a connection from the pool and check it's valid - May return null and log a message on error. void pgpool_close(server_rec*, PGconn*) - return a connection to the pool after use CONFIG: See the code round about line 106 WARNING: this module is a hack that I spent less than an hour on, and is untested. YMMV. Actually that's only half-true. Yes it's a hack, but it's distilled from stable code running at Valet, so most of it is at least somewhat tested. Just not in this form. I'm releasing it as a demo-of-concept for modules exporting a generic reusable shared resource for other modules, where (as in my case with Valet) more than one module wants to share a connection pool. */ #include #include #include #include #include #include #include #include extern module AP_MODULE_DECLARE_DATA pg_pool_module ; /************ svr cfg: manage db connection pool ****************/ typedef struct svr_cfg { apr_reslist_t* dbpool ; int nmin ; int nkeep ; int nmax ; int exptime ; const char* host ; const char* port ; const char* db ; const char* user ; const char* pass ; } svr_cfg ; typedef enum { cmd_host , cmd_port , cmd_db, cmd_user, cmd_pass, cmd_min, cmd_keep, cmd_max, cmd_exp } cmd_parts ; #define ISINT(val) \ for ( p = val; *p; ++p) \ if ( ! isdigit(*p) ) \ return "Argument must be numeric!" static const char* set_param(cmd_parms* cmd, void* cfg, const char* val) { const char* p ; svr_cfg* svr = (svr_cfg*) ap_get_module_config (cmd->server->module_config, &pg_pool_module ) ; switch ( (int) cmd->info ) { case cmd_host: svr->host = val ; break ; case cmd_port: svr->port = val ; break ; case cmd_db: svr->db = val ; break ; case cmd_user: svr->user = val ; break ; case cmd_pass: svr->pass = val ; break ; case cmd_min: ISINT(val) ; svr->nmin = atoi(val) ; break ; case cmd_keep: ISINT(val) ; svr->nkeep = atoi(val) ; break ; case cmd_max: ISINT(val) ; svr->nmax = atoi(val) ; break ; case cmd_exp: ISINT(val) ; svr->exptime = atoi(val) ; break ; } return NULL ; } static const command_rec pg_pool_cmds[] = { AP_INIT_TAKE1("PgPoolHost", set_param, (void*)cmd_host, RSRC_CONF, "PgSQL Host") , AP_INIT_TAKE1("PgPoolPort", set_param, (void*)cmd_port, RSRC_CONF, "PgSQL Port") , AP_INIT_TAKE1("PgPoolDB", set_param, (void*)cmd_db, RSRC_CONF, "PgSQL Database") , AP_INIT_TAKE1("PgPoolUser", set_param, (void*)cmd_user, RSRC_CONF, "PgSQL Username") , AP_INIT_TAKE1("PgPoolPass", set_param, (void*)cmd_pass, RSRC_CONF, "PgSQL Password") , AP_INIT_TAKE1("PgPoolMin", set_param, (void*)cmd_min, RSRC_CONF, "Minimum number of connections") , AP_INIT_TAKE1("PgPoolKeep", set_param, (void*)cmd_keep, RSRC_CONF, "Maximum number of sustained connections") , AP_INIT_TAKE1("PgPoolMax", set_param, (void*)cmd_max, RSRC_CONF, "Maximum number of connections") , AP_INIT_TAKE1("PgPoolExptime", set_param, (void*)cmd_exp, RSRC_CONF, "Keepalive time for idle connections") , {NULL} } ; static void* pg_pool_cfg(apr_pool_t* p, server_rec* x) { svr_cfg* svr = (svr_cfg*) apr_pcalloc(p, sizeof(svr_cfg)) ; return svr ; } /************ svr cfg: manage db connection pool ****************/ /* an apr_reslist_constructor for PgSQL connections */ static apr_status_t pgpool_construct(void** db, void* params, apr_pool_t* pool) { svr_cfg* svr = (svr_cfg*) params ; PGconn* sql = PQsetdbLogin(svr->host, svr->port, NULL, NULL, svr->db, svr->user, svr->pass) ; *db = sql ; if ( sql ) return APR_SUCCESS ; else return APR_EGENERAL ; } static apr_status_t pgpool_destruct(void* sql, void* params, apr_pool_t* pool) { PQfinish((PGconn*)sql) ; return APR_SUCCESS ; } static int setup_db_pool(apr_pool_t* p, apr_pool_t* plog, apr_pool_t* ptemp, server_rec* s) { svr_cfg* svr = (svr_cfg*) ap_get_module_config(s->module_config, &pg_pool_module) ; if ( apr_reslist_create(&svr->dbpool, svr->nmin, svr->nkeep, svr->nmax, svr->exptime, pgpool_construct, pgpool_destruct, svr, p) != APR_SUCCESS ) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, "PgPool: failed to initialise") ; return 500 ; } apr_pool_cleanup_register(p, svr->dbpool, (void*)apr_reslist_destroy, apr_pool_cleanup_null) ; return OK ; } static void pg_pool_hooks(apr_pool_t* p) { ap_hook_post_config(setup_db_pool, NULL, NULL, APR_HOOK_MIDDLE) ; } module AP_MODULE_DECLARE_DATA pg_pool_module = { STANDARD20_MODULE_STUFF, NULL , NULL , pg_pool_cfg , NULL , pg_pool_cmds , pg_pool_hooks } ; /* Functions we export for modules to use: - open acquires a connection from the pool (opens one if necessary) - close releases it back in to the pool */ PGconn* pgpool_open(server_rec* s) { PGconn* ret = NULL ; svr_cfg* svr = (svr_cfg*) ap_get_module_config(s->module_config, &pg_pool_module) ; if ( apr_reslist_acquire(svr->dbpool, (void**)&ret) != APR_SUCCESS ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "Failed to acquire PgSQL connection from pool!") ; return NULL ; } if (PQstatus(ret) != CONNECTION_OK) { PQreset(ret); if (PQstatus(ret) != CONNECTION_OK) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "PgSQL Error: %s", PQerrorMessage(ret) ) ; apr_reslist_release(svr->dbpool, ret) ; return NULL ; } } return ret ; } void pgpool_close(server_rec* s, PGconn* sql) { svr_cfg* svr = (svr_cfg*) ap_get_module_config(s->module_config, &pg_pool_module) ; apr_reslist_release(svr->dbpool, sql) ; }