pgpool2.git / summary / log / commit / refs

commit    f284be467220cca09016b65ed7d07196b8f9fe3a
Author:   Muhammad Usama <m.usama@gmail.com>
Date:     Tue Aug 16 15:51:17 2016 +0000

    Handling of pg_terminate_backend for simple query protocol
    
    Since the pg_terminate_backend function in PostgreSQL is used to terminate the
    backend connection, So what happens is, when this function kills a PostgreSQL
    backend that is connected to the pgpool-II, This disconnection of backend by
    pg_terminate_backend function is appeared as a backend node failure
    to the pgpool-II.
    But the problem here is, PostgreSQL does not give any information to the
    client program that the connection is going to be killed because of the
    pg_terminate_backend call and on the client side, it looks similar
    to the backend node failure.
    
    Now to solve this in pgpool-II we need two things.
    First is to identify the pg_terminate_backend function in the query and the
    pgpool-II child process that hosts the particular backend connection which will
    be killed by that pg_terminate_backend function call, so that we get a heads up
    in advance about the backend termination, and secondly the routing of the query
    containing pg_terminate_backend also needs a new logic so that the query should
    be sent to the correct PostgreSQL node that hosts the backend with the
    PID referred by the pg_terminate_bakend()
    
    So how does this commit handles pg_terminate_backend()??
    In the SimpleQuery() function which is the work horse of simple query processing
    in the pgpool-II we start with the search of the pg_terminate_backend()
    function call in the query parse tree and if the search comes out to be
    successful, the next step is to locate the pgpool-II child process and a
    backend node of that connection whose PID is specified in pg_terminate_backend
    function's argument. Once the connection and the pgpool-II child process
    is identified, we just set the swallow_termination flag(added by this commit in
    ConnectionInfo structure) for that connection in the shared memory, and also
    set the query destination node to the backend node that hosts that particular
    connection and does not call pool_where_to_send() for this query so that the
    query should be sent to the correct backend node.
    
    Now when the query is routed to the correct node and consequently the backend
    gets killed, that results in the communication error on pgpool-II side, the
    pgpool-II already knows that this disconnection is due the pg_terminate_backend
    and not because of node failure as the swallow_termination flag is already
    set for the connection.
    
    TODOs:
    
    -- pg_terminate_backend is not handled with extended query protocol
    
    -- Currently we only support pg_terminate_backend(constant number) function
    calls. If the expression or sub query is used in the argument of
    pg_terminate_backend then it would not be handled
       e.g
        pgpool=# select pg_terminate_backend(1025); -- Supported
        pgpool=# select pg_terminate_backend( 2 +1); -- NOT SUPPORTED
        pgpool=# select pg_terminate_backend((select 1)); -- NOT SUPPORTED
    
     -- Currently only one pg_terminate_backend call in a query is handled.


src/auth/pool_auth.c | 2 + src/context/pool_process_context.c | 81 ++++++++++++++++++++++++++++++ src/context/pool_query_context.c | 46 ++++++++++++++++- src/include/context/pool_process_context.h | 5 ++ src/include/context/pool_query_context.h | 9 +++- src/include/pcp/libpcp_ext.h | 11 +++- src/include/pool.h | 3 ++ src/include/utils/pool_select_walker.h | 2 + src/protocol/pool_connection_pool.c | 1 + src/protocol/pool_process_query.c | 14 +++++- src/protocol/pool_proto_modules.c | 80 +++++++++++++++++++++++++++-- src/utils/pool_select_walker.c | 34 +++++++++++++ src/utils/pool_stream.c | 42 +++++++++++++++- 13 files changed, 318 insertions(+), 12 deletions(-) diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c index f2660c385..5c585b014 100644 --- a/src/auth/pool_auth.c +++ b/src/auth/pool_auth.c @@ -406,6 +406,8 @@ int pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) strlcpy(cp->info[i].database, sp->database, sizeof(cp->info[i].database)); strlcpy(cp->info[i].user, sp->user, sizeof(cp->info[i].user)); cp->info[i].counter = 1; + CONNECTION(cp, i)->con_info = &cp->info[i]; + cp->info[i].swallow_termination = 0; } } diff --git a/src/context/pool_process_context.c b/src/context/pool_process_context.c index e1f560c35..cfc2369c6 100644 --- a/src/context/pool_process_context.c +++ b/src/context/pool_process_context.c @@ -190,6 +190,87 @@ ConnectionInfo *pool_coninfo_pid(int pid, int connection_pool, int backend) backend]; } +/* + * locate and return the shared memory ConnectionInfo having the + * backend connection with the pid + * if the connection is found the *backend_node_id contains the backend node id + * of the backend node that has the connection + */ +ConnectionInfo* pool_coninfo_backend_pid(int backend_pid, int* backend_node_id) +{ + int child; + /* + * look for the child process that has the backend + * with the pid + */ + + ereport(DEBUG1, + (errmsg("searching for the connection with backend pid:%d",backend_pid))); + + for (child = 0; child < pool_config->num_init_children; child++) + { + int pool; + ProcessInfo *pi = pool_get_process_info(process_info[child].pid); + + for (pool = 0; pool < pool_config->max_pool; pool++) + { + int backend_id; + for (backend_id = 0; backend_id < NUM_BACKENDS; backend_id++) + { + int poolBE = pool*MAX_NUM_BACKENDS+backend_id; + if (ntohl(pi->connection_info[poolBE].pid) == backend_pid) + { + ereport(DEBUG1, + (errmsg("found for the connection with backend pid:%d on backend node %d",backend_pid,backend_id))); + *backend_node_id = backend_id; + return &pi->connection_info[poolBE]; + } + } + } + } + return NULL; +} + +/* + * returns true if the conInfo object belongs to the current child process + */ +bool pool_is_my_coninfo(ConnectionInfo* connInfo) +{ + int pool; + ProcessInfo *pi = pool_get_my_process_info(); + for (pool = 0; pool < pool_config->max_pool; pool++) + { + int backend_id; + for (backend_id = 0; backend_id < NUM_BACKENDS; backend_id++) + { + int poolBE = pool*MAX_NUM_BACKENDS+backend_id; + ConnectionInfo* cInfo = &pi->connection_info[poolBE]; + if (cInfo == connInfo) + { + ereport(DEBUG1, + (errmsg("connection Info object is local"))); + return true; + } + } + } + return false; +} + +/* + * sets the flag to mark that the connection will be terminated by the + * backend and it should not be considered as a backend node failure. + * This flag is used to handle pg_terminate_backend() + */ +void pool_set_connection_will_be_terminated(ConnectionInfo* connInfo) +{ + connInfo->swallow_termination = 1; +} + +void pool_unset_connection_will_be_terminated(ConnectionInfo* connInfo) +{ + connInfo->swallow_termination = 0; +} + /* * Set frontend connected flag */ diff --git a/src/context/pool_query_context.c b/src/context/pool_query_context.c index 49b005606..49431acb8 100644 --- a/src/context/pool_query_context.c +++ b/src/context/pool_query_context.c @@ -88,6 +88,13 @@ void pool_query_context_destroy(POOL_QUERY_CONTEXT *query_context) MemoryContext memory_context = query_context->memory_context; session_context = pool_get_session_context(false); pool_unset_query_in_progress(); + if (query_context->pg_terminate_backend_conn) + { + ereport(DEBUG1, + (errmsg("resetting the connection flag for pg_terminate_backend"))); + pool_unset_connection_will_be_terminated(query_context->pg_terminate_backend_conn); + } + query_context->pg_terminate_backend_conn = NULL; query_context->original_query = NULL; session_context->query_context = NULL; pfree(query_context); @@ -288,11 +295,24 @@ int pool_virtual_master_db_node_id(void) * Make sure that virtual_master_node_id is either primary node * id or load balance node id. If not, it is likely that * virtual_master_node_id is not set up yet. Let's use the - * primary node id. + * primary node id. except for the special case where we need + * to send the query to the node which is not primary nor the + * load balance node. Currently there is only one special such + * case that is handling of pg_terminate_backend() function, which + * may refer to the backend connection that is neither hosted by + * the primary or load balance node for current child process, but + * the query must be forwarded to that node. Since only that backend + * node can handle that pg_terminate_backend query + * */ if (node_id != sc->load_balance_node_id && node_id != PRIMARY_NODE_ID) { - node_id = PRIMARY_NODE_ID; + /* + * Only return the primary node id if we are not processing the + * pg_terminate_backend query + */ + if (sc->query_context->pg_terminate_backend_conn == NULL) + node_id = PRIMARY_NODE_ID; } } @@ -311,6 +331,28 @@ int pool_virtual_master_db_node_id(void) return my_master_node_id; } +/* + * The function sets the destination for the current query to the specific backend node + */ +void pool_force_query_node_to_backend(POOL_QUERY_CONTEXT *query_context, int backend_id) +{ + int i; + CHECK_QUERY_CONTEXT_IS_VALID; + + ereport(DEBUG1, + (errmsg("forcing query destination node to backend node:%d",backend_id))); + + pool_set_node_to_be_sent(query_context,backend_id); + for (i=0;i<NUM_BACKENDS;i++) + { + if (query_context->where_to_send[i]) + { + query_context->virtual_master_node_id = i; + break; + } + } +} + /* * Decide where to send queries(thus expecting response) */ diff --git a/src/include/context/pool_process_context.h b/src/include/context/pool_process_context.h index 85ffb6039..5bdc49819 100644 --- a/src/include/context/pool_process_context.h +++ b/src/include/context/pool_process_context.h @@ -59,4 +59,9 @@ extern ConnectionInfo *pool_coninfo_pid(int pid, int connection_pool, int backen extern void pool_coninfo_set_frontend_connected(int proc_id, int pool_index); extern void pool_coninfo_unset_frontend_connected(int proc_id, int pool_index); +extern ConnectionInfo* pool_coninfo_backend_pid(int backend_pid, int* backend_node_id); +extern bool pool_is_my_coninfo(ConnectionInfo* connInfo); +extern void pool_set_connection_will_be_terminated(ConnectionInfo* connInfo); +extern void pool_unset_connection_will_be_terminated(ConnectionInfo* connInfo); + #endif /* POOL_PROCESS_CONTEXT_H */ diff --git a/src/include/context/pool_query_context.h b/src/include/context/pool_query_context.h index 207e23951..8ed403b58 100644 --- a/src/include/context/pool_query_context.h +++ b/src/include/context/pool_query_context.h @@ -68,6 +68,13 @@ typedef struct { * query and parsed node is actually a dummy query. */ int num_original_params; /* number of parameters in original query */ + ConnectionInfo *pg_terminate_backend_conn; + /* pointer to the shared memory connection info object + * referred by pg_terminate_backend() function. + * we need this to reset the flag after executing + * the pg_terminate_backend query, especially for the + * case when the query gets fail on the backend. + */ MemoryContext memory_context; /* memory context for query context */ } POOL_QUERY_CONTEXT; @@ -103,5 +110,5 @@ extern bool pool_is_cache_exceeded(void); extern void pool_set_cache_exceeded(void); extern void pool_unset_cache_exceeded(void); extern bool pool_is_transaction_read_only(Node *node); - +extern void pool_force_query_node_to_backend(POOL_QUERY_CONTEXT *query_context, int backend_id); #endif /* POOL_QUERY_CONTEXT_H */ diff --git a/src/include/pcp/libpcp_ext.h b/src/include/pcp/libpcp_ext.h index 6243c3aaf..f58e1c950 100644 --- a/src/include/pcp/libpcp_ext.h +++ b/src/include/pcp/libpcp_ext.h @@ -89,8 +89,8 @@ typedef struct { int pid; /* backend process id */ int key; /* cancel key */ int counter; /* used counter */ - time_t create_time; /* connection creation time */ - int load_balancing_node; /* load balancing node */ + time_t create_time; /* connection creation time */ + int load_balancing_node; /* load balancing node */ char connected; /* True if frontend connected. Please note * that we use "char" instead of "bool". * Since 3.1, we need to export this @@ -101,6 +101,13 @@ typedef struct { * might be out of control of * pgpool-II. So we use "char" here. */ + char swallow_termination; + /* Flag to mark that if the connection will + * be terminated by the backend. it should + * not be treated as a backend node failure. + * This flag is used to handle + * pg_terminate_backend() + */ } ConnectionInfo; /* diff --git a/src/include/pool.h b/src/include/pool.h index 7ae888181..59533d27e 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -235,6 +235,9 @@ typedef struct { char *auth_arg; char *database; char *username; + ConnectionInfo *con_info; /* shared memory coninfo used + * for handling the query containing + * pg_terminate_backend*/ } POOL_CONNECTION; /* diff --git a/src/include/utils/pool_select_walker.h b/src/include/utils/pool_select_walker.h index d3e6fa03a..4195aa6f1 100644 --- a/src/include/utils/pool_select_walker.h +++ b/src/include/utils/pool_select_walker.h @@ -40,6 +40,7 @@ typedef struct { bool has_unlogged_table; /* True if unlogged table is used */ bool has_view; /* True if view is used */ bool has_function_call; /* True if write function call is used */ + int pg_terminate_backend_pid; /* pid argument of pg_terminate_backedn_call(if used) */ bool has_non_immutable_function_call; /* True if non immutable functions are used */ bool has_insertinto_or_locking_clause; /* True if it has SELECT INTO or FOR SHARE/UPDATE */ int num_oids; /* number of oids */ @@ -47,6 +48,7 @@ typedef struct { char table_names[POOL_MAX_SELECT_OIDS][POOL_NAMEDATALEN]; /* table names */ } SelectContext; +extern int pool_get_terminate_backend_pid(Node *node); extern bool pool_has_function_call(Node *node); extern bool pool_has_non_immutable_function_call(Node *node); extern bool pool_has_system_catalog(Node *node); diff --git a/src/protocol/pool_connection_pool.c b/src/protocol/pool_connection_pool.c index 23c66656b..b51d931af 100644 --- a/src/protocol/pool_connection_pool.c +++ b/src/protocol/pool_connection_pool.c @@ -167,6 +167,7 @@ POOL_CONNECTION_POOL *pool_get_cp(char *user, char *database, int protoMajor, in info = connection_pool->info; memset(connection_pool, 0, sizeof(POOL_CONNECTION_POOL)); connection_pool->info = info; + info->swallow_termination = 0; memset(connection_pool->info, 0, sizeof(ConnectionInfo) * MAX_NUM_BACKENDS); POOL_SETMASK(&oldmask); return NULL; diff --git a/src/protocol/pool_process_query.c b/src/protocol/pool_process_query.c index 2f12132a0..fe0df4d16 100644 --- a/src/protocol/pool_process_query.c +++ b/src/protocol/pool_process_query.c @@ -4719,8 +4719,18 @@ SELECT_RETRY: was_error = 1; if (!VALID_BACKEND(i)) break; - notice_backend_error(i, true); - sleep(5); + /* check if the pg_terminate_backend was issued on this connection */ + if (CONNECTION(backend, i)->con_info->swallow_termination == 1) + { + ereport(FATAL, + (errmsg("connection to postmaster on DB node %d was lost due to pg_terminate_backend", i), + errdetail("pg_terminate_backend was called on the backend"))); + } + else + { + notice_backend_error(i, true); + sleep(5); + } break; } diff --git a/src/protocol/pool_proto_modules.c b/src/protocol/pool_proto_modules.c index cc956b1e4..c833f48a9 100644 --- a/src/protocol/pool_proto_modules.c +++ b/src/protocol/pool_proto_modules.c @@ -93,6 +93,71 @@ static POOL_STATUS close_standby_transactions(POOL_CONNECTION *frontend, static char * flatten_set_variable_args(const char *name, List *args); +static bool +process_pg_terminate_backend_func(POOL_QUERY_CONTEXT *query_context); + +/* + * This is the workhorse of processing the pg_terminate_backend function to + * make sure that the use of function should not trigger the backend node failover. + * + * The function searches for the pg_terminate_backend() function call in the + * query parse tree and if the search comes out to be successful, + * the next step is to locate the pgpool-II child process and a backend node + * of that connection whose PID is specified in pg_terminate_backend argument. + * + * Once the connection is identified, we set the swallow_termination flag of + * that connection (in shared memory) and also sets the query destination to + * the same backend that hosts the connection. + * + * The function returns true on success, i.e. when the query contains the + * pg_terminate_backend call and that call refers to the backend + * connection that belongs to pgpool-II. + * + * Note: Since upon successful return this function has already + * set the destination backend node for the current query, + * so for that case pool_where_to_send() should not be called. + * + */ +static bool process_pg_terminate_backend_func(POOL_QUERY_CONTEXT *query_context) +{ + /* + * locate pg_terminate_backend and get the pid argument, if pg_terminate_backend + * is present in the query + */ + int backend_pid = pool_get_terminate_backend_pid(query_context->parse_tree); + if (backend_pid > 0) + { + int backend_node = 0; + ConnectionInfo* conn = pool_coninfo_backend_pid(backend_pid, &backend_node); + if(conn == NULL) + { + ereport(LOG, + (errmsg("found the pg_terminate_backend request for backend pid:%d, but the backend connection does not belong to pgpool-II",backend_pid))); + /* we are not able to find the backend connection with the pid + * so there is not much left for us to do here + */ + return false; + } + ereport(LOG, + (errmsg("found the pg_terminate_backend request for backend pid:%d on backend node:%d",backend_pid,backend_node), + errdetail("setting the connection flag"))); + + if (pool_is_my_coninfo(conn)){ + ereport(LOG, + (errmsg("pg_terminate_backend refer to the current child process connection"), + errdetail("setting the connection flag not required"))); + } + else{ + pool_set_connection_will_be_terminated(conn); + } + /* It was the pg_terminate_backend call so send the query to appropriate backend */ + query_context->pg_terminate_backend_conn = conn; + pool_force_query_node_to_backend(query_context, backend_node); + return true; + } + return false; +} + /* * Process Query('Q') message * Query messages include an SQL string. @@ -409,11 +474,18 @@ POOL_STATUS SimpleQuery(POOL_CONNECTION *frontend, } /* - * Decide where to send query + * pg_terminate function needs special handling, process it if the query + * contains one, otherwise use pool_where_to_send() to decide destination + * backend node for the query */ - pool_where_to_send(query_context, query_context->original_query, - query_context->parse_tree); - + if (process_pg_terminate_backend_func(query_context) == false) + { + /* + * Decide where to send query + */ + pool_where_to_send(query_context, query_context->original_query, + query_context->parse_tree); + } /* * if this is DROP DATABASE command, send USR1 signal to parent and * ask it to close all idle connections. diff --git a/src/utils/pool_select_walker.c b/src/utils/pool_select_walker.c index be5c38ae2..7c063ddb8 100644 --- a/src/utils/pool_select_walker.c +++ b/src/utils/pool_select_walker.c @@ -58,12 +58,32 @@ bool pool_has_function_call(Node *node) return false; ctx.has_function_call = false; + ctx.pg_terminate_backend_pid = -1; raw_expression_tree_walker(node, function_call_walker, &ctx); return ctx.has_function_call; } +/* + * Search the pg_terminate_backend() call in the query + */ +int pool_get_terminate_backend_pid(Node *node) +{ + SelectContext ctx; + + if (!IsA(node, SelectStmt)) + return false; + + ctx.has_function_call = false; + ctx.pg_terminate_backend_pid = 0; + + raw_expression_tree_walker(node, function_call_walker, &ctx); + + return ctx.pg_terminate_backend_pid; + +} + /* * Return true if this SELECT has system catalog table. */ @@ -286,6 +306,20 @@ static bool function_call_walker(Node *node, void *context) ereport(DEBUG1, (errmsg("function call walker, function name: \"%s\"", fname))); + if (ctx->pg_terminate_backend_pid == 0 && strcmp("pg_terminate_backend", fname) == 0) + { + if (list_length(fcall->args) == 1) + { + Node *arg = linitial(fcall->args); + if (IsA(arg, A_Const) && + ((A_Const *)arg)->val.type == T_Integer) + { + ctx->pg_terminate_backend_pid = ((A_Const *)arg)->val.val.ival; + ereport(DEBUG1, + (errmsg("pg_terminate_backend pid = %d",ctx->pg_terminate_backend_pid))); + } + } + } /* * Check white list if any. */ diff --git a/src/utils/pool_stream.c b/src/utils/pool_stream.c index 39903bf51..ff3142d0b 100644 --- a/src/utils/pool_stream.c +++ b/src/utils/pool_stream.c @@ -207,8 +207,16 @@ int pool_read(POOL_CONNECTION *cp, void *buf, int len) cp->socket_state = POOL_SOCKET_ERROR; if (cp->isbackend) { - ereport(ERROR, + if (cp->con_info->swallow_termination == 1) + { + cp->con_info->swallow_termination = 0; + ereport(FATAL, (errmsg("unable to read data from DB node %d",cp->db_node_id), + errdetail("pg_terminate_backend was called on the backend"))); + } + + ereport(ERROR, + (errmsg("unable to read data from DB node %d",cp->db_node_id), errdetail("socket read failed with an error \"%s\"", strerror(errno)))); /* if fail_over_on_backend_error is true, then trigger failover */ @@ -347,6 +355,14 @@ char *pool_read2(POOL_CONNECTION *cp, int len) cp->socket_state = POOL_SOCKET_ERROR; if (cp->isbackend) { + if (cp->con_info->swallow_termination == 1) + { + cp->con_info->swallow_termination = 0; + ereport(FATAL, + (errmsg("unable to read data from DB node %d",cp->db_node_id), + errdetail("pg_terminate_backend was called on the backend"))); + } + /* if fail_over_on_backend_error is true, then trigger failover */ if (pool_config->fail_over_on_backend_error) { @@ -603,6 +619,14 @@ int pool_flush(POOL_CONNECTION *cp) { if (cp->isbackend) { + if (cp->con_info->swallow_termination == 1) + { + cp->con_info->swallow_termination = 0; + ereport(FATAL, + (errmsg("unable to read data from DB node %d",cp->db_node_id), + errdetail("pg_terminate_backend was called on the backend"))); + } + /* if fail_over_on_backend_error is true, then trigger failover */ if (pool_config->fail_over_on_backend_error) { @@ -650,6 +674,14 @@ int pool_flush_noerror(POOL_CONNECTION *cp) { if (cp->isbackend) { + if (cp->con_info->swallow_termination == 1) + { + cp->con_info->swallow_termination = 0; + ereport(FATAL, + (errmsg("unable to read data from DB node %d",cp->db_node_id), + errdetail("pg_terminate_backend was called on the backend"))); + } + /* if fail_over_on_backend_erro is true, then trigger failover */ if (pool_config->fail_over_on_backend_error) { @@ -810,6 +842,14 @@ char *pool_read_string(POOL_CONNECTION *cp, int *len, int line) cp->socket_state = POOL_SOCKET_ERROR; if (cp->isbackend) { + if (cp->con_info->swallow_termination == 1) + { + cp->con_info->swallow_termination = 0; + ereport(FATAL, + (errmsg("unable to read data from DB node %d",cp->db_node_id), + errdetail("pg_terminate_backend was called on the backend"))); + } + notice_backend_error(cp->db_node_id, true); child_exit(POOL_EXIT_AND_RESTART); ereport(ERROR, [parent: d5c3cf2d9d94]