static void ev_handler(struct ns_connection *nc, int ev, void *ev_data) { struct conn_data *conn = (struct conn_data *) nc->user_data; const time_t now = time(NULL); #ifdef DEBUG write_log("%d conn=%p nc=%p ev=%d ev_data=%p bec=%p bec_nc=%p\n", now, conn, nc, ev, ev_data, conn != NULL ? conn->be_conn : NULL, conn != NULL && conn->be_conn != NULL ? conn->be_conn->nc : NULL); #endif if (conn == NULL) { if (ev == NS_ACCEPT) { conn = calloc(1, sizeof(*conn)); if (conn == NULL) { send_http_err(nc, s_error_500); } else { memset(conn, 0, sizeof(*conn)); nc->user_data = conn; conn->client.nc = nc; conn->client.body_len = -1; conn->backend.body_len = -1; conn->last_activity = now; } return; } else { nc->flags |= NSF_CLOSE_IMMEDIATELY; return; } } if (ev != NS_POLL) conn->last_activity = now; switch (ev) { case NS_HTTP_REQUEST: { /* From client */ assert(conn != NULL); assert(conn->be_conn == NULL); struct http_message *hm = (struct http_message *) ev_data; conn->client.flags.keep_alive = is_keep_alive(hm); if (!connect_backend(conn, hm)) { respond_with_error(conn, s_error_500); break; } if (conn->backend.nc == NULL) { /* This is a redirect, we're done. */ conn->client.nc->flags |= NSF_SEND_AND_CLOSE; break; } forward(conn, hm, &conn->client, &conn->backend); break; } case NS_CONNECT: { /* To backend */ assert(conn != NULL); assert(conn->be_conn != NULL); int status = *(int *) ev_data; if (status != 0) { write_log("Error connecting to %s: %d (%s)\n", conn->be_conn->be->host_port, status, strerror(status)); /* TODO(lsm): mark backend as defunct, try it later on */ respond_with_error(conn, s_error_500); conn->be_conn->nc = NULL; release_backend(conn); break; } break; } case NS_HTTP_REPLY: { /* From backend */ assert(conn != NULL); struct http_message *hm = (struct http_message *) ev_data; conn->backend.flags.keep_alive = s_backend_keepalive && is_keep_alive(hm); forward(conn, hm, &conn->backend, &conn->client); release_backend(conn); if (!conn->client.flags.keep_alive) { conn->client.nc->flags |= NSF_SEND_AND_CLOSE; } else { #ifdef DEBUG write_log("conn=%p remains open\n", conn); #endif } break; } case NS_POLL: { assert(conn != NULL); if (now - conn->last_activity > CONN_IDLE_TIMEOUT && conn->backend.nc == NULL /* not waiting for backend */) { #ifdef DEBUG write_log("conn=%p has been idle for too long\n", conn); conn->client.nc->flags |= NSF_SEND_AND_CLOSE; #endif } break; } case NS_CLOSE: { assert(conn != NULL); if (nc == conn->client.nc) { #ifdef DEBUG write_log("conn=%p nc=%p client closed, body_sent=%d\n", conn, nc, conn->backend.body_sent); #endif conn->client.nc = NULL; if (conn->backend.nc != NULL) { conn->backend.nc->flags |= NSF_CLOSE_IMMEDIATELY; } } else if (nc == conn->backend.nc) { #ifdef DEBUG write_log("conn=%p nc=%p backend closed\n", conn, nc); #endif conn->backend.nc = NULL; if (conn->client.nc != NULL && (conn->backend.body_len < 0 || conn->backend.body_sent < conn->backend.body_len)) { write_log("Backend %s disconnected.\n", conn->be_conn->be->host_port); respond_with_error(conn, s_error_500); } } if (conn->client.nc == NULL && conn->backend.nc == NULL) { free(conn); } break; } } }
/* * 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); }