#include <netinet/in.h>
#include <stdlib.h>
#include <time.h>
+#include <unistd.h>
/*
* Where to send query
int
pool_virtual_main_db_node_id(void)
{
+ volatile POOL_REQUEST_INFO *my_req;
POOL_SESSION_CONTEXT *sc;
/*
- * Check whether failover is in progress. If so, just abort this session.
+ * Check whether failover is in progress and we are child process.
+ * If so, we will wait for failover to finish.
*/
- if (Req_info->switching)
+ my_req = Req_info;
+ if (processType == PT_CHILD && my_req->switching)
{
#ifdef NOT_USED
POOL_SETMASK(&BlockSig);
errhint("In a moment you should be able to reconnect to the database")));
POOL_SETMASK(&UnBlockSig);
#endif
- child_exit(POOL_EXIT_AND_RESTART);
+ /*
+ * Wait for failover to finish
+ */
+ if (wait_for_failover_to_finish() == -2)
+ /*
+ * Waiting for failover/failback to finish was timed out.
+ * Time to exit this process (and session disconnection).
+ */
+ child_exit(POOL_EXIT_AND_RESTART);
}
sc = pool_get_session_context(true);
}
}
}
+
+/*
+ * Wait for failover/failback to finish.
+ * Return values:
+ * 0: no failover/failback occurred.
+ * -1: failover/failback occurred and finished within certain period.
+ * -2: failover/failback occurred and timed out.
+ */
+int
+wait_for_failover_to_finish(void)
+{
+#define MAX_FAILOVER_WAIT 30 /* waiting for failover finish timeout in seconds */
+
+ volatile POOL_REQUEST_INFO *my_req;
+ int ret = 0;
+ int i;
+
+ /*
+ * Wait for failover to finish
+ */
+ for (i = 0;i < MAX_FAILOVER_WAIT; i++)
+ {
+ my_req = Req_info;
+ if (my_req->switching == 0)
+ return ret;
+ ret = -1; /* failover/failback finished */
+ sleep(1);
+ }
+ return -2; /* timed out */
+}
* pgpool: a language independent connection pool server for PostgreSQL
* written by Tatsuo Ishii
*
- * Copyright (c) 2003-2017 PgPool Global Development Group
+ * Copyright (c) 2003-2023 PgPool Global Development Group
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby
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);
extern void check_object_relationship_list(char *name, bool is_func_name);
+extern int wait_for_failover_to_finish(void);
+
#endif /* POOL_QUERY_CONTEXT_H */
backend_timer_expired = 0;
}
+ /*
+ * Check whether failover/failback is ongoing and wait for it to
+ * finish. If it actually happened, update the private backend status
+ * because it is possible that a backend maybe went down.
+ */
+ if (wait_for_failover_to_finish() < 0)
+ pool_initialize_private_backend_status();
+
backend = get_backend_connection(child_frontend);
if (!backend)
{
for (i = 0; i < NUM_BACKENDS; i++)
{
if (VALID_BACKEND(i))
- if (do_command(frontend, CONNECTION(backend, i),
+ {
+ /*
+ * We want to catch and ignore errors in do_command if a
+ * backend is just going down right now. Otherwise
+ * do_command raises an error and disconnects the
+ * connection to frontend. We can safely ignore error from
+ * "SET application_name" command if the backend goes
+ * down.
+ */
+ PG_TRY();
+ {
+ do_command(frontend, CONNECTION(backend, i),
command_buf, MAJOR(backend),
MAIN_CONNECTION(backend)->pid,
- MAIN_CONNECTION(backend)->key, 0) != POOL_CONTINUE)
+ MAIN_CONNECTION(backend)->key, 0);
+ }
+ PG_CATCH();
{
- ereport(ERROR,
- (errmsg("unable to process command for backend connection"),
- errdetail("do_command returned DEADLOCK status")));
+ /* ignore the error message */
+ MemoryContextSwitchTo(oldContext);
+ FlushErrorState();
}
+ PG_END_TRY();
+ }
}
-
pool_add_param(&MAIN(backend)->params, "application_name", sp->application_name);
set_application_name_with_string(sp->application_name);
}
pause();
}
+ /* wait for failover/failback to finish */
+ if (wait_for_failover_to_finish() < 0)
+ /* failover/failback occurred. Update private backend status */
+ pool_initialize_private_backend_status();
+
afd = accept(fd, (struct sockaddr *) &saddr->addr, &saddr->salen);
save_errno = errno;
* pgpool: a language independent connection pool server for PostgreSQL
* written by Tatsuo Ishii
*
- * Copyright (c) 2003-2020 PgPool Global Development Group
+ * Copyright (c) 2003-2023 PgPool Global Development Group
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby
#include <stdlib.h>
#include "pool.h"
+#include "context/pool_query_context.h"
#include "utils/pool_stream.h"
#include "utils/palloc.h"
#include "pool_config.h"
ereport(LOG,
(errmsg("connection closed."),
errdetail("retry to create new connection pool")));
+ /*
+ * It is possible that one of backend just broke. sleep 1
+ * second to wait for failover occurres, then wait for the
+ * failover finishes.
+ */
+ sleep(1);
+ wait_for_failover_to_finish();
for (j = 0; j < NUM_BACKENDS; j++)
{
{
ereport(ERROR,
(errmsg("do command failed"),
- errdetail("backend error: \"%s\"", string)));
+ errdetail("backend error: \"%s\" \"%s\"", string, query)));
}
}
}
--- /dev/null
+#!/usr/bin/env bash
+#-------------------------------------------------------------------
+# Test script for session disconnection with failover.
+# This test is for streaming replication mode only.
+#
+source $TESTLIBS
+TESTDIR=testdir
+PSQL=$PGBIN/psql
+PG_CTL=$PGBIN/pg_ctl
+PGBENCH=$PGBENCH_PATH
+export PGDATABASE=test
+
+rm -fr $TESTDIR
+mkdir $TESTDIR
+cd $TESTDIR
+
+# create streaming replication, 3-node test environment.
+echo -n "creating test environment..."
+$PGPOOL_SETUP -m s -n 3 -s || exit 1
+echo "done."
+
+source ./bashrc.ports
+
+# PCP_PORT is defined in bashrc.ports
+PCP_DETACH_NODE="$PGPOOL_INSTALL_DIR/bin/pcp_detach_node -w -h localhost -p $PCP_PORT 2"
+
+# customize pgpool.conf. disable load balance to node 2.
+cat >> etc/pgpool.conf <<EOF
+backend_weight2 = 0
+log_per_node_statement = off
+log_error_verbosity = verbose
+EOF
+
+./startall
+export PGPORT=$PGPOOL_PORT
+wait_for_pgpool_startup
+
+$PGBENCH -i
+
+echo "=== test1: backend_weight2 = 0 and pgbench without -C option"
+# In this test we expect that shutdown of node 2 does not affect
+# client sessions at all.
+
+($PGBENCH -n -S -c 10 -T 5)&
+sleep 1
+$PG_CTL -D data2 stop
+wait $!
+if [ $? != 0 ];then
+ echo "pgbench exited with error. test1 failed."
+ ./shutdownall
+ exit 1
+fi
+./shutdownall
+
+echo "=== test2: backend_weight2 = 0 and pgbench with -C option"
+# In this test we expect that shutdown of node 2 does not affect
+# client sessions at all if pcp_detach_node is executed beforehand.
+
+./startall
+wait_for_pgpool_startup
+
+($PGBENCH -n -S -C -c 10 -T 5)&
+sleep 1
+echo $PCP_DETACH_NODE
+$PCP_DETACH_NODE
+sleep 3
+$PG_CTL -D data2 stop
+wait $!
+if [ $? != 0 ];then
+ echo "pgbench exited with error. test2 failed."
+ ./shutdownall
+ exit 1
+fi
+
+./shutdownall
+
+echo "=== test3: load_balance_mode = off and pgbench without -C option"
+# Same test as test1. The only the difference is load_balance_mode is
+# off instead of backend_weitht2 = 0. To make sure that both have same
+# effect against failover.
+
+echo "backend_weight2 = 1" >> etc/pgpool.conf
+echo "load_balance_mode = off" >> etc/pgpool.conf
+
+./startall
+wait_for_pgpool_startup
+
+($PGBENCH -n -S -c 10 -T 5)&
+sleep 1
+$PG_CTL -D data2 stop
+wait $!
+if [ $? != 0 ];then
+ echo "pgbench exited with error. test3 failed."
+ ./shutdownall
+ exit 1
+fi
+./shutdownall
+
+echo "=== test4: load_balance_mode = off and pgbench with -C option"
+# Same test as test3. The only the difference is load_balance_mode is
+# off instead of backend_weitht2 = 0. To make sure that both have same
+# effect against failover.
+
+./startall
+wait_for_pgpool_startup
+
+($PGBENCH -n -S -C -c 10 -T 5)&
+sleep 1
+echo $PCP_DETACH_NODE
+$PCP_DETACH_NODE
+sleep 3
+$PG_CTL -D data2 stop
+wait $!
+if [ $? != 0 ];then
+ echo "pgbench exited with error. test4 failed."
+ ./shutdownall
+ exit 1
+fi
+
+./shutdownall
+
+exit 0