#include "pgqd.h" #include #include #include #include #include #include #include static void detect_dbs(void); static void recheck_dbs(void); static const char usage_str[] = "usage: pgq-ticker [switches] config.file\n" "Switches:\n" " -v Increase verbosity\n" " -q No output to console\n" " -d Daemonize\n" " -h Show help\n" " -V Show version\n" " --ini Show sample config file\n" " -s Stop - send SIGINT to running process\n" " -k Kill - send SIGTERM to running process\n" #ifdef SIGHUP " -r Reload - send SIGHUP to running process\n" #endif ""; static const char *sample_ini = #include "pgqd.ini.h" ; struct Config cf; struct Stats stats; static struct PgSocket *db_template; static STATLIST(database_list); static int got_sigint; #define CF_REL_BASE struct Config static const struct CfKey conf_params[] = { CF_ABS("logfile", CF_FILE, cf_logfile, 0, NULL), CF_REL("pidfile", CF_FILE, pidfile, 0, NULL), CF_REL("initial_database", CF_STR, initial_database, 0, "template1"), CF_REL("base_connstr", CF_STR, base_connstr, 0, ""), CF_REL("database_list", CF_STR, database_list, 0, NULL), CF_ABS("syslog", CF_INT, cf_syslog, 0, "1"), CF_ABS("syslog_ident", CF_STR, cf_syslog_ident, 0, "pgqd"), CF_ABS("syslog_facility", CF_STR, cf_syslog_facility, 0, "daemon"), CF_REL("check_period", CF_TIME_DOUBLE, check_period, 0, "60"), CF_REL("maint_period", CF_TIME_DOUBLE, maint_period, 0, "120"), CF_REL("retry_period", CF_TIME_DOUBLE, retry_period, 0, "30"), CF_REL("ticker_period", CF_TIME_DOUBLE, ticker_period, 0, "1"), CF_REL("stats_period", CF_TIME_DOUBLE, stats_period, 0, "30"), CF_REL("connection_lifetime", CF_TIME_DOUBLE, connection_lifetime, 0, "3600"), { NULL }, }; static const struct CfSect conf_sects[] = { { "pgqd", conf_params }, { NULL } }; static struct CfContext conf_info = { .sect_list = conf_sects, .base = &cf, }; static void load_config(void) { bool ok = cf_load_file(&conf_info, cf.config_file); if (!ok) fatal("failed to read config"); reset_logging(); } static void handle_sigterm(int sock, short flags, void *arg) { log_info("Got SIGTERM, fast exit"); /* pidfile cleanup happens via atexit() */ exit(1); } static void handle_sigint(int sock, short flags, void *arg) { log_info("Got SIGINT, shutting down"); /* notify main loop to exit */ got_sigint = 1; } static void handle_sighup(int sock, short flags, void *arg) { log_info("Got SIGHUP, re-reading config"); load_config(); recheck_dbs(); } static void signal_setup(void) { static struct event ev_sighup; static struct event ev_sigterm; static struct event ev_sigint; int err; #ifdef SIGPIPE sigset_t set; /* block SIGPIPE */ sigemptyset(&set); sigaddset(&set, SIGPIPE); err = sigprocmask(SIG_BLOCK, &set, NULL); if (err < 0) fatal_perror("sigprocmask"); #endif #ifdef SIGHUP /* catch signals */ signal_set(&ev_sighup, SIGHUP, handle_sighup, NULL); err = signal_add(&ev_sighup, NULL); if (err < 0) fatal_perror("signal_add"); #endif signal_set(&ev_sigterm, SIGTERM, handle_sigterm, NULL); err = signal_add(&ev_sigterm, NULL); if (err < 0) fatal_perror("signal_add"); signal_set(&ev_sigint, SIGINT, handle_sigint, NULL); err = signal_add(&ev_sigint, NULL); if (err < 0) fatal_perror("signal_add"); } const char *make_connstr(const char *dbname) { static char buf[512]; snprintf(buf, sizeof(buf), "%s dbname=%s ", cf.base_connstr, dbname); return buf; } static void launch_db(const char *dbname) { struct PgDatabase *db; struct List *elem; /* check of already exists */ statlist_for_each(elem, &database_list) { db = container_of(elem, struct PgDatabase, head); if (strcmp(db->name, dbname) == 0) { db->dropped = false; return; } } /* create new db entry */ db = calloc(1, sizeof(*db)); db->name = strdup(dbname); list_init(&db->head); statlist_init(&db->maint_op_list, "maint_op_list"); statlist_append(&database_list, &db->head); /* start working on it */ launch_ticker(db); } static void drop_db(struct PgDatabase *db, bool log) { if (log) log_info("Unregister database: %s", db->name); statlist_remove(&database_list, &db->head); pgs_free(db->c_ticker); pgs_free(db->c_maint); pgs_free(db->c_retry); free_maint(db); free(db->name); free(db); } static void detect_handler(struct PgSocket *sk, void *arg, enum PgEvent ev, PGresult *res) { int i; const char *s; struct List *el, *tmp; struct PgDatabase *db; switch (ev) { case PGS_CONNECT_OK: pgs_send_query_simple(sk, "select datname from pg_database" " where not datistemplate and datallowconn"); break; case PGS_RESULT_OK: /* tag old dbs as dead */ statlist_for_each(el, &database_list) { db = container_of(el, struct PgDatabase, head); db->dropped = true; } /* process new dbs */ for (i = 0; i < PQntuples(res); i++) { s = PQgetvalue(res, i, 0); launch_db(s); } /* drop old dbs */ statlist_for_each_safe(el, &database_list, tmp) { db = container_of(el, struct PgDatabase, head); if (db->dropped) drop_db(db, true); } pgs_disconnect(sk); pgs_sleep(sk, cf.check_period); break; case PGS_TIMEOUT: detect_dbs(); break; default: pgs_disconnect(sk); pgs_sleep(sk, cf.check_period); } } static void detect_dbs(void) { if (!db_template) { const char *cstr = make_connstr(cf.initial_database); db_template = pgs_create(cstr, detect_handler, NULL); } pgs_connect(db_template); } static bool launch_db_cb(void *arg, const char *db) { launch_db(db); return true; } static void recheck_dbs(void) { struct PgDatabase *db; struct List *el, *tmp; if (cf.database_list && cf.database_list[0]) { /* tag old dbs as dead */ statlist_for_each(el, &database_list) { db = container_of(el, struct PgDatabase, head); db->dropped = true; } /* process new ones */ if (!parse_word_list(cf.database_list, launch_db_cb, NULL)) { log_warning("database_list parsing failed: %s", strerror(errno)); return; } /* drop old ones */ statlist_for_each_safe(el, &database_list, tmp) { db = container_of(el, struct PgDatabase, head); if (db->dropped) drop_db(db, true); } /* done with template for the moment */ if (db_template) { pgs_free(db_template); db_template = NULL; } } else if (!db_template) { log_info("auto-detecting dbs ..."); detect_dbs(); } } static struct event stats_ev; static void stats_handler(int fd, short flags, void *arg) { struct timeval tv = { cf.stats_period, 0 }; log_info("{ticks: %d, maint: %d, retry: %d}", stats.n_ticks, stats.n_maint, stats.n_retry); memset(&stats, 0, sizeof(stats)); if (evtimer_add(&stats_ev, &tv) < 0) fatal_perror("evtimer_add"); } static void stats_setup(void) { struct timeval tv = { cf.stats_period, 0 }; evtimer_set(&stats_ev, stats_handler, NULL); if (evtimer_add(&stats_ev, &tv) < 0) fatal_perror("evtimer_add"); } static void cleanup(void) { struct PgDatabase *db; struct List *elem, *tmp; statlist_for_each_safe(elem, &database_list, tmp) { db = container_of(elem, struct PgDatabase, head); drop_db(db, false); } pgs_free(db_template); event_base_free(NULL); reset_logging(); } static void main_loop_once(void) { reset_time_cache(); if (event_loop(EVLOOP_ONCE) != 0) { log_error("event_loop failed: %s", strerror(errno)); } } int main(int argc, char *argv[]) { int c; bool daemon = false; int sig = 0; const char *signame = NULL; for (c = 1; c < argc; c++) { if (!strcmp(argv[c], "--ini")) { printf("%s", sample_ini); exit(0); } if (!strcmp(argv[c], "--help")) { printf(usage_str); exit(0); } } while ((c = getopt(argc, argv, "dqvhVrsk")) != -1) { switch (c) { case 'd': daemon = true; break; case 'v': cf_verbose++; break; case 'q': cf_quiet = 1; break; case 'h': printf(usage_str); return 0; #ifdef SIGHUP case 'r': sig = SIGHUP; signame = "SIGHUP"; break; #endif case 's': sig = SIGINT; signame = "SIGINT"; break; case 'k': sig = SIGTERM; signame = "SIGTERM"; break; default: printf("bad switch: "); printf(usage_str); return 1; } } if (optind + 1 != argc) { fprintf(stderr, "pgqd requires config file\n"); return 1; } cf.config_file = argv[optind]; load_config(); conf_info.loaded = true; if (sig) { if (!cf.pidfile || !cf.pidfile[0]) { fprintf(stderr, "No pidfile configured\n"); return 1; } if (signal_pidfile(cf.pidfile, sig)) fprintf(stderr, "%s sent\n", signame); else fprintf(stderr, "Old process is not running\n"); return 0; } log_info("Starting pgqd " PACKAGE_VERSION); daemonize(cf.pidfile, daemon); if (!event_init()) fatal("event_init failed"); signal_setup(); stats_setup(); recheck_dbs(); while (!got_sigint) main_loop_once(); cleanup(); return 0; }