pgpool2.git / summary / log / commit / refs

commit    711b00a8ff7d861350e3d65cbe8f4120654f2b4d
Author:   Muhammad Usama <m.usama@gmail.com>
Date:     Wed Dec 04 10:05:09 2013 +0000

    Merge branch 'master' into EXCEPTION_MGR
    
    Conflicts:
    	src/auth/pool_auth.c
    	src/main/main.c


doc/pgpool-en.html | 1 + doc/pgpool-ja.html | 1 + src/auth/pool_auth.c | 21 ++++++++++---------- src/context/pool_process_context.c | 8 +++++++- src/context/pool_query_context.c | 18 ++++++++--------- src/main/main.c | 2 +- src/main/pgpool_main.c | 10 +++++++--- src/protocol/pool_process_query.c | 23 +++++++++++----------- src/protocol/pool_proto_modules.c | 17 +++++++--------- src/sql/insert_lock.sql | 3 ++- src/test/regression/tests/001.load_balance/test.sh | 22 ++++++++++++++++++++- src/utils/pool_process_reporting.c | 4 ++-- src/watchdog/wd_ping.c | 7 +++++-- 13 files changed, 86 insertions(+), 51 deletions(-) diff --cc src/auth/pool_auth.c index c2aa5fe94,22e9cbb3a..9bd9b7748 --- a/src/auth/pool_auth.c +++ b/src/auth/pool_auth.c @@@ -406,19 -380,13 +407,17 @@@ int pool_do_auth(POOL_CONNECTION *front } } - #ifdef NOT_USED - sp = MASTER_CONNECTION(cp)->sp; - cp->info->major = sp->major; - cp->info->minor = sp->minor; - strncpy(cp->info->database, sp->database, sizeof(cp->info->database) - 1); - strncpy(cp->info->user, sp->user, sizeof(cp->info->user) - 1); - cp->info->counter = 1; - #endif + if (pid == -1) + { - pool_error("pool_do_auth: all backends are down"); - return -1; ++ ereport(ERROR, ++ (errmsg("authentication failed"), ++ errdetail("pool_do_auth: all backends are down"))); + } - - return pool_send_backend_key_data(frontend, pid, key, protoMajor); + if(pool_send_backend_key_data(frontend, pid, key, protoMajor)) + ereport(ERROR, + (errmsg("authentication failed"), - errdetail("failed to send backend data to frontedn"))); ++ errdetail("failed to send backend data to frontend"))); + return 0; } /* diff --cc src/main/main.c index 3cfaf8077,553b7241b..586dffb02 --- a/src/main/main.c +++ b/src/main/main.c @@@ -537,9 -1126,1539 +537,9 @@@ char *get_config_file_name(void } /* -* Write the pid file -*/ -static int write_status_file(void) -{ - FILE *fd; - char fnamebuf[POOLMAXPATHLEN]; - int i; - - snprintf(fnamebuf, sizeof(fnamebuf), "%s/%s", pool_config->logdir, STATUS_FILE_NAME); - fd = fopen(fnamebuf, "w"); - if (!fd) - { - pool_error("Could not open status file %s", fnamebuf); - return -1; - } - - memset(&backend_rec, 0, sizeof(backend_rec)); - - for (i=0;i< pool_config->backend_desc->num_backends;i++) - { - backend_rec.status[i] = BACKEND_INFO(i).backend_status; - } - - if (fwrite(&backend_rec, 1, sizeof(backend_rec), fd) != sizeof(backend_rec)) - { - pool_error("Could not write backend status file as %s. reason: %s", - fnamebuf, strerror(errno)); - fclose(fd); - return -1; - } - fclose(fd); - return 0; -} - -/* - * fork a child for PCP - */ -pid_t pcp_fork_a_child(int unix_fd, int inet_fd, char *pcp_conf_file) -{ - pid_t pid; - - pid = fork(); - - if (pid == 0) - { - close(pipe_fds[0]); - close(pipe_fds[1]); - - myargv = save_ps_display_args(myargc, myargv); - - /* call PCP child main */ - POOL_SETMASK(&UnBlockSig); - health_check_timer_expired = 0; - reload_config_request = 0; - run_as_pcp_child = true; - pcp_do_child(unix_fd, inet_fd, pcp_conf_file); - } - else if (pid == -1) - { - pool_error("fork() failed. reason: %s", strerror(errno)); - myexit(1); - } - return pid; -} - -/* -* fork a child -*/ -pid_t fork_a_child(int unix_fd, int inet_fd, int id) -{ - pid_t pid; - - pid = fork(); - - if (pid == 0) - { - /* Before we unconditionally closed pipe_fds[0] and pipe_fds[1] - * here, which is apparently wrong since in the start up of - * pgpool, pipe(2) is not called yet and it mistakenly closes - * fd 0. Now we check the fd > 0 before close(), expecting - * pipe returns fds greater than 0. Note that we cannot - * unconditionally remove close(2) calls since fork_a_child() - * may be called *after* pgpool starting up. - */ - if (pipe_fds[0] > 0) - { - close(pipe_fds[0]); - close(pipe_fds[1]); - } - - myargv = save_ps_display_args(myargc, myargv); - - /* call child main */ - POOL_SETMASK(&UnBlockSig); - health_check_timer_expired = 0; - reload_config_request = 0; - my_proc_id = id; - run_as_pcp_child = false; - do_child(unix_fd, inet_fd); - } - else if (pid == -1) - { - pool_error("fork() failed. reason: %s", strerror(errno)); - myexit(1); - } - return pid; -} - -/* -* fork worker child process -*/ -pid_t worker_fork_a_child() -{ - pid_t pid; - - pid = fork(); - - if (pid == 0) - { - /* Before we unconditionally closed pipe_fds[0] and pipe_fds[1] - * here, which is apparently wrong since in the start up of - * pgpool, pipe(2) is not called yet and it mistakenly closes - * fd 0. Now we check the fd > 0 before close(), expecting - * pipe returns fds greater than 0. Note that we cannot - * unconditionally remove close(2) calls since fork_a_child() - * may be called *after* pgpool starting up. - */ - if (pipe_fds[0] > 0) - { - close(pipe_fds[0]); - close(pipe_fds[1]); - } - - myargv = save_ps_display_args(myargc, myargv); - - /* call child main */ - POOL_SETMASK(&UnBlockSig); - health_check_timer_expired = 0; - reload_config_request = 0; - do_worker_child(); - } - else if (pid == -1) - { - pool_error("fork() failed. reason: %s", strerror(errno)); - myexit(1); - } - return pid; -} - -/* -* create inet domain socket -*/ -static int create_inet_domain_socket(const char *hostname, const int port) -{ - struct sockaddr_in addr; - int fd; - int status; - int one = 1; - int len; - int backlog; - - fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd == -1) - { - pool_error("Failed to create INET domain socket. reason: %s", strerror(errno)); - myexit(1); - } - if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, - sizeof(one))) == -1) - { - pool_error("setsockopt() failed. reason: %s", strerror(errno)); - myexit(1); - } - - memset((char *) &addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - - if (strcmp(hostname, "*")==0) - { - addr.sin_addr.s_addr = htonl(INADDR_ANY); - } - else - { - struct hostent *hostinfo; - - hostinfo = gethostbyname(hostname); - if (!hostinfo) - { - pool_error("could not resolve host name \"%s\": %s", hostname, hstrerror(h_errno)); - myexit(1); - } - addr.sin_addr = *(struct in_addr *) hostinfo->h_addr; - } - - addr.sin_port = htons(port); - len = sizeof(struct sockaddr_in); - status = bind(fd, (struct sockaddr *)&addr, len); - if (status == -1) - { - char *host = "", *serv = ""; - char hostname[NI_MAXHOST], servname[NI_MAXSERV]; - if (getnameinfo((struct sockaddr *) &addr, len, hostname, sizeof(hostname), servname, sizeof(servname), 0) == 0) { - host = hostname; - serv = servname; - } - pool_error("bind(%s:%s) failed. reason: %s", host, serv, strerror(errno)); - myexit(1); - } - - backlog = pool_config->num_init_children * 2; - if (backlog > PGPOOLMAXLITSENQUEUELENGTH) - backlog = PGPOOLMAXLITSENQUEUELENGTH; - - status = listen(fd, backlog); - if (status < 0) - { - pool_error("listen() failed. reason: %s", strerror(errno)); - myexit(1); - } - return fd; -} - -/* -* create UNIX domain socket -*/ -static int create_unix_domain_socket(struct sockaddr_un un_addr_tmp) -{ - struct sockaddr_un addr; - int fd; - int status; - int len; - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd == -1) - { - pool_error("Failed to create UNIX domain socket. reason: %s", strerror(errno)); - myexit(1); - } - memset((char *) &addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", un_addr_tmp.sun_path); - len = sizeof(struct sockaddr_un); - status = bind(fd, (struct sockaddr *)&addr, len); - if (status == -1) - { - pool_error("bind(%s) failed. reason: %s", addr.sun_path, strerror(errno)); - myexit(1); - } - - if (chmod(un_addr_tmp.sun_path, 0777) == -1) - { - pool_error("chmod() failed. reason: %s", strerror(errno)); - myexit(1); - } - - status = listen(fd, PGPOOLMAXLITSENQUEUELENGTH); - if (status < 0) - { - pool_error("listen() failed. reason: %s", strerror(errno)); - myexit(1); - } - return fd; -} - -static void myunlink(const char* path) -{ - if (unlink(path) == 0) return; - pool_error("unlink(%s) failed: %s", path, strerror(errno)); -} - -static void myexit(int code) -{ - int i; - - if (getpid() != mypid) - return; - - if (process_info != NULL) { - POOL_SETMASK(&AuthBlockSig); - exiting = 1; - for (i = 0; i < pool_config->num_init_children; i++) - { - pid_t pid = process_info[i].pid; - if (pid) - { - kill(pid, SIGTERM); - } - } - - /* wait for all children to exit */ - while (wait(NULL) > 0) - ; - if (errno != ECHILD) - pool_error("wait() failed. reason:%s", strerror(errno)); - POOL_SETMASK(&UnBlockSig); - } - - myunlink(un_addr.sun_path); - myunlink(pcp_un_addr.sun_path); - myunlink(pool_config->pid_file_name); - - write_status_file(); - - pool_shmem_exit(code); - exit(code); -} - -void notice_backend_error(int node_id) -{ - int n = node_id; - - if (getpid() == mypid) - { - pool_log("notice_backend_error: called from pgpool main. ignored."); - } - else - { - degenerate_backend_set(&n, 1); - } -} - -/* notice backend connection error using SIGUSR1 */ -void degenerate_backend_set(int *node_id_set, int count) -{ - pid_t parent = getppid(); - int i; - bool need_signal = false; -#ifdef HAVE_SIGPROCMASK - sigset_t oldmask; -#else - int oldmask; -#endif - - if (pool_config->parallel_mode) - { - return; - } - - POOL_SETMASK2(&BlockSig, &oldmask); - pool_semaphore_lock(REQUEST_INFO_SEM); - Req_info->kind = NODE_DOWN_REQUEST; - for (i = 0; i < count; i++) - { - if (node_id_set[i] < 0 || node_id_set[i] >= MAX_NUM_BACKENDS || - !VALID_BACKEND(node_id_set[i])) - { - pool_log("degenerate_backend_set: node %d is not valid backend.", i); - continue; - } - - if (POOL_DISALLOW_TO_FAILOVER(BACKEND_INFO(node_id_set[i]).flag)) - { - pool_log("degenerate_backend_set: %d failover request from pid %d is canceled because failover is disallowed", node_id_set[i], getpid()); - continue; - } - - pool_log("degenerate_backend_set: %d fail over request from pid %d", node_id_set[i], getpid()); - Req_info->node_id[i] = node_id_set[i]; - need_signal = true; - } - - if (need_signal) - { - if (!pool_config->use_watchdog || WD_OK == wd_degenerate_backend_set(node_id_set, count)) - { - kill(parent, SIGUSR1); - } - else - { - pool_log("degenerate_backend_set: failover request from pid %d is canceled by other pgpool", getpid()); - memset(Req_info->node_id, -1, sizeof(int) * MAX_NUM_BACKENDS); - } - } - - pool_semaphore_unlock(REQUEST_INFO_SEM); - POOL_SETMASK(&oldmask); -} - -/* send promote node request using SIGUSR1 */ -void promote_backend(int node_id) -{ - pid_t parent = getppid(); - - if (!MASTER_SLAVE || strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP)) - { - return; - } - - if (node_id < 0 || node_id >= MAX_NUM_BACKENDS || !VALID_BACKEND(node_id)) - { - pool_error("promote_backend: node %d is not valid backend.", node_id); - return; - } - - pool_semaphore_lock(REQUEST_INFO_SEM); - Req_info->kind = PROMOTE_NODE_REQUEST; - Req_info->node_id[0] = node_id; - pool_log("promote_backend: %d promote node request from pid %d", node_id, getpid()); - - if (!pool_config->use_watchdog || WD_OK == wd_promote_backend(node_id)) - { - kill(parent, SIGUSR1); - } - else - { - pool_log("promote_backend: promote request from pid %d is canceled by other pgpool", getpid()); - Req_info->node_id[0] = -1; - } - - pool_semaphore_unlock(REQUEST_INFO_SEM); -} - -/* send failback request using SIGUSR1 */ -void send_failback_request(int node_id) -{ - pid_t parent = getppid(); - - pool_log("send_failback_request: fail back %d th node request from pid %d", node_id, getpid()); - Req_info->kind = NODE_UP_REQUEST; - Req_info->node_id[0] = node_id; - - if (node_id < 0 || node_id >= MAX_NUM_BACKENDS || - (RAW_MODE && BACKEND_INFO(node_id).backend_status != CON_DOWN && VALID_BACKEND(node_id))) - { - pool_error("send_failback_request: node %d is alive.", node_id); - Req_info->node_id[0] = -1; - return; - } - - if (pool_config->use_watchdog && WD_OK != wd_send_failback_request(node_id)) - { - pool_log("send_failback_request: failback request from pid %d is canceled by other pgpool", getpid()); - Req_info->node_id[0] = -1; - return; - } - kill(parent, SIGUSR1); -} - -static RETSIGTYPE exit_handler(int sig) -{ - int i; - - POOL_SETMASK(&AuthBlockSig); - - /* - * this could happen in a child process if a signal has been sent - * before resetting signal handler - */ - if (getpid() != mypid) - { - pool_debug("exit_handler: I am not parent"); - POOL_SETMASK(&UnBlockSig); - pool_shmem_exit(0); - exit(0); - } - - if (sig == SIGTERM) - pool_log("received smart shutdown request"); - else if (sig == SIGINT) - pool_log("received fast shutdown request"); - else if (sig == SIGQUIT) - pool_log("received immediate shutdown request"); - else - { - pool_error("exit_handler: unknown signal received %d", sig); - POOL_SETMASK(&UnBlockSig); - return; - } - - exiting = 1; - - for (i = 0; i < pool_config->num_init_children; i++) - { - pid_t pid = process_info[i].pid; - if (pid) - { - kill(pid, sig); - } - } - - kill(pcp_pid, sig); - kill(worker_pid, sig); - - if (pool_config->use_watchdog) - { - wd_kill_watchdog(sig); - } - - POOL_SETMASK(&UnBlockSig); - - while (wait(NULL) > 0) - ; - - if (errno != ECHILD) - pool_error("wait() failed. reason:%s", strerror(errno)); - - process_info = NULL; - myexit(0); -} - -/* - * Calculate next valid master node id. - * If no valid node found, returns -1. - */ -static int get_next_master_node(void) -{ - int i; - - for (i=0;i<pool_config->backend_desc->num_backends;i++) - { - /* - * Do not use VALID_BACKEND macro in raw mode. - * VALID_BACKEND return true only if the argument is master - * node id. In other words, standby nodes are false. So need - * to check backend status with VALID_BACKEND_RAW. - */ - if (RAW_MODE) - { - if (VALID_BACKEND_RAW(i)) - break; - } - else - { - if (VALID_BACKEND(i)) - break; - } - } - - if (i == pool_config->backend_desc->num_backends) - i = -1; - - return i; -} - -/* - * handle SIGUSR1 - * - */ -static RETSIGTYPE failover_handler(int sig) -{ - POOL_SETMASK(&BlockSig); - failover_request = 1; - write(pipe_fds[1], "\0", 1); - POOL_SETMASK(&UnBlockSig); -} - -/* - * backend connection error, failover/failback request, if possible - * failover() must be called under protecting signals. - */ -static void failover(void) -{ - int i; - int node_id; - bool by_health_check; - int new_master; - int new_primary; - int nodes[MAX_NUM_BACKENDS]; - bool need_to_restart_children; - int status; - int sts; - - pool_debug("failover_handler called"); - - memset(nodes, 0, sizeof(int) * MAX_NUM_BACKENDS); - - /* - * this could happen in a child process if a signal has been sent - * before resetting signal handler - */ - if (getpid() != mypid) - { - pool_debug("failover_handler: I am not parent"); - kill(pcp_pid, SIGUSR2); - return; - } - - /* - * processing SIGTERM, SIGINT or SIGQUIT - */ - if (exiting) - { - pool_debug("failover_handler called while exiting"); - kill(pcp_pid, SIGUSR2); - return; - } - - /* - * processing fail over or switch over - */ - if (switching) - { - pool_debug("failover_handler called while switching"); - kill(pcp_pid, SIGUSR2); - return; - } - - pool_semaphore_lock(REQUEST_INFO_SEM); - - if (Req_info->kind == CLOSE_IDLE_REQUEST) - { - pool_semaphore_unlock(REQUEST_INFO_SEM); - kill_all_children(SIGUSR1); - kill(pcp_pid, SIGUSR2); - return; - } - - /* - * if not in replication mode/master slave mode, we treat this a restart request. - * otherwise we need to check if we have already failovered. - */ - pool_debug("failover_handler: starting to select new master node"); - switching = 1; - Req_info->switching = true; - node_id = Req_info->node_id[0]; - - /* start of command inter-lock with watchdog */ - if (pool_config->use_watchdog) - { - by_health_check = (!failover_request && Req_info->kind==NODE_DOWN_REQUEST); - wd_start_interlock(by_health_check); - } - - /* failback request? */ - if (Req_info->kind == NODE_UP_REQUEST) - { - if (node_id < 0 || node_id >= MAX_NUM_BACKENDS || - (Req_info->kind == NODE_UP_REQUEST && !(RAW_MODE && - BACKEND_INFO(node_id).backend_status == CON_DOWN) && VALID_BACKEND(node_id)) || - (Req_info->kind == NODE_DOWN_REQUEST && !VALID_BACKEND(node_id))) - { - pool_semaphore_unlock(REQUEST_INFO_SEM); - - if (node_id < 0 || node_id >= MAX_NUM_BACKENDS) - pool_error("failover_handler: invalid node_id %d MAX_NUM_BACKENDS: %d", node_id, MAX_NUM_BACKENDS); - else - pool_error("failover_handler: invalid node_id %d status:%d MAX_NUM_BACKENDS: %d", node_id, - BACKEND_INFO(node_id).backend_status, MAX_NUM_BACKENDS); - kill(pcp_pid, SIGUSR2); - switching = 0; - Req_info->switching = false; - - /* end of command inter-lock */ - if (pool_config->use_watchdog) - wd_leave_interlock(); - - return; - } - - pool_log("starting fail back. reconnect host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - BACKEND_INFO(node_id).backend_status = CON_CONNECT_WAIT; /* unset down status */ - - /* wait for failback command lock or to be lock holder */ - if (pool_config->use_watchdog && !wd_am_I_lock_holder()) - { - wd_wait_for_lock(WD_FAILBACK_COMMAND_LOCK); - } - /* execute failback command if lock holder */ - if (!pool_config->use_watchdog || wd_am_I_lock_holder()) - { - trigger_failover_command(node_id, pool_config->failback_command, - MASTER_NODE_ID, get_next_master_node(), PRIMARY_NODE_ID); - - /* unlock failback command */ - if (pool_config->use_watchdog) - wd_unlock(WD_FAILBACK_COMMAND_LOCK); - } - } - else if (Req_info->kind == PROMOTE_NODE_REQUEST) - { - if (node_id != -1 && VALID_BACKEND(node_id)) - { - pool_log("starting promotion. promote host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - } - else - { - pool_log("failover: no backends are promoted"); - pool_semaphore_unlock(REQUEST_INFO_SEM); - kill(pcp_pid, SIGUSR2); - switching = 0; - Req_info->switching = false; - - /* end of command inter-lock */ - if (pool_config->use_watchdog) - wd_leave_interlock(); - - return; - } - } - else - { - int cnt = 0; - - for (i = 0; i < MAX_NUM_BACKENDS; i++) - { - if (Req_info->node_id[i] != -1 && - ((RAW_MODE && VALID_BACKEND_RAW(Req_info->node_id[i])) || - VALID_BACKEND(Req_info->node_id[i]))) - { - pool_log("starting degeneration. shutdown host %s(%d)", - BACKEND_INFO(Req_info->node_id[i]).backend_hostname, - BACKEND_INFO(Req_info->node_id[i]).backend_port); - - BACKEND_INFO(Req_info->node_id[i]).backend_status = CON_DOWN; /* set down status */ - /* save down node */ - nodes[Req_info->node_id[i]] = 1; - cnt++; - } - } - - if (cnt == 0) - { - pool_log("failover: no backends are degenerated"); - pool_semaphore_unlock(REQUEST_INFO_SEM); - kill(pcp_pid, SIGUSR2); - switching = 0; - Req_info->switching = false; - - /* end of command inter-lock */ - if (pool_config->use_watchdog) - wd_leave_interlock(); - - return; - } - } - - new_master = get_next_master_node(); - - if (new_master < 0) - { - pool_error("failover_handler: no valid DB node found"); - } - -/* - * Before we tried to minimize restarting pgpool to protect existing - * connections from clients to pgpool children. What we did here was, - * if children other than master went down, we did not fail over. - * This is wrong. Think about following scenario. If someone - * accidentally plugs out the network cable, the TCP/IP stack keeps - * retrying for long time (typically 2 hours). The only way to stop - * the retry is restarting the process. Bottom line is, we need to - * restart all children in any case. See pgpool-general list posting - * "TCP connections are *not* closed when a backend timeout" on Jul 13 - * 2008 for more details. - */ -#ifdef NOT_USED - else - { - if (Req_info->master_node_id == new_master && *InRecovery == RECOVERY_INIT) - { - pool_log("failover_handler: do not restart pgpool. same master node %d was selected", new_master); - if (Req_info->kind == NODE_UP_REQUEST) - { - pool_log("failback done. reconnect host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - } - else - { - pool_log("failover done. shutdown host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - } - - /* exec failover_command */ - for (i = 0; i < pool_config->backend_desc->num_backends; i++) - { - if (nodes[i]) - trigger_failover_command(i, pool_config->failover_command); - } - - pool_semaphore_unlock(REQUEST_INFO_SEM); - switching = 0; - Req_info->switching = false; - kill(pcp_pid, SIGUSR2); - switching = 0; - Req_info->switching = false; - return; - } - } -#endif - - - /* On 2011/5/2 Tatsuo Ishii says: if mode is streaming replication - * and request is NODE_UP_REQUEST(failback case) we don't need to - * restart all children. Existing session will not use newly - * attached node, but load balanced node is not changed until this - * session ends, so it's harmless anyway. - */ - if (MASTER_SLAVE && !strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP) && - Req_info->kind == NODE_UP_REQUEST) - { - pool_log("Do not restart children because we are failbacking node id %d host%s port:%d and we are in streaming replication mode", node_id, - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - - need_to_restart_children = false; - } - else - { - pool_log("Restart all children"); - - /* kill all children */ - for (i = 0; i < pool_config->num_init_children; i++) - { - pid_t pid = process_info[i].pid; - if (pid) - { - kill(pid, SIGQUIT); - pool_debug("failover_handler: kill %d", pid); - } - } - - need_to_restart_children = true; - } - - /* wait for failover command lock or to be lock holder*/ - if (pool_config->use_watchdog && !wd_am_I_lock_holder()) - { - wd_wait_for_lock(WD_FAILOVER_COMMAND_LOCK); - } - - /* execute failover command if lock holder */ - if (!pool_config->use_watchdog || wd_am_I_lock_holder()) - { - /* Exec failover_command if needed */ - for (i = 0; i < pool_config->backend_desc->num_backends; i++) - { - if (nodes[i]) - trigger_failover_command(i, pool_config->failover_command, - MASTER_NODE_ID, new_master, PRIMARY_NODE_ID); - } - - /* unlock failover command */ - if (pool_config->use_watchdog) - wd_unlock(WD_FAILOVER_COMMAND_LOCK); - } - - -/* no need to wait since it will be done in reap_handler */ -#ifdef NOT_USED - while (wait(NULL) > 0) - ; - - if (errno != ECHILD) - pool_error("failover_handler: wait() failed. reason:%s", strerror(errno)); -#endif - - if (Req_info->kind == PROMOTE_NODE_REQUEST && VALID_BACKEND(node_id)) - new_primary = node_id; - - /* - * If the down node was a standby node in streaming replication - * mode, we can avoid calling find_primary_node_repeatedly() and - * recognize the former primary as the new primary node, which - * will reduce the time to process standby down. - */ - else if (MASTER_SLAVE && !strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP) && - Req_info->kind == NODE_DOWN_REQUEST) - { - if (Req_info->primary_node_id != node_id) - new_primary = Req_info->primary_node_id; - else - new_primary = find_primary_node_repeatedly(); - } - else - new_primary = find_primary_node_repeatedly(); - - /* - * If follow_master_command is provided and in master/slave - * streaming replication mode, we start degenerating all backends - * as they are not replicated anymore. - */ - int follow_cnt = 0; - if (MASTER_SLAVE && !strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP)) - { - if (*pool_config->follow_master_command != '\0' || - Req_info->kind == PROMOTE_NODE_REQUEST) - { - /* only if the failover is against the current primary */ - if (((Req_info->kind == NODE_DOWN_REQUEST) && - (nodes[Req_info->primary_node_id])) || - ((Req_info->kind == PROMOTE_NODE_REQUEST) && - (VALID_BACKEND(node_id)))) { - - for (i = 0; i < pool_config->backend_desc->num_backends; i++) - { - /* do not degenerate the new primary */ - if ((new_primary >= 0) && (i != new_primary)) { - BackendInfo *bkinfo; - bkinfo = pool_get_node_info(i); - pool_log("starting follow degeneration. shutdown host %s(%d)", - bkinfo->backend_hostname, - bkinfo->backend_port); - bkinfo->backend_status = CON_DOWN; /* set down status */ - follow_cnt++; - } - } - - if (follow_cnt == 0) - { - pool_log("failover: no follow backends are degenerated"); - } - else - { - /* update new master node */ - new_master = get_next_master_node(); - pool_log("failover: %d follow backends have been degenerated", follow_cnt); - } - } - } - } - - memset(Req_info->node_id, -1, sizeof(int) * MAX_NUM_BACKENDS); - pool_semaphore_unlock(REQUEST_INFO_SEM); - - /* wait for follow_master_command lock or to be lock holder */ - if (pool_config->use_watchdog && !wd_am_I_lock_holder()) - { - wd_wait_for_lock(WD_FOLLOW_MASTER_COMMAND_LOCK); - } - - /* execute follow_master_command */ - if (!pool_config->use_watchdog || wd_am_I_lock_holder()) - { - if ((follow_cnt > 0) && (*pool_config->follow_master_command != '\0')) - { - follow_pid = fork_follow_child(Req_info->master_node_id, new_primary, - Req_info->primary_node_id); - } - - /* unlock follow_master_command */ - if (pool_config->use_watchdog) - wd_unlock(WD_FOLLOW_MASTER_COMMAND_LOCK); - } - - /* end of command inter-lock */ - if (pool_config->use_watchdog) - wd_end_interlock(); - - /* Save primary node id */ - Req_info->primary_node_id = new_primary; - pool_log("failover: set new primary node: %d", Req_info->primary_node_id); - - if (new_master >= 0) - { - Req_info->master_node_id = new_master; - pool_log("failover: set new master node: %d", Req_info->master_node_id); - } - - - /* Fork the children if needed */ - if (need_to_restart_children) - { - for (i=0;i<pool_config->num_init_children;i++) - { - - /* - * Try to kill pgpool child because previous kill signal - * may not be received by pgpool child. This could happen - * if multiple PostgreSQL are going down (or even starting - * pgpool, without starting PostgreSQL can trigger this). - * Child calls degenerate_backend() and it tries to aquire - * semaphore to write a failover request. In this case the - * signal mask is set as well, thus signals are never - * received. - */ - kill(process_info[i].pid, SIGQUIT); - - process_info[i].pid = fork_a_child(unix_fd, inet_fd, i); - process_info[i].start_time = time(NULL); - } - } - else - { - /* Set restart request to each child. Children will exit(1) - * whenever they are idle to restart. - */ - for (i=0;i<pool_config->num_init_children;i++) - { - process_info[i].need_to_restart = 1; - } - } - - /* - * Send restart request to worker child. - */ - kill(worker_pid, SIGUSR1); - - if (Req_info->kind == NODE_UP_REQUEST) - { - pool_log("failback done. reconnect host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - } - else if (Req_info->kind == PROMOTE_NODE_REQUEST) - { - pool_log("promotion done. promoted host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - } - else - { - pool_log("failover done. shutdown host %s(%d)", - BACKEND_INFO(node_id).backend_hostname, - BACKEND_INFO(node_id).backend_port); - } - - switching = 0; - Req_info->switching = false; - - /* kick wakeup_handler in pcp_child to notice that - * failover/failback done - */ - kill(pcp_pid, SIGUSR2); - - sleep(1); - - /* - * Send restart request to pcp child. - */ - kill(pcp_pid, SIGUSR1); - for (;;) - { - sts = waitpid(pcp_pid, &status, 0); - if (sts != -1) - break; - if (sts == -1) - { - if (errno == EINTR) - continue; - else - { - pool_error("failover: waitpid failed. reason: %s", strerror(errno)); - return; - } - } - } - if (WIFSIGNALED(status)) - pool_log("PCP child %d exits with status %d by signal %d in failover()", pcp_pid, status, WTERMSIG(status)); - else - pool_log("PCP child %d exits with status %d in failover()", pcp_pid, status); - - pcp_pid = pcp_fork_a_child(pcp_unix_fd, pcp_inet_fd, pcp_conf_file); - pool_log("fork a new PCP child pid %d in failover()", pcp_pid); -} - -/* - * health check timer handler - */ -static RETSIGTYPE health_check_timer_handler(int sig) -{ - POOL_SETMASK(&BlockSig); - health_check_timer_expired = 1; - POOL_SETMASK(&UnBlockSig); -} - - -/* - * Check if we can connect to the backend - * returns 0 for OK. otherwise returns backend id + 1 - */ -static int health_check(void) -{ - POOL_CONNECTION_POOL_SLOT *slot; - BackendInfo *bkinfo; - static bool is_first = true; - static char *dbname; - int i; - - /* Do not execute health check during recovery */ - if (*InRecovery) - return 0; - - Retry: - /* - * First we try with "postgres" database. - */ - if (is_first) - dbname = "postgres"; - - for (i=0;i<pool_config->backend_desc->num_backends;i++) - { - /* - * Make sure that health check timer has not been expired. - * Before called health_check(), health_check_timer_expired is - * set to 0. However it is possible that while processing DB - * nodes health check timer expired. - */ - if (health_check_timer_expired) - { - pool_log("health_check: health check timer has been already expired before attempting to connect to %d th backend", i); - return i+1; - } - - bkinfo = pool_get_node_info(i); - - pool_debug("health_check: %d th DB node status: %d", i, bkinfo->backend_status); - - if (bkinfo->backend_status == CON_UNUSED || - bkinfo->backend_status == CON_DOWN) - continue; - - slot = make_persistent_db_connection(bkinfo->backend_hostname, - bkinfo->backend_port, - dbname, - pool_config->health_check_user, - pool_config->health_check_password, false); - - if (is_first) - is_first = false; - - if (!slot) - { - /* - * Retry with template1 unless health check timer is expired. - */ - if (!strcmp(dbname, "postgres") && health_check_timer_expired == 0) - { - dbname = "template1"; - goto Retry; - } - else - { - pool_error("health check failed. %d th host %s at port %d is down", - i, - bkinfo->backend_hostname, - bkinfo->backend_port); - return i+1; - } - } - else - { - discard_persistent_db_connection(slot); - } - } - - return 0; -} - -/* - * check if we can connect to the SystemDB - * returns 0 for OK. otherwise returns -1 - */ -static int -system_db_health_check(void) -{ - int fd; - - /* V2 startup packet */ - typedef struct { - int len; /* startup packet length */ - StartupPacket_v2 sp; - } MySp; - MySp mysp; - char kind; - - memset(&mysp, 0, sizeof(mysp)); - mysp.len = htonl(296); - mysp.sp.protoVersion = htonl(PROTO_MAJOR_V2 << 16); - strcpy(mysp.sp.database, "template1"); - strncpy(mysp.sp.user, SYSDB_INFO->user, sizeof(mysp.sp.user) - 1); - *mysp.sp.options = '\0'; - *mysp.sp.unused = '\0'; - *mysp.sp.tty = '\0'; - - pool_debug("health_check: SystemDB status: %d", SYSDB_STATUS); - - /* if SystemDB is already down, ignore */ - if (SYSDB_STATUS == CON_UNUSED || SYSDB_STATUS == CON_DOWN) - return 0; - - if (*SYSDB_INFO->hostname == '/') - fd = connect_unix_domain_socket_by_port(SYSDB_INFO->port, SYSDB_INFO->hostname, FALSE); - else - fd = connect_inet_domain_socket_by_port(SYSDB_INFO->hostname, SYSDB_INFO->port, FALSE); - - if (fd < 0) - { - pool_error("health check failed. SystemDB host %s at port %d is down", - SYSDB_INFO->hostname, - SYSDB_INFO->port); - - return -1; - } - - if (write(fd, &mysp, sizeof(mysp)) < 0) - { - pool_error("health check failed during write. SystemDB host %s at port %d is down", - SYSDB_INFO->hostname, - SYSDB_INFO->port); - close(fd); - return -1; - } - - read(fd, &kind, 1); - - if (write(fd, "X", 1) < 0) - { - pool_error("health check failed during write. SystemDB host %s at port %d is down", - SYSDB_INFO->hostname, - SYSDB_INFO->port); - close(fd); - return -1; - } - - close(fd); - return 0; -} - -/* - * handle SIGCHLD - */ -static RETSIGTYPE reap_handler(int sig) -{ - POOL_SETMASK(&BlockSig); - sigchld_request = 1; - write(pipe_fds[1], "\0", 1); - POOL_SETMASK(&UnBlockSig); -} - -/* - * Attach zombie processes and restart child processes. - * reaper() must be called protected from signals. - */ -static void reaper(void) -{ - pid_t pid; - int status; - int i; - - pool_debug("reap_handler called"); - - if (exiting) - { - pool_debug("reap_handler: exited due to exiting"); - return; - } - - if (switching) - { - pool_debug("reap_handler: exited due to switching"); - return; - } - - /* clear SIGCHLD request */ - sigchld_request = 0; - -#ifdef HAVE_WAITPID - pool_debug("reap_handler: call waitpid"); - while ((pid = waitpid(-1, &status, WNOHANG)) > 0) -#else - pool_debug("reap_handler: call wait3"); - while ((pid = wait3(&status, WNOHANG, NULL)) > 0) -#endif - { - if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) - { - /* Child terminated by segmentation fault. Report it */ - pool_error("Child process %d was terminated by segmentation fault", pid); - } - - /* if exiting child process was PCP handler */ - if (pid == pcp_pid) - { - if (WIFSIGNALED(status)) - pool_log("PCP child %d exits with status %d by signal %d", pid, status, WTERMSIG(status)); - else - pool_log("PCP child %d exits with status %d", pid, status); - - pcp_pid = pcp_fork_a_child(pcp_unix_fd, pcp_inet_fd, pcp_conf_file); - pool_log("fork a new PCP child pid %d", pcp_pid); - } - - /* exiting process was worker process */ - else if (pid == worker_pid) - { - if (WIFSIGNALED(status)) - pool_log("worker child %d exits with status %d by signal %d", pid, status, WTERMSIG(status)); - else - pool_log("worker child %d exits with status %d", pid, status); - - if (status) - worker_pid = worker_fork_a_child(); - - pool_log("fork a new worker child pid %d", worker_pid); - } - - /* exiting process was watchdog process */ - else if (pool_config->use_watchdog && wd_is_watchdog_pid(pid)) - { - if (!wd_reaper_watchdog(pid, status)) - { - pool_error("wd_reaper failed"); - myexit(1); - } - } - - else - { - if (WIFSIGNALED(status)) - pool_debug("child %d exits with status %d by signal %d", pid, status, WTERMSIG(status)); - else - pool_debug("child %d exits with status %d", pid, status); - - /* look for exiting child's pid */ - for (i=0;i<pool_config->num_init_children;i++) - { - if (pid == process_info[i].pid) - { - /* if found, fork a new child */ - if (!switching && !exiting && status) - { - process_info[i].pid = fork_a_child(unix_fd, inet_fd, i); - process_info[i].start_time = time(NULL); - pool_debug("fork a new child pid %d", process_info[i].pid); - break; - } - } - } - } - } - pool_debug("reap_handler: normally exited"); -} - -/* - * get node information specified by node_number - */ -BackendInfo * -pool_get_node_info(int node_number) -{ - if (node_number < 0 || node_number >= NUM_BACKENDS) - return NULL; - - return &BACKEND_INFO(node_number); -} - -/* - * get number of nodes - */ -int -pool_get_node_count(void) -{ - return NUM_BACKENDS; -} - -/* - * get process ids - */ -int * -pool_get_process_list(int *array_size) -{ - int *array; - int i; - - *array_size = pool_config->num_init_children; - array = calloc(*array_size, sizeof(int)); - for (i = 0; i < *array_size; i++) - array[i] = process_info[i].pid; - - return array; -} - -/* - * get process information specified by pid - */ -ProcessInfo * -pool_get_process_info(pid_t pid) -{ - int i; - - for (i = 0; i < pool_config->num_init_children; i++) - if (process_info[i].pid == pid) - return &process_info[i]; - - return NULL; -} - -/* - * get System DB information - */ -SystemDBInfo * -pool_get_system_db_info(void) -{ - if (system_db_info == NULL) - return NULL; - - return system_db_info->info; -} - - -/* - * handle SIGUSR2 - * Wakeup all processes - */ -static void wakeup_children(void) -{ - kill_all_children(SIGUSR2); -} - - -static RETSIGTYPE wakeup_handler(int sig) -{ - POOL_SETMASK(&BlockSig); - wakeup_request = 1; - write(pipe_fds[1], "\0", 1); - POOL_SETMASK(&UnBlockSig); -} - -/* - * handle SIGHUP - * - */ -static RETSIGTYPE reload_config_handler(int sig) -{ - POOL_SETMASK(&BlockSig); - reload_config_request = 1; - write(pipe_fds[1], "\0", 1); - POOL_SETMASK(&UnBlockSig); -} - -static void reload_config(void) -{ - pool_log("reload config files."); - pool_get_config(conf_file, RELOAD_CONFIG); - if (pool_config->enable_pool_hba) - load_hba(hba_file); - if (pool_config->parallel_mode) - pool_memset_system_db_info(system_db_info->info); - kill_all_children(SIGHUP); - - if (worker_pid) - kill(worker_pid, SIGHUP); -} - -static void kill_all_children(int sig) -{ - int i; - - /* kill all children */ - for (i = 0; i < pool_config->num_init_children; i++) - { - pid_t pid = process_info[i].pid; - if (pid) - { - kill(pid, sig); - } - } - - /* make PCP process reload as well */ - if (sig == SIGHUP) - kill(pcp_pid, sig); -} - -/* - * pause in a period specified by timeout. If any data is coming - * through pipe_fds[0], that means one of: failover request(SIGUSR1), - * SIGCHLD received, children wake up request(SIGUSR2 used in on line - * recovery processing) or config file reload request(SIGHUP) has been - * occurred. In this case this function returns 1. - * otherwise 0: (no signal event occurred), -1: (error) - * XXX: is it OK that select(2) error is ignored here? - */ -static int pool_pause(struct timeval *timeout) -{ - fd_set rfds; - int n; - char dummy; - - FD_ZERO(&rfds); - FD_SET(pipe_fds[0], &rfds); - n = select(pipe_fds[0]+1, &rfds, NULL, NULL, timeout); - if (n == 1) - read(pipe_fds[0], &dummy, 1); - return n; -} - -/* - * sleep for seconds specified by "second". Unlike pool_pause(), this - * function guarantees that it will sleep for specified seconds. This - * function uses pool_pause() internally. If it informs that there is - * a pending signal event, they are processed using CHECK_REQUEST - * macro. Note that most of these processes are done while all signals - * are blocked. - */ -void pool_sleep(unsigned int second) -{ - struct timeval current_time, sleep_time; - - gettimeofday(&current_time, NULL); - sleep_time.tv_sec = second + current_time.tv_sec; - sleep_time.tv_usec = current_time.tv_usec; - - POOL_SETMASK(&UnBlockSig); - while (sleep_time.tv_sec > current_time.tv_sec) - { - struct timeval timeout; - int r; - - timeout.tv_sec = sleep_time.tv_sec - current_time.tv_sec; - timeout.tv_usec = sleep_time.tv_usec - current_time.tv_usec; - if (timeout.tv_usec < 0) - { - timeout.tv_sec--; - timeout.tv_usec += 1000000; - } - - r = pool_pause(&timeout); - POOL_SETMASK(&BlockSig); - if (r > 0) - CHECK_REQUEST; - POOL_SETMASK(&UnBlockSig); - gettimeofday(&current_time, NULL); - } - POOL_SETMASK(&BlockSig); -} - -/* - * get_config_file_name: return full path of pgpool.conf. - */ -char *get_config_file_name(void) -{ - return conf_file; -} - -/* -- * get_config_file_name: return full path of pool_hba.conf. - */ -char *get_hba_file_name(void) ++ * get_hba_file_name: return full path of pool_hba.conf. + */ +char *get_hba_file_name(void) { return hba_file; } diff --cc src/main/pgpool_main.c index 576a4c3ac,000000000..5c73d02fa mode 100644,000000..100644 --- a/src/main/pgpool_main.c +++ b/src/main/pgpool_main.c @@@ -1,2383 -1,0 +1,2387 @@@ +/* -*-pgpool_main-c-*- */ +/* + * $Header$ + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Copyright (c) 2003-2013 PgPool Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + */ + +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> + +#include <sys/wait.h> + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include <signal.h> + +#include <libgen.h> +#include "utils/elog.h" +#include "utils/palloc.h" + +#include "pool.h" +#include "utils/palloc.h" +#include "utils/memutils.h" +#include "pool_config.h" +#include "context/pool_process_context.h" +#include "version.h" +#include "parser/pool_string.h" +#include "auth/pool_passwd.h" +#include "query_cache/pool_memqcache.h" +#include "watchdog/wd_ext.h" + +/* + * Process pending signal actions. + */ +#define CHECK_REQUEST \ + do { \ + if (wakeup_request) \ + { \ + wakeup_children(); \ + wakeup_request = 0; \ + } \ + if (failover_request) \ + { \ + failover(); \ + failover_request = 0; \ + } \ + if (sigchld_request) \ + { \ + reaper(); \ + } \ + if (reload_config_request) \ + { \ + reload_config(); \ + reload_config_request = 0; \ + } \ + } while (0) + +#define CLEAR_ALARM \ + do { \ + pool_debug("health check: clearing alarm"); \ + } while (alarm(0) > 0) + + +#define PGPOOLMAXLITSENQUEUELENGTH 10000 +static void FileUnlink(int code, Datum path); +static int write_status_file(void); +static pid_t pcp_fork_a_child(int unix_fd, int inet_fd, char *pcp_conf_file); +static pid_t fork_a_child(int unix_fd, int inet_fd, int id); +static pid_t worker_fork_a_child(void); +static int create_unix_domain_socket(struct sockaddr_un un_addr_tmp); +static int create_inet_domain_socket(const char *hostname, const int port); +static void myexit(int code); +static void failover(void); +static void reaper(void); +static void wakeup_children(void); +static void reload_config(void); +static int pool_pause(struct timeval *timeout); +static void kill_all_children(int sig); +static int get_next_master_node(void); +static pid_t fork_follow_child(int old_master, int new_primary, int old_primary); +static int read_status_file(bool discard_status); +static int write_status_file(void); +static RETSIGTYPE exit_handler(int sig); +static RETSIGTYPE reap_handler(int sig); +static RETSIGTYPE failover_handler(int sig); +static RETSIGTYPE reload_config_handler(int sig); +static RETSIGTYPE health_check_timer_handler(int sig); +static RETSIGTYPE wakeup_handler(int sig); + +static int health_check(void); +static void initialize_shared_mem_objects(void); +static int trigger_failover_command(int node, const char *command_line, + int old_master, int new_master, int old_primary); +static int find_primary_node(void); +static int find_primary_node_repeatedly(void); + +static struct sockaddr_un un_addr; /* unix domain socket path */ +static struct sockaddr_un pcp_un_addr; /* unix domain socket path for PCP */ +ProcessInfo *process_info; /* Per child info table on shmem */ + +/* + * Private copy of backend status + */ +BACKEND_STATUS private_backend_status[MAX_NUM_BACKENDS]; + +/* + * shmem connection info table + * this is a three dimension array. i.e.: + * con_info[pool_config->num_init_children][pool_config->max_pool][MAX_NUM_BACKENDS] + */ +ConnectionInfo *con_info; + +static int unix_fd; /* unix domain socket fd */ +static int inet_fd; /* inet domain socket fd */ + +static int follow_pid; /* pid for child process handling follow command */ +static int pcp_pid; /* pid for child process handling PCP */ +static int pcp_unix_fd; /* unix domain socket fd for PCP (not used) */ +static int pcp_inet_fd; /* inet domain socket fd for PCP */ +extern char pcp_conf_file[POOLMAXPATHLEN+1]; /* path for pcp.conf */ +extern char conf_file[POOLMAXPATHLEN+1]; +extern char hba_file[POOLMAXPATHLEN+1]; + +static int exiting = 0; /* non 0 if I'm exiting */ +static int switching = 0; /* non 0 if I'm fail overing or degenerating */ + +POOL_REQUEST_INFO *Req_info; /* request info area in shared memory */ +volatile sig_atomic_t *InRecovery; /* non 0 if recovery is started */ +volatile sig_atomic_t reload_config_request = 0; +static volatile sig_atomic_t failover_request = 0; +static volatile sig_atomic_t sigchld_request = 0; +static volatile sig_atomic_t wakeup_request = 0; + +static int pipe_fds[2]; /* for delivering signals */ + +int my_proc_id; + +static BackendStatusRecord backend_rec; /* Backend status record */ + +static pid_t worker_pid; /* pid of worker process */ + +BACKEND_STATUS* my_backend_status[MAX_NUM_BACKENDS]; /* Backend status buffer */ +int my_master_node_id; /* Master node id buffer */ + +/* +* pgpool main program +*/ +int PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) +{ + int i; + int retrycnt; + int sys_retrycnt; + bool retrying; + MemoryContext MainLoopMemoryContext; + + sigjmp_buf local_sigjmp_buf; + + /* Set the process type variable */ + processType = PT_MAIN; + + /* + * Restore previous backend status if possible + */ + read_status_file(discard_status); + + /* set unix domain socket path for connections to pgpool */ + snprintf(un_addr.sun_path, sizeof(un_addr.sun_path), "%s/.s.PGSQL.%d", + pool_config->socket_dir, + pool_config->port); + /* set unix domain socket path for pgpool PCP communication */ + snprintf(pcp_un_addr.sun_path, sizeof(pcp_un_addr.sun_path), "%s/.s.PGSQL.%d", + pool_config->pcp_socket_dir, + pool_config->pcp_port); + + /* set up signal handlers */ + pool_signal(SIGPIPE, SIG_IGN); + + /* create unix domain socket */ + unix_fd = create_unix_domain_socket(un_addr); + on_proc_exit(FileUnlink, (Datum) un_addr.sun_path); + + /* create inet domain socket if any */ + if (pool_config->listen_addresses[0]) + inet_fd = create_inet_domain_socket(pool_config->listen_addresses, pool_config->port); + + initialize_shared_mem_objects(); + + /* start watchdog */ + if (pool_config->use_watchdog ) + { + if (!wd_main(1)) + { + pool_error("wd_main error"); + myexit(1); + } + } + + /* + * We need to block signal here. Otherwise child might send some + * signals, for example SIGUSR1(fail over). Children will inherit + * signal blocking but they do unblock signals at the very beginning + * of process. So this is harmless. + */ + POOL_SETMASK(&BlockSig); + + /* fork the children */ + for (i=0;i<pool_config->num_init_children;i++) + { + process_info[i].pid = fork_a_child(unix_fd, inet_fd, i); + process_info[i].start_time = time(NULL); + } + + /* set up signal handlers */ + + pool_signal(SIGTERM, exit_handler); + pool_signal(SIGINT, exit_handler); + pool_signal(SIGQUIT, exit_handler); + pool_signal(SIGCHLD, reap_handler); + pool_signal(SIGUSR1, failover_handler); + pool_signal(SIGUSR2, wakeup_handler); + pool_signal(SIGHUP, reload_config_handler); + + /* create pipe for delivering event */ + if (pipe(pipe_fds) < 0) + { + ereport(FATAL, + (errmsg("failed to create pipe"))); + } + + MemoryContextSwitchTo(TopMemoryContext); + + /* Create per loop iteration memory context */ + MainLoopMemoryContext = AllocSetContextCreate(TopMemoryContext, + "pgpool_main_loop", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + ereport(LOG, + (errmsg("%s successfully started. version %s (%s)", PACKAGE, VERSION, PGPOOLVERSION))); + + /* fork a child for PCP handling */ + pcp_unix_fd = create_unix_domain_socket(pcp_un_addr); + /* Add onproc exit to clean up the unix domain socket at exit */ + on_proc_exit(FileUnlink, (Datum)pcp_un_addr.sun_path); + + /* maybe change "*" to pool_config->pcp_listen_addresses */ + pcp_inet_fd = create_inet_domain_socket("*", pool_config->pcp_port); + pcp_pid = pcp_fork_a_child(pcp_unix_fd, pcp_inet_fd, pcp_conf_file); + + /* Fork worker process */ + worker_pid = worker_fork_a_child(); + + retrycnt = 0; /* reset health check retry counter */ + sys_retrycnt = 0; /* reset SystemDB health check retry counter */ + retrying = false; + + /* Save primary node id */ + Req_info->primary_node_id = find_primary_node(); + + if (sigsetjmp(local_sigjmp_buf, 1) != 0) + { + /* Since not using PG_TRY, must reset error stack by hand */ + error_context_stack = NULL; + EmitErrorReport(); + FlushErrorState(); + } + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* This is the main loop */ + for (;;) + { + CHECK_REQUEST; + /* do we need health checking for PostgreSQL? */ + if (pool_config->health_check_period > 0) + { + int sts; + int sys_sts = 0; + unsigned int sleep_time; + unsigned int max_retries; + + /* rest per iteration memory context */ + MemoryContextSwitchTo(MainLoopMemoryContext); + MemoryContextResetAndDeleteChildren(MainLoopMemoryContext); + + if (retrycnt == 0) + ereport(LOG, + (errmsg("starting health check"))); + else + ereport(LOG, + (errmsg("health checking retry count %d", retrycnt))); + + if (pool_config->health_check_timeout > 0) + { + /* + * set health checker timeout. we want to detect + * communication path failure much earlier before + * TCP/IP stack detects it. + */ + CLEAR_ALARM; + pool_signal(SIGALRM, health_check_timer_handler); + alarm(pool_config->health_check_timeout); + } + /* + * do actual health check. trying to connect to the backend + */ + errno = 0; + health_check_timer_expired = 0; + POOL_SETMASK(&UnBlockSig); + sts = health_check(); + POOL_SETMASK(&BlockSig); + if (pool_config->parallel_mode) + sys_sts = system_db_health_check(); + + if ( (sts > 0 || sys_sts < 0) && (errno != EINTR || health_check_timer_expired)) + { + if(sts > 0 ) /* Backend connection failed */ + { + sts--; + retrycnt++; + pool_signal(SIGALRM, SIG_IGN); /* Cancel timer */ + CLEAR_ALARM; + + max_retries = pool_config->parallel_mode?NUM_BACKENDS:pool_config->health_check_max_retries; + + if (!pool_config->parallel_mode && POOL_DISALLOW_TO_FAILOVER(BACKEND_INFO(sts).flag)) + { + ereport(LOG, + (errmsg("health_check: %d failover is canceled because failover is disallowed", sts))); + } + if (retrycnt > max_retries) + { + /* retry count over */ + ereport(LOG, + (errmsg("set %d th backend down status", sts))); + Req_info->kind = NODE_DOWN_REQUEST; + Req_info->node_id[0] = sts; + health_check_timer_expired = 0; + failover(); + if (pool_config->parallel_mode) + retrycnt = 0; + retrying = false; + } + else + { + /* continue to retry */ + if (pool_config->parallel_mode) + sleep_time = pool_config->health_check_period/NUM_BACKENDS; + else + sleep_time = pool_config->health_check_retry_delay; + + ereport(DEBUG2, + (errmsg("retry sleep time: %d seconds", sleep_time))); + + pool_sleep(sleep_time); + continue; + } + } + + if (sys_sts < 0) /* SystemDB is down */ + { + sys_retrycnt++; + + pool_signal(SIGALRM, SIG_IGN); + CLEAR_ALARM; + + if (sys_retrycnt > NUM_BACKENDS) + { + ereport(LOG, + (errmsg("set SystemDB down status"))); + SYSDB_STATUS = CON_DOWN; + sys_retrycnt = 0; + } + else if (sts == 0) /* goes to sleep only when SystemDB alone was down */ + { + sleep_time = pool_config->health_check_period/NUM_BACKENDS; + pool_debug("retry sleep time: %d seconds", sleep_time); + pool_sleep(sleep_time); + continue; + } + } + } + else + { + /* success. reset retry count */ + retrycnt = 0; + if (retrying) + { + ereport(LOG, + (errmsg("after some retrying backend returned to healthy state"))); + retrying = false; + } + } + + if (pool_config->health_check_timeout > 0) + { + /* seems OK. cancel health check timer */ + pool_signal(SIGALRM, SIG_IGN); + CLEAR_ALARM; + } + + sleep_time = pool_config->health_check_period; + pool_sleep(sleep_time); + } + else /* Health Check is not enable and we have not much to do */ + { + for (;;) + { + int r; + struct timeval t = {3, 0}; + + POOL_SETMASK(&UnBlockSig); + r = pool_pause(&t); + POOL_SETMASK(&BlockSig); + if (r > 0) + break; + } + } + } +} + +/* + * fork a child for PCP + */ +pid_t pcp_fork_a_child(int unix_fd, int inet_fd, char *pcp_conf_file) +{ + pid_t pid; + + pid = fork(); + + if (pid == 0) + { + on_exit_reset(); + + close(pipe_fds[0]); + close(pipe_fds[1]); + + myargv = save_ps_display_args(myargc, myargv); + /* Set the process type variable */ + processType = PT_PCP; + + /* call PCP child main */ + POOL_SETMASK(&UnBlockSig); + health_check_timer_expired = 0; + reload_config_request = 0; + run_as_pcp_child = true; + pcp_do_child(unix_fd, inet_fd, pcp_conf_file); + } + else if (pid == -1) + { + ereport(FATAL, + (errmsg("fork() failed. reason: %s", strerror(errno)))); + } + + return pid; +} + +/* +* fork a child +*/ +pid_t fork_a_child(int unix_fd, int inet_fd, int id) +{ + pid_t pid; + + pid = fork(); + + if (pid == 0) + { + on_exit_reset(); + + /* Before we unconditionally closed pipe_fds[0] and pipe_fds[1] + * here, which is apparently wrong since in the start up of + * pgpool, pipe(2) is not called yet and it mistakenly closes + * fd 0. Now we check the fd > 0 before close(), expecting + * pipe returns fds greater than 0. Note that we cannot + * unconditionally remove close(2) calls since fork_a_child() + * may be called *after* pgpool starting up. + */ + if (pipe_fds[0] > 0) + { + close(pipe_fds[0]); + close(pipe_fds[1]); + } + + myargv = save_ps_display_args(myargc, myargv); + /* Set the process type variable */ + processType = PT_CHILD; + + /* call child main */ + POOL_SETMASK(&UnBlockSig); + health_check_timer_expired = 0; + reload_config_request = 0; + my_proc_id = id; + run_as_pcp_child = false; + do_child(unix_fd, inet_fd); + } + else if (pid == -1) + { + pool_error("fork() failed. reason: %s", strerror(errno)); + myexit(1); + } + + return pid; +} + +/* +* fork worker child process +*/ +pid_t worker_fork_a_child() +{ + pid_t pid; + + pid = fork(); + + if (pid == 0) + { + on_exit_reset(); + + /* Before we unconditionally closed pipe_fds[0] and pipe_fds[1] + * here, which is apparently wrong since in the start up of + * pgpool, pipe(2) is not called yet and it mistakenly closes + * fd 0. Now we check the fd > 0 before close(), expecting + * pipe returns fds greater than 0. Note that we cannot + * unconditionally remove close(2) calls since fork_a_child() + * may be called *after* pgpool starting up. + */ + if (pipe_fds[0] > 0) + { + close(pipe_fds[0]); + close(pipe_fds[1]); + } + + myargv = save_ps_display_args(myargc, myargv); + /* Set the process type variable */ + processType = PT_WORKER; + + /* call child main */ + POOL_SETMASK(&UnBlockSig); + health_check_timer_expired = 0; + reload_config_request = 0; + do_worker_child(); + } + else if (pid == -1) + { + myexit(1); + } + + return pid; +} + +/* +* create inet domain socket +*/ +static int create_inet_domain_socket(const char *hostname, const int port) +{ + struct sockaddr_in addr; + int fd; + int status; + int one = 1; + int len; + int backlog; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + { + ereport(FATAL, + (errmsg("failed to create INET domain socket"), + errdetail("socket error \"%s\"",strerror(errno)))); +// myexit(1); + } + if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, + sizeof(one))) == -1) + { + ereport(FATAL, + (errmsg("failed to create INET domain socket"), + errdetail("socket error \"%s\"",strerror(errno)))); + } + + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + + if (strcmp(hostname, "*")==0) + { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } + else + { + struct hostent *hostinfo; + + hostinfo = gethostbyname(hostname); + if (!hostinfo) + { + ereport(FATAL, + (errmsg("failed to create INET domain socket"), + errdetail("could not resolve hostname \"%s\": error \"%s\"",hostname,hstrerror(h_errno)))); + + } + addr.sin_addr = *(struct in_addr *) hostinfo->h_addr; + } + + addr.sin_port = htons(port); + len = sizeof(struct sockaddr_in); + status = bind(fd, (struct sockaddr *)&addr, len); + if (status == -1) + { + char *host = "", *serv = ""; + char hostname[NI_MAXHOST], servname[NI_MAXSERV]; + if (getnameinfo((struct sockaddr *) &addr, len, hostname, sizeof(hostname), servname, sizeof(servname), 0) == 0) + { + host = hostname; + serv = servname; + } + ereport(FATAL, + (errmsg("failed to create INET domain socket"), + errdetail("bind on host:\"%s\" server:\"%s\" failed with error \"%s\"",host, serv,strerror(errno)))); + } + + backlog = pool_config->num_init_children * 2; + if (backlog > PGPOOLMAXLITSENQUEUELENGTH) + backlog = PGPOOLMAXLITSENQUEUELENGTH; + + status = listen(fd, backlog); + if (status < 0) + ereport(FATAL, + (errmsg("failed to create INET domain socket"), + errdetail("listen on socket failed with error \"%s\"",strerror(errno)))); + + return fd; +} + +/* +* create UNIX domain socket +*/ +static int create_unix_domain_socket(struct sockaddr_un un_addr_tmp) +{ + struct sockaddr_un addr; + int fd; + int status; + int len; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + pool_error("Failed to create UNIX domain socket. reason: %s", strerror(errno)); + myexit(1); + } + memset((char *) &addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", un_addr_tmp.sun_path); + len = sizeof(struct sockaddr_un); + status = bind(fd, (struct sockaddr *)&addr, len); + if (status == -1) + { + pool_error("bind(%s) failed. reason: %s", addr.sun_path, strerror(errno)); + myexit(1); + } + + if (chmod(un_addr_tmp.sun_path, 0777) == -1) + { + pool_error("chmod() failed. reason: %s", strerror(errno)); + myexit(1); + } + + status = listen(fd, PGPOOLMAXLITSENQUEUELENGTH); + if (status < 0) + { + pool_error("listen() failed. reason: %s", strerror(errno)); + myexit(1); + } + return fd; +} + +static void myexit(int code) +{ + int i; + + if(processType != PT_MAIN) + return; + + if (process_info != NULL) { + POOL_SETMASK(&AuthBlockSig); + exiting = 1; + for (i = 0; i < pool_config->num_init_children; i++) + { + pid_t pid = process_info[i].pid; + if (pid) + { + kill(pid, SIGTERM); + } + } + + /* wait for all children to exit */ + while (wait(NULL) > 0); + if (errno != ECHILD) + pool_error("wait() failed. reason:%s", strerror(errno)); + POOL_SETMASK(&UnBlockSig); + } + + write_status_file(); + + proc_exit(code); + +} + +void notice_backend_error(int node_id) +{ + int n = node_id; + + if (getpid() == mypid) + { + pool_log("notice_backend_error: called from pgpool main. ignored."); + } + else + { + degenerate_backend_set(&n, 1); + } +} + +/* notice backend connection error using SIGUSR1 */ +void degenerate_backend_set(int *node_id_set, int count) +{ + pid_t parent = getppid(); + int i; + bool need_signal = false; +#ifdef HAVE_SIGPROCMASK + sigset_t oldmask; +#else + int oldmask; +#endif + + if (pool_config->parallel_mode) + { + return; + } + + POOL_SETMASK2(&BlockSig, &oldmask); + pool_semaphore_lock(REQUEST_INFO_SEM); + Req_info->kind = NODE_DOWN_REQUEST; + for (i = 0; i < count; i++) + { + if (node_id_set[i] < 0 || node_id_set[i] >= MAX_NUM_BACKENDS || + !VALID_BACKEND(node_id_set[i])) + { + pool_log("degenerate_backend_set: node %d is not valid backend.", i); + continue; + } + + if (POOL_DISALLOW_TO_FAILOVER(BACKEND_INFO(node_id_set[i]).flag)) + { + pool_log("degenerate_backend_set: %d failover request from pid %d is canceled because failover is disallowed", node_id_set[i], getpid()); + continue; + } + + pool_log("degenerate_backend_set: %d fail over request from pid %d", node_id_set[i], getpid()); + Req_info->node_id[i] = node_id_set[i]; + need_signal = true; + } + + if (need_signal) + { + if (!pool_config->use_watchdog || WD_OK == wd_degenerate_backend_set(node_id_set, count)) + { + kill(parent, SIGUSR1); + } + else + { + pool_log("degenerate_backend_set: failover request from pid %d is canceled by other pgpool", getpid()); + memset(Req_info->node_id, -1, sizeof(int) * MAX_NUM_BACKENDS); + } + } + + pool_semaphore_unlock(REQUEST_INFO_SEM); + POOL_SETMASK(&oldmask); +} + +/* send promote node request using SIGUSR1 */ +void promote_backend(int node_id) +{ + pid_t parent = getppid(); + + if (!MASTER_SLAVE || strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP)) + { + return; + } + + if (node_id < 0 || node_id >= MAX_NUM_BACKENDS || !VALID_BACKEND(node_id)) + { + pool_error("promote_backend: node %d is not valid backend.", node_id); + return; + } + + pool_semaphore_lock(REQUEST_INFO_SEM); + Req_info->kind = PROMOTE_NODE_REQUEST; + Req_info->node_id[0] = node_id; + pool_log("promote_backend: %d promote node request from pid %d", node_id, getpid()); + + if (!pool_config->use_watchdog || WD_OK == wd_promote_backend(node_id)) + { + kill(parent, SIGUSR1); + } + else + { + pool_log("promote_backend: promote request from pid %d is canceled by other pgpool", getpid()); + Req_info->node_id[0] = -1; + } + + pool_semaphore_unlock(REQUEST_INFO_SEM); +} + +/* send failback request using SIGUSR1 */ +void send_failback_request(int node_id) +{ + pid_t parent = getppid(); + + pool_log("send_failback_request: fail back %d th node request from pid %d", node_id, getpid()); + Req_info->kind = NODE_UP_REQUEST; + Req_info->node_id[0] = node_id; + + if (node_id < 0 || node_id >= MAX_NUM_BACKENDS || + (RAW_MODE && BACKEND_INFO(node_id).backend_status != CON_DOWN && VALID_BACKEND(node_id))) + { + pool_error("send_failback_request: node %d is alive.", node_id); + Req_info->node_id[0] = -1; + return; + } + + if (pool_config->use_watchdog && WD_OK != wd_send_failback_request(node_id)) + { + pool_log("send_failback_request: failback request from pid %d is canceled by other pgpool", getpid()); + Req_info->node_id[0] = -1; + return; + } + kill(parent, SIGUSR1); +} + +static RETSIGTYPE exit_handler(int sig) +{ + int i; + POOL_SETMASK(&AuthBlockSig); + + /* + * this could happen in a child process if a signal has been sent + * before resetting signal handler + */ + if (getpid() != mypid) + { + pool_debug("exit_handler: I am not parent"); + POOL_SETMASK(&UnBlockSig); + proc_exit(0); + } + + if (sig == SIGTERM) + pool_log("received smart shutdown request"); + else if (sig == SIGINT) + pool_log("received fast shutdown request"); + else if (sig == SIGQUIT) + pool_log("received immediate shutdown request"); + else + { + pool_error("exit_handler: unknown signal received %d", sig); + POOL_SETMASK(&UnBlockSig); + return; + } + exiting = 1; + + for (i = 0; i < pool_config->num_init_children; i++) + { + + pid_t pid = process_info[i].pid; + if (pid) + { + kill(pid, sig); + } + } + kill(pcp_pid, sig); + kill(worker_pid, sig); + + if (pool_config->use_watchdog) + { + wd_kill_watchdog(sig); + } + + POOL_SETMASK(&UnBlockSig); + + while (wait(NULL) > 0); + + if (errno != ECHILD) + pool_error("wait() failed. reason:%s", strerror(errno)); + + process_info = NULL; + myexit(0); +} + +/* + * Calculate next valid master node id. + * If no valid node found, returns -1. + */ +static int get_next_master_node(void) +{ + int i; + + for (i=0;i<pool_config->backend_desc->num_backends;i++) + { + /* + * Do not use VALID_BACKEND macro in raw mode. + * VALID_BACKEND return true only if the argument is master + * node id. In other words, standby nodes are false. So need + * to check backend status with VALID_BACKEND_RAW. + */ + if (RAW_MODE) + { + if (VALID_BACKEND_RAW(i)) + break; + } + else + { + if (VALID_BACKEND(i)) + break; + } + } + + if (i == pool_config->backend_desc->num_backends) + i = -1; + + return i; +} + +/* + * handle SIGUSR1 + * + */ +static RETSIGTYPE failover_handler(int sig) +{ + POOL_SETMASK(&BlockSig); + failover_request = 1; + write(pipe_fds[1], "\0", 1); + POOL_SETMASK(&UnBlockSig); +} + +/* + * backend connection error, failover/failback request, if possible + * failover() must be called under protecting signals. + */ +static void failover(void) +{ + int i; + int node_id; + bool by_health_check; + int new_master; + int new_primary; + int nodes[MAX_NUM_BACKENDS]; + bool need_to_restart_children; + int status; + int sts; + + pool_debug("failover_handler called"); + + memset(nodes, 0, sizeof(int) * MAX_NUM_BACKENDS); + + /* + * this could happen in a child process if a signal has been sent + * before resetting signal handler + */ + if (getpid() != mypid) + { + pool_debug("failover_handler: I am not parent"); + kill(pcp_pid, SIGUSR2); + return; + } + + /* + * processing SIGTERM, SIGINT or SIGQUIT + */ + if (exiting) + { + pool_debug("failover_handler called while exiting"); + kill(pcp_pid, SIGUSR2); + return; + } + + /* + * processing fail over or switch over + */ + if (switching) + { + pool_debug("failover_handler called while switching"); + kill(pcp_pid, SIGUSR2); + return; + } + + pool_semaphore_lock(REQUEST_INFO_SEM); + + if (Req_info->kind == CLOSE_IDLE_REQUEST) + { + pool_semaphore_unlock(REQUEST_INFO_SEM); + kill_all_children(SIGUSR1); + kill(pcp_pid, SIGUSR2); + return; + } + + /* + * if not in replication mode/master slave mode, we treat this a restart request. + * otherwise we need to check if we have already failovered. + */ + pool_debug("failover_handler: starting to select new master node"); + switching = 1; + Req_info->switching = true; + node_id = Req_info->node_id[0]; + + /* start of command inter-lock with watchdog */ + if (pool_config->use_watchdog) + { + by_health_check = (!failover_request && Req_info->kind==NODE_DOWN_REQUEST); + wd_start_interlock(by_health_check); + } + + /* failback request? */ + if (Req_info->kind == NODE_UP_REQUEST) + { - if (node_id >= MAX_NUM_BACKENDS || ++ if (node_id < 0 || node_id >= MAX_NUM_BACKENDS || + (Req_info->kind == NODE_UP_REQUEST && !(RAW_MODE && + BACKEND_INFO(node_id).backend_status == CON_DOWN) && VALID_BACKEND(node_id)) || + (Req_info->kind == NODE_DOWN_REQUEST && !VALID_BACKEND(node_id))) + { + pool_semaphore_unlock(REQUEST_INFO_SEM); - pool_error("failover_handler: invalid node_id %d status:%d MAX_NUM_BACKENDS: %d", node_id, - BACKEND_INFO(node_id).backend_status, MAX_NUM_BACKENDS); ++ ++ if (node_id < 0 || node_id >= MAX_NUM_BACKENDS) ++ pool_error("failover_handler: invalid node_id %d MAX_NUM_BACKENDS: %d", node_id, MAX_NUM_BACKENDS); ++ else ++ pool_error("failover_handler: invalid node_id %d status:%d MAX_NUM_BACKENDS: %d", node_id, ++ BACKEND_INFO(node_id).backend_status, MAX_NUM_BACKENDS); + kill(pcp_pid, SIGUSR2); + switching = 0; + Req_info->switching = false; + + /* end of command inter-lock */ + if (pool_config->use_watchdog) + wd_leave_interlock(); + + return; + } + + pool_log("starting fail back. reconnect host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + BACKEND_INFO(node_id).backend_status = CON_CONNECT_WAIT; /* unset down status */ + + /* wait for failback command lock or to be lock holder */ + if (pool_config->use_watchdog && !wd_am_I_lock_holder()) + { + wd_wait_for_lock(WD_FAILBACK_COMMAND_LOCK); + } + /* execute failback command if lock holder */ + if (!pool_config->use_watchdog || wd_am_I_lock_holder()) + { + trigger_failover_command(node_id, pool_config->failback_command, + MASTER_NODE_ID, get_next_master_node(), PRIMARY_NODE_ID); + + /* unlock failback command */ + if (pool_config->use_watchdog) + wd_unlock(WD_FAILBACK_COMMAND_LOCK); + } + } + else if (Req_info->kind == PROMOTE_NODE_REQUEST) + { + if (node_id != -1 && VALID_BACKEND(node_id)) + { + pool_log("starting promotion. promote host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + } + else + { + pool_log("failover: no backends are promoted"); + pool_semaphore_unlock(REQUEST_INFO_SEM); + kill(pcp_pid, SIGUSR2); + switching = 0; + Req_info->switching = false; + + /* end of command inter-lock */ + if (pool_config->use_watchdog) + wd_leave_interlock(); + + return; + } + } + else + { + int cnt = 0; + + for (i = 0; i < MAX_NUM_BACKENDS; i++) + { + if (Req_info->node_id[i] != -1 && + ((RAW_MODE && VALID_BACKEND_RAW(Req_info->node_id[i])) || + VALID_BACKEND(Req_info->node_id[i]))) + { + pool_log("starting degeneration. shutdown host %s(%d)", + BACKEND_INFO(Req_info->node_id[i]).backend_hostname, + BACKEND_INFO(Req_info->node_id[i]).backend_port); + + BACKEND_INFO(Req_info->node_id[i]).backend_status = CON_DOWN; /* set down status */ + /* save down node */ + nodes[Req_info->node_id[i]] = 1; + cnt++; + } + } + + if (cnt == 0) + { + pool_log("failover: no backends are degenerated"); + pool_semaphore_unlock(REQUEST_INFO_SEM); + kill(pcp_pid, SIGUSR2); + switching = 0; + Req_info->switching = false; + + /* end of command inter-lock */ + if (pool_config->use_watchdog) + wd_leave_interlock(); + + return; + } + } + + new_master = get_next_master_node(); + + if (new_master < 0) + { + pool_error("failover_handler: no valid DB node found"); + } + +/* + * Before we tried to minimize restarting pgpool to protect existing + * connections from clients to pgpool children. What we did here was, + * if children other than master went down, we did not fail over. + * This is wrong. Think about following scenario. If someone + * accidentally plugs out the network cable, the TCP/IP stack keeps + * retrying for long time (typically 2 hours). The only way to stop + * the retry is restarting the process. Bottom line is, we need to + * restart all children in any case. See pgpool-general list posting + * "TCP connections are *not* closed when a backend timeout" on Jul 13 + * 2008 for more details. + */ +#ifdef NOT_USED + else + { + if (Req_info->master_node_id == new_master && *InRecovery == RECOVERY_INIT) + { + pool_log("failover_handler: do not restart pgpool. same master node %d was selected", new_master); + if (Req_info->kind == NODE_UP_REQUEST) + { + pool_log("failback done. reconnect host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + } + else + { + pool_log("failover done. shutdown host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + } + + /* exec failover_command */ + for (i = 0; i < pool_config->backend_desc->num_backends; i++) + { + if (nodes[i]) + trigger_failover_command(i, pool_config->failover_command); + } + + pool_semaphore_unlock(REQUEST_INFO_SEM); + switching = 0; + Req_info->switching = false; + kill(pcp_pid, SIGUSR2); + switching = 0; + Req_info->switching = false; + return; + } + } +#endif + + + /* On 2011/5/2 Tatsuo Ishii says: if mode is streaming replication + * and request is NODE_UP_REQUEST(failback case) we don't need to + * restart all children. Existing session will not use newly + * attached node, but load balanced node is not changed until this + * session ends, so it's harmless anyway. + */ + if (MASTER_SLAVE && !strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP) && + Req_info->kind == NODE_UP_REQUEST) + { + pool_log("Do not restart children because we are failbacking node id %d host%s port:%d and we are in streaming replication mode", node_id, + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + + need_to_restart_children = false; + } + else + { + pool_log("Restart all children"); + + /* kill all children */ + for (i = 0; i < pool_config->num_init_children; i++) + { + pid_t pid = process_info[i].pid; + if (pid) + { + kill(pid, SIGQUIT); + pool_debug("failover_handler: kill %d", pid); + } + } + + need_to_restart_children = true; + } + + /* wait for failover command lock or to be lock holder*/ + if (pool_config->use_watchdog && !wd_am_I_lock_holder()) + { + wd_wait_for_lock(WD_FAILOVER_COMMAND_LOCK); + } + + /* execute failover command if lock holder */ + if (!pool_config->use_watchdog || wd_am_I_lock_holder()) + { + /* Exec failover_command if needed */ + for (i = 0; i < pool_config->backend_desc->num_backends; i++) + { + if (nodes[i]) + trigger_failover_command(i, pool_config->failover_command, + MASTER_NODE_ID, new_master, PRIMARY_NODE_ID); + } + + /* unlock failover command */ + if (pool_config->use_watchdog) + wd_unlock(WD_FAILOVER_COMMAND_LOCK); + } + + +/* no need to wait since it will be done in reap_handler */ +#ifdef NOT_USED + while (wait(NULL) > 0) + ; + + if (errno != ECHILD) + pool_error("failover_handler: wait() failed. reason:%s", strerror(errno)); +#endif + + if (Req_info->kind == PROMOTE_NODE_REQUEST && VALID_BACKEND(node_id)) + new_primary = node_id; + + /* + * If the down node was a standby node in streaming replication + * mode, we can avoid calling find_primary_node_repeatedly() and + * recognize the former primary as the new primary node, which + * will reduce the time to process standby down. + */ + else if (MASTER_SLAVE && !strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP) && + Req_info->kind == NODE_DOWN_REQUEST) + { + if (Req_info->primary_node_id != node_id) + new_primary = Req_info->primary_node_id; + else + new_primary = find_primary_node_repeatedly(); + } + else + new_primary = find_primary_node_repeatedly(); + + /* + * If follow_master_command is provided and in master/slave + * streaming replication mode, we start degenerating all backends + * as they are not replicated anymore. + */ + int follow_cnt = 0; + if (MASTER_SLAVE && !strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP)) + { + if (*pool_config->follow_master_command != '\0' || + Req_info->kind == PROMOTE_NODE_REQUEST) + { + /* only if the failover is against the current primary */ + if (((Req_info->kind == NODE_DOWN_REQUEST) && + (nodes[Req_info->primary_node_id])) || + ((Req_info->kind == PROMOTE_NODE_REQUEST) && + (VALID_BACKEND(node_id)))) { + + for (i = 0; i < pool_config->backend_desc->num_backends; i++) + { + /* do not degenerate the new primary */ + if ((new_primary >= 0) && (i != new_primary)) { + BackendInfo *bkinfo; + bkinfo = pool_get_node_info(i); + pool_log("starting follow degeneration. shutdown host %s(%d)", + bkinfo->backend_hostname, + bkinfo->backend_port); + bkinfo->backend_status = CON_DOWN; /* set down status */ + follow_cnt++; + } + } + + if (follow_cnt == 0) + { + pool_log("failover: no follow backends are degenerated"); + } + else + { + /* update new master node */ + new_master = get_next_master_node(); + pool_log("failover: %d follow backends have been degenerated", follow_cnt); + } + } + } + } + + memset(Req_info->node_id, -1, sizeof(int) * MAX_NUM_BACKENDS); + pool_semaphore_unlock(REQUEST_INFO_SEM); + + /* wait for follow_master_command lock or to be lock holder */ + if (pool_config->use_watchdog && !wd_am_I_lock_holder()) + { + wd_wait_for_lock(WD_FOLLOW_MASTER_COMMAND_LOCK); + } + + /* execute follow_master_command */ + if (!pool_config->use_watchdog || wd_am_I_lock_holder()) + { + if ((follow_cnt > 0) && (*pool_config->follow_master_command != '\0')) + { + follow_pid = fork_follow_child(Req_info->master_node_id, new_primary, + Req_info->primary_node_id); + } + + /* unlock follow_master_command */ + if (pool_config->use_watchdog) + wd_unlock(WD_FOLLOW_MASTER_COMMAND_LOCK); + } + + /* end of command inter-lock */ + if (pool_config->use_watchdog) + wd_end_interlock(); + + /* Save primary node id */ + Req_info->primary_node_id = new_primary; + pool_log("failover: set new primary node: %d", Req_info->primary_node_id); + + if (new_master >= 0) + { + Req_info->master_node_id = new_master; + pool_log("failover: set new master node: %d", Req_info->master_node_id); + } + + + /* Fork the children if needed */ + if (need_to_restart_children) + { + for (i=0;i<pool_config->num_init_children;i++) + { + + /* + * Try to kill pgpool child because previous kill signal + * may not be received by pgpool child. This could happen + * if multiple PostgreSQL are going down (or even starting + * pgpool, without starting PostgreSQL can trigger this). + * Child calls degenerate_backend() and it tries to aquire + * semaphore to write a failover request. In this case the + * signal mask is set as well, thus signals are never + * received. + */ + kill(process_info[i].pid, SIGQUIT); + + process_info[i].pid = fork_a_child(unix_fd, inet_fd, i); + process_info[i].start_time = time(NULL); + } + } + else + { + /* Set restart request to each child. Children will exit(1) + * whenever they are idle to restart. + */ + for (i=0;i<pool_config->num_init_children;i++) + { + process_info[i].need_to_restart = 1; + } + } + + /* + * Send restart request to worker child. + */ + kill(worker_pid, SIGUSR1); + + if (Req_info->kind == NODE_UP_REQUEST) + { + pool_log("failback done. reconnect host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + } + else if (Req_info->kind == PROMOTE_NODE_REQUEST) + { + pool_log("promotion done. promoted host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + } + else + { + pool_log("failover done. shutdown host %s(%d)", + BACKEND_INFO(node_id).backend_hostname, + BACKEND_INFO(node_id).backend_port); + } + + switching = 0; + Req_info->switching = false; + + /* kick wakeup_handler in pcp_child to notice that + * failover/failback done + */ + kill(pcp_pid, SIGUSR2); + + sleep(1); + + /* + * Send restart request to pcp child. + */ + kill(pcp_pid, SIGUSR1); + for (;;) + { + sts = waitpid(pcp_pid, &status, 0); + if (sts != -1) + break; + if (sts == -1) + { + if (errno == EINTR) + continue; + else + { + pool_error("failover: waitpid failed. reason: %s", strerror(errno)); + return; + } + } + } + if (WIFSIGNALED(status)) + pool_log("PCP child %d exits with status %d by signal %d in failover()", pcp_pid, status, WTERMSIG(status)); + else + pool_log("PCP child %d exits with status %d in failover()", pcp_pid, status); + + pcp_pid = pcp_fork_a_child(pcp_unix_fd, pcp_inet_fd, pcp_conf_file); + pool_log("fork a new PCP child pid %d in failover()", pcp_pid); +} + +/* + * health check timer handler + */ +static RETSIGTYPE health_check_timer_handler(int sig) +{ + POOL_SETMASK(&BlockSig); + health_check_timer_expired = 1; + POOL_SETMASK(&UnBlockSig); +} + + +/* + * Check if we can connect to the backend + * returns 0 for OK. otherwise returns backend id + 1 + */ +static int health_check(void) +{ + POOL_CONNECTION_POOL_SLOT *slot; + BackendInfo *bkinfo; + static bool is_first = true; + static char *dbname; + int i; + + /* Do not execute health check during recovery */ + if (*InRecovery) + return 0; + + Retry: + /* + * First we try with "postgres" database. + */ + if (is_first) + dbname = "postgres"; + + for (i=0;i<pool_config->backend_desc->num_backends;i++) + { + /* + * Make sure that health check timer has not been expired. + * Before called health_check(), health_check_timer_expired is + * set to 0. However it is possible that while processing DB + * nodes health check timer expired. + */ + if (health_check_timer_expired) + { + ereport(DEBUG1, + (errmsg("health check timer has been already expired before attempting to connect to %d th backend", i))); + return i+1; + } + + bkinfo = pool_get_node_info(i); + + ereport(DEBUG1, + (errmsg("health_check: %d th DB node status: %d", i, bkinfo->backend_status))); + + + if (bkinfo->backend_status == CON_UNUSED || + bkinfo->backend_status == CON_DOWN) + continue; + + slot = make_persistent_db_connection(bkinfo->backend_hostname, + bkinfo->backend_port, + dbname, + pool_config->health_check_user, + pool_config->health_check_password, false); + + if (is_first) + is_first = false; + + if (!slot) + { + /* + * Retry with template1 unless health check timer is expired. + */ + if (!strcmp(dbname, "postgres") && health_check_timer_expired == 0) + { + dbname = "template1"; + goto Retry; + } + else + { + pool_error("health check failed. %d th host %s at port %d is down", + i, + bkinfo->backend_hostname, + bkinfo->backend_port); + return i+1; + } + } + else + { + discard_persistent_db_connection(slot); + } + } + + return 0; +} + +/* + * handle SIGCHLD + */ +static RETSIGTYPE reap_handler(int sig) +{ + POOL_SETMASK(&BlockSig); + sigchld_request = 1; + write(pipe_fds[1], "\0", 1); + POOL_SETMASK(&UnBlockSig); +} + +/* + * Attach zombie processes and restart child processes. + * reaper() must be called protected from signals. + */ +static void reaper(void) +{ + pid_t pid; + int status; + int i; + + pool_debug("reap_handler called"); + + if (exiting) + { + pool_debug("reap_handler: exited due to exiting"); + return; + } + + if (switching) + { + pool_debug("reap_handler: exited due to switching"); + return; + } + + /* clear SIGCHLD request */ + sigchld_request = 0; + +#ifdef HAVE_WAITPID + pool_debug("reap_handler: call waitpid"); + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) +#else + pool_debug("reap_handler: call wait3"); + while ((pid = wait3(&status, WNOHANG, NULL)) > 0) +#endif + { + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) + { + /* Child terminated by segmentation fault. Report it */ + pool_error("Child process %d was terminated by segmentation fault", pid); + } + + /* if exiting child process was PCP handler */ + if (pid == pcp_pid) + { + if (WIFSIGNALED(status)) + pool_log("PCP child %d exits with status %d by signal %d", pid, status, WTERMSIG(status)); + else + pool_log("PCP child %d exits with status %d", pid, status); + + pcp_pid = pcp_fork_a_child(pcp_unix_fd, pcp_inet_fd, pcp_conf_file); + pool_log("fork a new PCP child pid %d", pcp_pid); + } + + /* exiting process was worker process */ + else if (pid == worker_pid) + { + if (WIFSIGNALED(status)) + pool_log("worker child %d exits with status %d by signal %d", pid, status, WTERMSIG(status)); + else + pool_log("worker child %d exits with status %d", pid, status); + + if (status) + worker_pid = worker_fork_a_child(); + + pool_log("fork a new worker child pid %d", worker_pid); + } + + /* exiting process was watchdog process */ + else if (pool_config->use_watchdog && wd_is_watchdog_pid(pid)) + { + if (!wd_reaper_watchdog(pid, status)) + { + pool_error("wd_reaper failed"); + myexit(1); + } + } + + else + { + if (WIFSIGNALED(status)) + pool_debug("child %d exits with status %d by signal %d", pid, status, WTERMSIG(status)); + else + pool_debug("child %d exits with status %d", pid, status); + + /* look for exiting child's pid */ + for (i=0;i<pool_config->num_init_children;i++) + { + if (pid == process_info[i].pid) + { + /* if found, fork a new child */ + if (!switching && !exiting && status) + { + process_info[i].pid = fork_a_child(unix_fd, inet_fd, i); + process_info[i].start_time = time(NULL); + pool_debug("fork a new child pid %d", process_info[i].pid); + break; + } + } + } + } + } + pool_debug("reap_handler: normally exited"); +} + +/* + * get node information specified by node_number + */ +BackendInfo * +pool_get_node_info(int node_number) +{ + if (node_number < 0 || node_number >= NUM_BACKENDS) + return NULL; + + return &BACKEND_INFO(node_number); +} + +/* + * get number of nodes + */ +int +pool_get_node_count(void) +{ + return NUM_BACKENDS; +} + +/* + * get process ids + */ +int * +pool_get_process_list(int *array_size) +{ + int *array; + int i; + + *array_size = pool_config->num_init_children; + array = palloc0(*array_size * sizeof(int)); + for (i = 0; i < *array_size; i++) + array[i] = process_info[i].pid; + + return array; +} + +/* + * get process information specified by pid + */ +ProcessInfo * +pool_get_process_info(pid_t pid) +{ + int i; + + for (i = 0; i < pool_config->num_init_children; i++) + if (process_info[i].pid == pid) + return &process_info[i]; + + return NULL; +} + +/* + * handle SIGUSR2 + * Wakeup all processes + */ +static void wakeup_children(void) +{ + kill_all_children(SIGUSR2); +} + + +static RETSIGTYPE wakeup_handler(int sig) +{ + POOL_SETMASK(&BlockSig); + wakeup_request = 1; + write(pipe_fds[1], "\0", 1); + POOL_SETMASK(&UnBlockSig); +} + +/* + * handle SIGHUP + * + */ +static RETSIGTYPE reload_config_handler(int sig) +{ + POOL_SETMASK(&BlockSig); + reload_config_request = 1; + write(pipe_fds[1], "\0", 1); + POOL_SETMASK(&UnBlockSig); +} + +static void kill_all_children(int sig) +{ + int i; + + /* kill all children */ + for (i = 0; i < pool_config->num_init_children; i++) + { + pid_t pid = process_info[i].pid; + if (pid) + { + kill(pid, sig); + } + } + + /* make PCP process reload as well */ + if (sig == SIGHUP) + kill(pcp_pid, sig); +} + +/* + * pause in a period specified by timeout. If any data is coming + * through pipe_fds[0], that means one of: failover request(SIGUSR1), + * SIGCHLD received, children wake up request(SIGUSR2 used in on line + * recovery processing) or config file reload request(SIGHUP) has been + * occurred. In this case this function returns 1. + * otherwise 0: (no signal event occurred), -1: (error) + * XXX: is it OK that select(2) error is ignored here? + */ +static int pool_pause(struct timeval *timeout) +{ + fd_set rfds; + int n; + char dummy; + + FD_ZERO(&rfds); + FD_SET(pipe_fds[0], &rfds); + n = select(pipe_fds[0]+1, &rfds, NULL, NULL, timeout); + if (n == 1) + read(pipe_fds[0], &dummy, 1); + return n; +} + +/* + * sleep for seconds specified by "second". Unlike pool_pause(), this + * function guarantees that it will sleep for specified seconds. This + * function uses pool_pause() internally. If it informs that there is + * a pending signal event, they are processed using CHECK_REQUEST + * macro. Note that most of these processes are done while all signals + * are blocked. + */ +void pool_sleep(unsigned int second) +{ + struct timeval current_time, sleep_time; + + gettimeofday(&current_time, NULL); + sleep_time.tv_sec = second + current_time.tv_sec; + sleep_time.tv_usec = current_time.tv_usec; + + POOL_SETMASK(&UnBlockSig); + while (sleep_time.tv_sec > current_time.tv_sec) + { + struct timeval timeout; + int r; + + timeout.tv_sec = sleep_time.tv_sec - current_time.tv_sec; + timeout.tv_usec = sleep_time.tv_usec - current_time.tv_usec; + if (timeout.tv_usec < 0) + { + timeout.tv_sec--; + timeout.tv_usec += 1000000; + } + + r = pool_pause(&timeout); + POOL_SETMASK(&BlockSig); + if (r > 0) + CHECK_REQUEST; + POOL_SETMASK(&UnBlockSig); + gettimeofday(&current_time, NULL); + } + POOL_SETMASK(&BlockSig); +} + +/* + * trigger_failover_command: execute specified command at failover. + * command_line is null-terminated string. + */ +static int trigger_failover_command(int node, const char *command_line, + int old_master, int new_master, int old_primary) +{ + int r = 0; + String *exec_cmd; + char port_buf[6]; + char buf[2]; + BackendInfo *info; + BackendInfo *newmaster; + if (command_line == NULL || (strlen(command_line) == 0)) + return 0; + + /* check failed nodeID */ + if (node < 0 || node > NUM_BACKENDS) + return -1; + + info = pool_get_node_info(node); + if (!info) + return -1; + + buf[1] = '\0'; + exec_cmd = init_string(""); + + while (*command_line) + { + if (*command_line == '%') + { + if (*(command_line + 1)) + { + char val = *(command_line + 1); + switch (val) + { + case 'p': /* failed node port */ + snprintf(port_buf, sizeof(port_buf), "%d", info->backend_port); + string_append_char(exec_cmd, port_buf); + break; + + case 'D': /* failed node database directory */ + string_append_char(exec_cmd, info->backend_data_directory); + break; + + case 'd': /* failed node id */ + snprintf(port_buf, sizeof(port_buf), "%d", node); + string_append_char(exec_cmd, port_buf); + break; + + case 'h': /* failed host name */ + string_append_char(exec_cmd, info->backend_hostname); + break; + + case 'H': /* new master host name */ + newmaster = pool_get_node_info(new_master); + if (newmaster) + string_append_char(exec_cmd, newmaster->backend_hostname); + else + /* no valid new master */ + string_append_char(exec_cmd, ""); + break; + + case 'm': /* new master node id */ + snprintf(port_buf, sizeof(port_buf), "%d", new_master); + string_append_char(exec_cmd, port_buf); + break; + + case 'r': /* new master port */ + newmaster = pool_get_node_info(get_next_master_node()); + if (newmaster) + { + snprintf(port_buf, sizeof(port_buf), "%d", newmaster->backend_port); + string_append_char(exec_cmd, port_buf); + } + else + /* no valid new master */ + string_append_char(exec_cmd, ""); + break; + + case 'R': /* new master database directory */ + newmaster = pool_get_node_info(get_next_master_node()); + if (newmaster) + string_append_char(exec_cmd, newmaster->backend_data_directory); + else + /* no valid new master */ + string_append_char(exec_cmd, ""); + break; + + case 'M': /* old master node id */ + snprintf(port_buf, sizeof(port_buf), "%d", old_master); + string_append_char(exec_cmd, port_buf); + break; + + case 'P': /* old primary node id */ + snprintf(port_buf, sizeof(port_buf), "%d", old_primary); + string_append_char(exec_cmd, port_buf); + break; + + case '%': /* escape */ + string_append_char(exec_cmd, "%"); + break; + + default: /* ignore */ + break; + } + command_line++; + } + } else { + buf[0] = *command_line; + string_append_char(exec_cmd, buf); + } + command_line++; + } + + if (strlen(exec_cmd->data) != 0) + { + pool_log("execute command: %s", exec_cmd->data); + r = system(exec_cmd->data); + } + + return r; +} + +/* + * Find the primary node (i.e. not standby node) and returns its node + * id. If no primary node is found, returns -1. + */ +static int find_primary_node(void) +{ + BackendInfo *bkinfo; + POOL_CONNECTION_POOL_SLOT *s; + POOL_CONNECTION *con; + POOL_STATUS status; + POOL_SELECT_RESULT *res; + bool is_standby; + int i; + + /* Streaming replication mode? */ + if (pool_config->master_slave_mode == 0 || + strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP)) + { + /* No point to look for primary node if not in streaming + * replication mode. + */ + pool_debug("find_primary_node: not in streaming replication mode"); + return -1; + } + + for(i=0;i<NUM_BACKENDS;i++) + { + if (!VALID_BACKEND(i)) + continue; + + /* + * Check to see if this is a standby node or not. + */ + is_standby = false; + + bkinfo = pool_get_node_info(i); + s = make_persistent_db_connection(bkinfo->backend_hostname, + bkinfo->backend_port, + "postgres", + pool_config->sr_check_user, + pool_config->sr_check_password, true); + if (!s) + { + pool_error("find_primary_node: make_persistent_connection failed"); + return -1; + } + con = s->con; + status = do_query(con, "SELECT pg_is_in_recovery()", + &res, PROTO_MAJOR_V3); + if (res->numrows <= 0) + { + pool_log("find_primary_node: do_query returns no rows"); + } + if (res->data[0] == NULL) + { + pool_log("find_primary_node: do_query returns no data"); + } + if (res->nullflags[0] == -1) + { + pool_log("find_primary_node: do_query returns NULL"); + } + if (res->data[0] && !strcmp(res->data[0], "t")) + { + is_standby = true; + } + free_select_result(res); + discard_persistent_db_connection(s); + + /* + * If this is a standby, we continue to look for primary node. + */ + if (is_standby) + { + pool_debug("find_primary_node: %d node is standby", i); + } + else + { + break; + } + } + + if (i == NUM_BACKENDS) + { + pool_debug("find_primary_node: no primary node found"); + return -1; + } + + pool_log("find_primary_node: primary node id is %d", i); + return i; +} + +static int find_primary_node_repeatedly(void) +{ + int sec; + int node_id = -1; + + /* Streaming replication mode? */ + if (pool_config->master_slave_mode == 0 || + strcmp(pool_config->master_slave_sub_mode, MODE_STREAMREP)) + { + /* No point to look for primary node if not in streaming + * replication mode. + */ + ereport(DEBUG1, + (errmsg("find_primary_node: not in streaming replication mode"))); + return -1; + } + + /* + * Try to find the new primary node and keep trying for + * search_primary_node_timeout seconds. + * search_primary_node_timeout = 0 means never timeout and keep searching + * indefinitely + */ + ereport(LOG, + (errmsg("find_primary_node_repeatedly: waiting for finding a primary node"))); + for (sec = 0; (pool_config->search_primary_node_timeout == 0 || + sec < pool_config->search_primary_node_timeout); sec++) + { + node_id = find_primary_node(); + if (node_id != -1) + break; + pool_sleep(1); + } + return node_id; +} + +/* +* fork a follow child +*/ +pid_t fork_follow_child(int old_master, int new_primary, int old_primary) +{ + pid_t pid; + int i; + + pid = fork(); + + if (pid == 0) + { + on_exit_reset(); + ereport(LOG, + (errmsg("start triggering follow command."))); + for (i = 0; i < pool_config->backend_desc->num_backends; i++) + { + BackendInfo *bkinfo; + bkinfo = pool_get_node_info(i); + if (bkinfo->backend_status == CON_DOWN) + trigger_failover_command(i, pool_config->follow_master_command, + old_master, new_primary, old_primary); + } + exit(0); + } + else if (pid == -1) + { + pool_error("follow fork() failed. reason: %s", strerror(errno)); + exit(1); + } + return pid; +} + + +static void initialize_shared_mem_objects() +{ + int size,i; + /* + * con_info is a 3 dimension array: i corresponds to pgpool child + * process, j corresponds to connection pool in each process and k + * corresponds to backends in each connection pool. + * + * XXX: Before 2010/4/12 this was a 2 dimension array: i + * corresponds to pgpool child process, j corresponds to + * connection pool in each process. Of course this was wrong. + */ + size = pool_coninfo_size(); + con_info = pool_shared_memory_create(size); + memset(con_info, 0, size); + + size = pool_config->num_init_children * (sizeof(ProcessInfo)); + process_info = pool_shared_memory_create(size); + memset(process_info, 0, size); + + for (i = 0; i < pool_config->num_init_children; i++) + { + process_info[i].connection_info = pool_coninfo(i,0,0); + } + + /* create fail over/switch over event area */ + Req_info = pool_shared_memory_create(sizeof(POOL_REQUEST_INFO)); + + /* + * Initialize backend status area. + * From now on, VALID_BACKEND macro can be used. + * (get_next_master_node() uses VALID_BACKEND) + */ + + for (i=0;i<MAX_NUM_BACKENDS;i++) + { + my_backend_status[i] = &(BACKEND_INFO(i).backend_status); + } + + /* initialize Req_info */ + Req_info->kind = NODE_UP_REQUEST; + memset(Req_info->node_id, -1, sizeof(int) * MAX_NUM_BACKENDS); + Req_info->master_node_id = get_next_master_node(); + Req_info->conn_counter = 0; + Req_info->switching = false; + + InRecovery = pool_shared_memory_create(sizeof(int)); + *InRecovery = RECOVERY_INIT; + + /* + * Initialize shared memory cache + */ + if (pool_config->memory_cache_enabled) + { + if (pool_is_shmem_cache()) + { + size_t size; + + size = pool_shared_memory_cache_size(); + pool_init_memory_cache(size); + + size = pool_shared_memory_fsmm_size(); + if (size == 0) + ereport(FATAL, + (errmsg("invalid shared memory size"), + errdetail("pool_shared_memory_fsmm_size error"))); + + pool_init_fsmm(size); + + pool_allocate_fsmm_clock_hand(); + + pool_discard_oid_maps(); + + ereport(LOG, + (errmsg("pool_discard_oid_maps: discarded memqcache oid maps"))); + + pool_hash_init(pool_config->memqcache_max_num_cache); + } + +#ifdef USE_MEMCACHED + else + { + if (clear_memcache_oidmaps) + { + pool_discard_oid_maps(); + ereport(LOG, + (errmsg("discarded memqcache oid maps"))); + } + else + { + ereport(DEBUG1, + (errmsg("skipped discarding memqcache oid maps"))); + } + } +#endif + + pool_init_memqcache_stats(); + } +} + +/* +* Read the status file +*/ +static int read_status_file(bool discard_status) +{ + FILE *fd; + char fnamebuf[POOLMAXPATHLEN]; + int i; + bool someone_wakeup = false; + + snprintf(fnamebuf, sizeof(fnamebuf), "%s/%s", pool_config->logdir, STATUS_FILE_NAME); + fd = fopen(fnamebuf, "r"); + if (!fd) + { + pool_log("Backend status file %s does not exist", fnamebuf); + return -1; + } + + /* + * If discard_status is true, unlink pgpool_status and + * do not restore previous status. + */ + if (discard_status) + { + fclose(fd); + if (unlink(fnamebuf) == 0) + { + pool_log("Backend status file %s discarded", fnamebuf); + } + else + { + pool_error("Failed to discard backend status file %s reason:%s", fnamebuf, strerror(errno)); + } + return 0; + } + + if (fread(&backend_rec, 1, sizeof(backend_rec), fd) != sizeof(backend_rec)) + { + pool_error("Could not read backend status file as %s. reason: %s", + fnamebuf, strerror(errno)); + fclose(fd); + return -1; + } + fclose(fd); + + for (i=0;i< pool_config->backend_desc->num_backends;i++) + { + if (backend_rec.status[i] == CON_DOWN) + { + BACKEND_INFO(i).backend_status = CON_DOWN; + pool_log("read_status_file: %d th backend is set to down status", i); + } + else + { + BACKEND_INFO(i).backend_status = CON_CONNECT_WAIT; + someone_wakeup = true; + } + } + + /* + * If no one woke up, we regard the status file bogus + */ + if (someone_wakeup == false) + { + for (i=0;i< pool_config->backend_desc->num_backends;i++) + { + BACKEND_INFO(i).backend_status = CON_CONNECT_WAIT; + } + } + + return 0; +} + +/* +* Write the pid file +*/ +static int write_status_file(void) +{ + FILE *fd; + char fnamebuf[POOLMAXPATHLEN]; + int i; + + snprintf(fnamebuf, sizeof(fnamebuf), "%s/%s", pool_config->logdir, STATUS_FILE_NAME); + fd = fopen(fnamebuf, "w"); + if (!fd) + { + pool_error("Could not open status file %s", fnamebuf); + return -1; + } + + memset(&backend_rec, 0, sizeof(backend_rec)); + + for (i=0;i< pool_config->backend_desc->num_backends;i++) + { + backend_rec.status[i] = BACKEND_INFO(i).backend_status; + } + + if (fwrite(&backend_rec, 1, sizeof(backend_rec), fd) != sizeof(backend_rec)) + { + pool_error("Could not write backend status file as %s. reason: %s", + fnamebuf, strerror(errno)); + fclose(fd); + return -1; + } + fclose(fd); + return 0; +} + + +static void reload_config(void) +{ + pool_log("reload config files."); + pool_get_config(conf_file, RELOAD_CONFIG); + if (pool_config->enable_pool_hba) + load_hba(hba_file); + if (pool_config->parallel_mode) + pool_memset_system_db_info(system_db_info->info); + kill_all_children(SIGHUP); + + if (worker_pid) + kill(worker_pid, SIGHUP); +} + +#ifdef NOT_USED +/* + * This is the function called by elog.c in case of + * Fatal error + */ +void +proc_exit(int code) +{ + if(processType == PT_CHILD) + child_exit(code); + else if(processType == PT_MAIN) + myexit(code); + exit(code); +} +#endif //NOT_USED + +/* Call back function to unlink the file */ +static void FileUnlink(int code, Datum path) +{ + char* filePath = (char*)path; + if (unlink(filePath) == 0) return; + /* + * We are already exiting the system just produce a log entry to report an error + */ + ereport(LOG, + (errmsg("unlink failed for file at path \"%s\"", filePath), + errdetail("%s", strerror(errno)))); +} [parent: d3704fc8443c]