/* * Discard connection and memory allocated by * make_persistent_db_connection(). */ void discard_persistent_db_connection(POOL_CONNECTION_POOL_SLOT *cp) { int len; if(cp == NULL) return; pool_write(cp->con, "X", 1); len = htonl(4); pool_write(cp->con, &len, sizeof(len)); /* * XXX we cannot call pool_flush() here since backend may already * close the socket and pool_flush() automatically invokes fail * over handler. This could happen in copy command (remember the * famous "lost synchronization with server, resetting * connection" message) */ pool_set_nonblock(cp->con->fd); pool_flush_it(cp->con); pool_unset_nonblock(cp->con->fd); pool_close(cp->con); free(cp->sp->startup_packet); free(cp->sp->database); free(cp->sp->user); free(cp->sp); free(cp); }
/* * Connect to PostgreSQL server by using INET domain socket. * If retry is true, retry to call connect() upon receiving EINTR error. */ int connect_inet_domain_socket_by_port(char *host, int port, bool retry) { int fd; int len; int on = 1; struct sockaddr_in addr; struct hostent *hp; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { pool_error("connect_inet_domain_socket_by_port: socket() failed: %s", strerror(errno)); return -1; } /* set nodelay */ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) < 0) { pool_error("connect_inet_domain_socket_by_port: setsockopt() failed: %s", strerror(errno)); close(fd); return -1; } memset((char *) &addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); len = sizeof(struct sockaddr_in); hp = gethostbyname(host); if ((hp == NULL) || (hp->h_addrtype != AF_INET)) { pool_error("connect_inet_domain_socket: gethostbyname() failed: %s host: %s", hstrerror(h_errno), host); close(fd); return -1; } memmove((char *) &(addr.sin_addr), (char *) hp->h_addr, hp->h_length); pool_set_nonblock(fd); for (;;) { if (exit_request) /* exit request already sent */ { pool_log("connect_inet_domain_socket_by_port: exit request has been sent"); close(fd); return -1; } if (health_check_timer_expired) /* has health check timer expired */ { pool_log("connect_inet_domain_socket_by_port: health check timer expired"); close(fd); return -1; } if (connect(fd, (struct sockaddr *)&addr, len) < 0) { if (errno == EISCONN) { /* Socket is already connected */ break; } if ((errno == EINTR && retry) || errno == EAGAIN) continue; /* Non block fd could return these */ if (errno == EINPROGRESS || errno == EALREADY) continue; pool_error("connect_inet_domain_socket: connect() failed: %s",strerror(errno)); close(fd); return -1; } break; } pool_unset_nonblock(fd); return fd; }
/* * child main loop */ void do_child(int unix_fd, int inet_fd) { POOL_CONNECTION *frontend; POOL_CONNECTION_POOL *backend; struct timeval now; struct timezone tz; struct timeval timeout; static int connected; /* non 0 if has been accepted connections from frontend */ int connections_count = 0; /* used if child_max_connections > 0 */ int found; char psbuf[NI_MAXHOST + 128]; pool_debug("I am %d", getpid()); /* Identify myself via ps */ init_ps_display("", "", "", ""); /* set up signal handlers */ signal(SIGALRM, SIG_DFL); signal(SIGTERM, die); signal(SIGINT, die); signal(SIGHUP, reload_config_handler); signal(SIGQUIT, die); signal(SIGCHLD, SIG_DFL); signal(SIGUSR1, close_idle_connection); signal(SIGUSR2, wakeup_handler); signal(SIGPIPE, SIG_IGN); #ifdef NONE_BLOCK /* set listen fds to none-blocking */ pool_set_nonblock(unix_fd); if (inet_fd) { pool_set_nonblock(inet_fd); } #endif /* Initialize my backend status */ pool_initialize_private_backend_status(); /* Initialize per process context */ pool_init_process_context(); /* initialize random seed */ gettimeofday(&now, &tz); #if defined(sun) || defined(__sun) srand((unsigned int) now.tv_usec); #else srandom((unsigned int) now.tv_usec); #endif /* initialize system db connection */ init_system_db_connection(); /* initialize connection pool */ if (pool_init_cp()) { child_exit(1); } /* * Open pool_passwd in child process. This is necessary to avoid the * file descriptor race condition reported in [pgpool-general: 1141]. */ if (strcmp("", pool_config->pool_passwd)) { pool_reopen_passwd_file(); } timeout.tv_sec = pool_config->child_life_time; timeout.tv_usec = 0; for (;;) { StartupPacket *sp; idle = 1; /* pgpool stop request already sent? */ check_stop_request(); /* Check if restart request is set because of failback event * happend. If so, exit myself with exit code 1 to be * restarted by pgpool parent. */ if (pool_get_my_process_info()->need_to_restart) { pool_log("do_child: failback event found. restart myself."); pool_get_my_process_info()->need_to_restart = 0; child_exit(1); } accepted = 0; /* perform accept() */ frontend = do_accept(unix_fd, inet_fd, &timeout); if (frontend == NULL) /* connection request from frontend timed out */ { /* check select() timeout */ if (connected && pool_config->child_life_time > 0 && timeout.tv_sec == 0 && timeout.tv_usec == 0) { pool_debug("child life %d seconds expired", pool_config->child_life_time); /* * Doesn't need to call this. child_exit() calls it. * send_frontend_exits(); */ child_exit(2); } continue; } /* set frontend fd to blocking */ pool_unset_nonblock(frontend->fd); /* reset busy flag */ idle = 0; /* check backend timer is expired */ if (backend_timer_expired) { pool_backend_timer(); backend_timer_expired = 0; } /* read the startup packet */ retry_startup: sp = read_startup_packet(frontend); if (sp == NULL) { /* failed to read the startup packet. return to the accept() loop */ pool_close(frontend); connection_count_down(); continue; } /* cancel request? */ if (sp->major == 1234 && sp->minor == 5678) { cancel_request((CancelPacket *)sp->startup_packet); pool_close(frontend); pool_free_startup_packet(sp); connection_count_down(); continue; } /* SSL? */ if (sp->major == 1234 && sp->minor == 5679 && !frontend->ssl_active) { pool_debug("SSLRequest from client"); pool_ssl_negotiate_serverclient(frontend); goto retry_startup; } if (pool_config->enable_pool_hba) { /* * do client authentication. * Note that ClientAuthentication does not return if frontend * was rejected; it simply terminates this process. */ frontend->protoVersion = sp->major; frontend->database = strdup(sp->database); if (frontend->database == NULL) { pool_error("do_child: strdup failed: %s\n", strerror(errno)); child_exit(1); } frontend->username = strdup(sp->user); if (frontend->username == NULL) { pool_error("do_child: strdup failed: %s\n", strerror(errno)); child_exit(1); } ClientAuthentication(frontend); } /* * Ok, negotiation with frontend has been done. Let's go to the * next step. Connect to backend if there's no existing * connection which can be reused by this frontend. * Authentication is also done in this step. */ /* Check if restart request is set because of failback event * happend. If so, close idle connections to backend and make * a new copy of backend status. */ if (pool_get_my_process_info()->need_to_restart) { pool_log("do_child: failback event found. discard existing connections"); pool_get_my_process_info()->need_to_restart = 0; close_idle_connection(0); pool_initialize_private_backend_status(); } /* * if there's no connection associated with user and database, * we need to connect to the backend and send the startup packet. */ /* look for existing connection */ found = 0; backend = pool_get_cp(sp->user, sp->database, sp->major, 1); if (backend != NULL) { found = 1; /* existing connection associated with same user/database/major found. * however we should make sure that the startup packet contents are identical. * OPTION data and others might be different. */ if (sp->len != MASTER_CONNECTION(backend)->sp->len) { pool_debug("do_child: connection exists but startup packet length is not identical"); found = 0; } else if(memcmp(sp->startup_packet, MASTER_CONNECTION(backend)->sp->startup_packet, sp->len) != 0) { pool_debug("do_child: connection exists but startup packet contents is not identical"); found = 0; } if (found == 0) { /* we need to discard existing connection since startup packet is different */ pool_discard_cp(sp->user, sp->database, sp->major); backend = NULL; } } if (backend == NULL) { /* create a new connection to backend */ if ((backend = connect_backend(sp, frontend)) == NULL) { connection_count_down(); continue; } } else { /* reuse existing connection */ if (!connect_using_existing_connection(frontend, backend, sp)) continue; } connected = 1; /* show ps status */ sp = MASTER_CONNECTION(backend)->sp; snprintf(psbuf, sizeof(psbuf), "%s %s %s idle", sp->user, sp->database, remote_ps_data); set_ps_display(psbuf, false); /* * Initialize per session context */ pool_init_session_context(frontend, backend); /* Mark this connection pool is connected from frontend */ pool_coninfo_set_frontend_connected(pool_get_process_context()->proc_id, pool_pool_index()); /* query process loop */ for (;;) { POOL_STATUS status; status = pool_process_query(frontend, backend, 0); sp = MASTER_CONNECTION(backend)->sp; switch (status) { /* client exits */ case POOL_END: /* * do not cache connection if: * pool_config->connection_cahe == 0 or * database name is template0, template1, postgres or regression */ if (pool_config->connection_cache == 0 || !strcmp(sp->database, "template0") || !strcmp(sp->database, "template1") || !strcmp(sp->database, "postgres") || !strcmp(sp->database, "regression")) { reset_connection(); pool_close(frontend); pool_send_frontend_exits(backend); pool_discard_cp(sp->user, sp->database, sp->major); } else { POOL_STATUS status1; /* send reset request to backend */ status1 = pool_process_query(frontend, backend, 1); pool_close(frontend); /* if we detect errors on resetting connection, we need to discard * this connection since it might be in unknown status */ if (status1 != POOL_CONTINUE) { pool_debug("error in resetting connections. discarding connection pools..."); pool_send_frontend_exits(backend); pool_discard_cp(sp->user, sp->database, sp->major); } else pool_connection_pool_timer(backend); } break; /* error occurred. discard backend connection pool and disconnect connection to the frontend */ case POOL_ERROR: pool_log("do_child: exits with status 1 due to error"); child_exit(1); break; /* fatal error occurred. just exit myself... */ case POOL_FATAL: notice_backend_error(1); child_exit(1); break; /* not implemented yet */ case POOL_IDLE: do_accept(unix_fd, inet_fd, &timeout); pool_debug("accept while idle"); break; default: break; } if (status != POOL_CONTINUE) break; } /* Destroy session context */ pool_session_context_destroy(); /* Mark this connection pool is not connected from frontend */ pool_coninfo_unset_frontend_connected(pool_get_process_context()->proc_id, pool_pool_index()); accepted = 0; connection_count_down(); timeout.tv_sec = pool_config->child_life_time; timeout.tv_usec = 0; /* increment queries counter if necessary */ if ( pool_config->child_max_connections > 0 ) connections_count++; /* check if maximum connections count for this child reached */ if ( ( pool_config->child_max_connections > 0 ) && ( connections_count >= pool_config->child_max_connections ) ) { pool_log("child exiting, %d connections reached", pool_config->child_max_connections); send_frontend_exits(); child_exit(2); } } child_exit(0); }
/* * Connect to PostgreSQL server by using INET domain socket. * If retry is true, retry to call connect() upon receiving EINTR error. */ int connect_inet_domain_socket_by_port(char *host, int port, bool retry) { int fd; int len; int on = 1; struct sockaddr_in addr; struct hostent *hp; struct timeval timeout; fd_set rset, wset; int error; socklen_t socklen; int sts; #define CONNECT_TIMEOUT_MSEC 1000 /* specify select(2) timeout in milliseconds */ #define CONNECT_TIMEOUT_SEC CONNECT_TIMEOUT_MSEC/1000 /* seconds part */ /* microseconds part */ #define CONNECT_TIMEOUT_MICROSEC (CONNECT_TIMEOUT_SEC == 0?CONNECT_TIMEOUT_MSEC*1000:\ CONNECT_TIMEOUT_MSEC*1000 - CONNECT_TIMEOUT_SEC*1000*1000) fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { pool_error("connect_inet_domain_socket_by_port: socket() failed: %s", strerror(errno)); return -1; } /* set nodelay */ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) < 0) { pool_error("connect_inet_domain_socket_by_port: setsockopt() failed: %s", strerror(errno)); close(fd); return -1; } memset((char *) &addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); len = sizeof(struct sockaddr_in); hp = gethostbyname(host); if ((hp == NULL) || (hp->h_addrtype != AF_INET)) { pool_error("connect_inet_domain_socket: gethostbyname() failed: %s host: %s", hstrerror(h_errno), host); close(fd); return -1; } memmove((char *) &(addr.sin_addr), (char *) hp->h_addr, hp->h_length); pool_set_nonblock(fd); for (;;) { if (exit_request) /* exit request already sent */ { pool_log("connect_inet_domain_socket_by_port: exit request has been sent"); close(fd); return -1; } if (health_check_timer_expired) /* has health check timer expired */ { pool_log("connect_inet_domain_socket_by_port: health check timer expired"); close(fd); return -1; } if (connect(fd, (struct sockaddr *)&addr, len) < 0) { if (errno == EISCONN) { /* Socket is already connected */ break; } if ((errno == EINTR && retry) || errno == EAGAIN) continue; /* * If error was "connect(2) is in progress", then wait for * completion. Otherwise error out. */ if (errno != EINPROGRESS && errno != EALREADY) { pool_error("connect_inet_domain_socket: connect() failed: %s",strerror(errno)); close(fd); return -1; } timeout.tv_sec = CONNECT_TIMEOUT_SEC; timeout.tv_usec = CONNECT_TIMEOUT_MICROSEC; FD_ZERO(&rset); FD_SET(fd, &rset); FD_ZERO(&wset); FD_SET(fd, &wset); sts = select(fd+1, &rset, &wset, NULL, &timeout); if (sts == 0) { /* select timeout */ if (retry) { pool_log("connect_inet_domain_socket: select() timed out. retrying..."); continue; } else { pool_error("connect_inet_domain_socket: select() timed out"); close(fd); return -1; } } else if (sts > 0) { /* * If read data or write data was set, either connect * succeeded or error. We need to figure it out. This * is the hardest part in using non blocking * connect(2). See W. Richar Stevens's "UNIX Network * Programming: Volume 1, Second Edition" section * 15.4. */ if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) { error = 0; socklen = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &socklen) < 0) { /* Solaris returns error in this case */ pool_error("connect_inet_domain_socket: getsockopt() failed: %s", strerror(errno)); close(fd); return -1; } /* Non Solaris case */ if (error != 0) { pool_error("connect_inet_domain_socket: getsockopt() detected error: %s", strerror(error)); close(fd); return -1; } } else { pool_error("connect_inet_domain_socket: both read data and write data was not set"); close(fd); return -1; } } else /* select returns error */ { if((errno == EINTR && retry) || errno == EAGAIN) { pool_log("connect_inet_domain_socket: select() interrupted. retrying..."); continue; } pool_log("connect_inet_domain_socket: select() interrupted"); close(fd); return -1; } } break; } pool_unset_nonblock(fd); return fd; }