openGauss-server/src/bin/pgxc_clean/pgxc_clean.cpp

2974 lines
101 KiB
C++

/*
* ------------------------------------------------------------------------
*
* pgxc_clean utility
*
* Recovers outstanding 2PC when after crashed nodes or entire cluster
* is recovered.
*
* Depending upon how nodes/XC cluster fail, there could be outstanding
* 2PC transactions which are partly prepared and partly commited/borted.
* Such transactions must be commited/aborted to remove them from the
* snapshot.
*
* This utility checks if there's such outstanding transactions and
* cleans them up.
*
* Command syntax
*
* pgxc_clean [option ... ] [database] [user]
*
* Options are:
*
* -a, --all cleanup all the database avilable
* -d, --dbname=DBNAME database name to clean up. Multiple -d option
* can be specified.
* -h, --host=HOSTNAME Coordinator hostname to connect to.
* -N, --no-clean only test. no cleanup actually.
* -p, --port=PORT Coordinator port number.
* -q, --quiet do not print messages except for error, default.
* -s, --status prints out 2PC status.
* -U, --username=USERNAME database user name
* -v, --verbose same as -s, plus prints result of each cleanup.
* -V, --version prints out the version,
* -w, --no-password never prompt for the password.
* -W, --password prompt for the password,
* -e, --exclusive only clean temp table and remove clean's timeout
* -?, --help prints help message
*
* ------------------------------------------------------------------------
*/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <pwd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include "c.h"
#include "libpq/libpq-fe.h"
#include "pg_config.h"
#include "getopt_long.h"
#include "pgxc_clean.h"
#include "txninfo.h"
#include "port.h"
#include "bin/elog.h"
#include <sys/stat.h>
#include "securec.h"
#include "securec_check.h"
#define PROG_NAME "gs_clean"
#define NAMEDATALEN 64
#define DEFAULT_CONNECT_TIMEOUT "5"
#define FORMATTED_TS_LEN 128
#define FirstNormalTransactionId ((TransactionId)3)
#define TransactionIdIsNormal(xid) ((xid) >= FirstNormalTransactionId)
char m_formatted_log_time[FORMATTED_TS_LEN];
/* Who I am */
const char* progname;
char* my_nodename;
int my_nodeidx = -1; /* Index in pgxc_clean_node_info */
char* gtm_mode = NULL;
char* gtm_option = NULL;
int gtm_option_num; /* GTM 0 GTMLITE 1 GTMFREE 2 */
/* Databases to clean */
bool clean_all_databases = false; /* "--all" overrides specific database specification */
/* Databases to clean */
bool commit_all_prepared = false; /* if global status is prepared ,wether commit all*/
bool rollback_all_prepared = false; /* if global status is prepared ,wether abort all*/
database_names* head_database_names = NULL;
database_names* last_database_names = NULL;
char* rollback_cn_name = NULL;
/* Coordinator to connect to */
char* coordinator_host = NULL;
int coordinator_port = -1;
char* target_node = NULL;
char* connect_timeout = DEFAULT_CONNECT_TIMEOUT;
typedef enum passwd_opt { TRI_DEFAULT, TRI_YES, TRI_NO } passwd_opt;
/* Miscellaneous */
char* username = NULL;
bool version_opt = false;
passwd_opt try_password_opt = TRI_DEFAULT;
bool status_opt = false;
bool no_clean_opt = false;
bool verbose_opt = false;
/* Global variables */
node_info* pgxc_clean_node_info;
int pgxc_clean_node_count;
database_info* head_database_info;
database_info* last_database_info;
/* @Temp Table. Store temp schema name selected from CN or DN. */
tempschema_info* temp_schema_info = NULL;
/* @Temp Table. Store current active backend pid to decide which temp schema should be dropped. */
activebackend_info* active_backend_info = NULL;
/* PLAN_TABLE: store session_id selected from plan_table_data of one db. */
sessionid_info* session_id_info = NULL;
/* clean temp table only */
static bool clean_temp_schema_only = false;
static bool do_commit_abort_failed = false;
/* clean plan_table_data only */
static bool clean_plan_table_only = false;
static bool no_clean_temp_schema_tbspc = false;
static char* password = NULL;
static char password_prompt[256];
#ifdef Assert
#undef Assert
#endif
#ifdef USE_ASSERT_CHECKING
#define Assert(cond) assert(cond)
#else
#define Assert(cond) ((void)0)
#endif
/* Funcs */
static char* transfor_gtm_optoin(const char* option, const char* gtm_free_mode);
static void add_to_database_list(char* dbname);
static void parse_pgxc_clean_options(int argc, char* argv[]);
static void usage(void);
static void showVersion(void);
static PGconn* loginDatabase(char* host, int port, char* user, char* password, char* dbname, const char* progname,
char* encoding, const char* password_prompt);
#ifdef ENABLE_MULTIPLE_NODES
static void getMyNodename(PGconn* conn);
static void getMyGtmMode(PGconn* conn);
#endif
static void recover2PCForDatabase(database_info* db_info, CleanWorkerInfo* wkinfo);
static void recover2PC(PGconn* conn, txn_info* txn, int num);
static void getDatabaseList(PGconn* conn);
#ifdef ENABLE_MULTIPLE_NODES
static void getNodeList(PGconn* conn);
#endif
static void getPreparedTxnList(PGconn* conn);
static void getTxnInfoOnOtherNodesAll(CleanWorkerInfo* wkinfo);
static void do_commit(PGconn* conn, txn_info* txn, int num);
static void do_abort(PGconn* conn, txn_info* txn, int num);
static void do_commit_abort(PGconn* conn, txn_info* txn, bool is_commit, int num);
static bool setMaintenanceMode(PGconn* conn, bool on);
static bool setEnableParallelDDL(PGconn* conn, bool on);
static char* pg_strdup(const char* string);
static char* formatLogTime(void);
static void getActiveBackendListOnCN(PGconn* conn, int datanode_no = -1);
static void getActiveBackendList(PGconn* conn, const char* query);
static void dropTempSchemas(PGconn* conn, bool missing_ok);
static void dropTempSchema(PGconn* conn, char* nspname, bool missing_ok);
static void getTempSchemaListOnCN(PGconn* conn);
#ifdef ENABLE_MULTIPLE_NODES
static void getTempSchemaOnOneDN(PGconn* conn, int no);
static int getTempSchemaListOnDN(PGconn* conn);
#endif
static void checkAllocMem(void* p);
static void cleanTempSchemaList();
static void cleanBackendList();
static void dropTempNamespace();
static int64 convertCsnStr(const char* csn);
static char* fetchCsnInfo(PGconn* conn, GlobalTransactionId gxid, int node_id, bool* meet_error);
/* For plan_table cleanning */
static void deletePlanTableDataForInactiveSession();
static void getSessionIdListInPlantable(PGconn* conn);
static void deletePlanTableDataS(PGconn* conn, bool missing_ok);
static void addSessionIdInfo(char* sessid);
static void deletePlanTableData(PGconn* conn, char* sessid, bool missing_ok);
static void cleanPlanTableSessidList();
/* For parallel clean */
const int DEFAULT_GS_CLEAN_WORK_NUM = 3;
const int MIN_GS_CLEAN_WORK_NUM = 1;
const int MAX_GS_CLEAN_WORK_NUM = 10;
int g_gs_clean_worker_num = DEFAULT_GS_CLEAN_WORK_NUM;
const int GS_CLEAN_CHECK_SECONDS = 15; /* check per seconds */
volatile int g_receive_chld_exit_sig = false;
volatile GSCleanGlobalThreads g_ThreadsData;
volatile GSCleanGlobalThreads* g_Threads = &g_ThreadsData;
pthread_mutex_t g_init_worker_lock;
pthread_mutex_t g_thread_lock;
pthread_t g_main_threadid;
static void* gs_clean_main(void* arg);
static void gs_clean_start(void);
static void gs_clean_end(void);
void gs_clean_work(void);
static bool check_gs_clean_done(bool need_lock);
static void cleanup_gs_clean_worker(void* arg);
static void cleanup_gs_clean_worker(void* arg)
{
bool send_signal = false;
if (pthread_mutex_lock(&g_thread_lock) != 0) {
write_stderr("fail to lock g_thread_lock.\n");
exit(1);
}
CleanWorkerInfo* wkinfo = (CleanWorkerInfo*)arg;
/* set worker status and clean connection */
wkinfo->worker_status = WRKR_TERMINATED;
PQfinish(wkinfo->conn);
wkinfo->conn = NULL;
send_signal = check_gs_clean_done(false);
if (pthread_mutex_unlock(&g_thread_lock) != 0) {
write_stderr("fail to release g_thread_lock.\n");
exit(1);
}
if (send_signal) {
if (pthread_kill(g_main_threadid, SIGCHLD) != 0) {
write_stderr("fail to kill thread %ld.\n", g_main_threadid);
}
}
}
static bool GetTxnInfo(CleanWorkerInfo* wkinfo)
{
int num = wkinfo->work_job;
char* database_name = (char*)(clean_all_databases ? "postgres" : head_database_names->database_name);
wkinfo->conn = loginDatabase(
coordinator_host, coordinator_port, username, password, database_name, progname, "auto", password_prompt);
if (wkinfo->conn == NULL) {
write_stderr("job%d: %s Could not connect to the database %s.\n", num, formatLogTime(), database_name);
return false;
}
if (!setMaintenanceMode(wkinfo->conn, true)) {
/* Cannot set */
write_stderr(
"job%d: %s fail to set xc_maintenance_mode to the database %s.\n", num, formatLogTime(), database_name);
PQfinish(wkinfo->conn);
wkinfo->conn = NULL;
return false;
}
if (verbose_opt)
write_stderr("job%d: %s %s: connected to the database \"%s\"\n", num, formatLogTime(), progname, database_name);
/*
* Check status of each prepared transaction. To do this, look into
* nodes where the transaction is not recorded as "prepared".
* Possible status are unknown (prepare has not been issued), committed or
* aborted, or running.
*/
getTxnInfoOnOtherNodesAll(wkinfo);
if (verbose_opt) {
/* Print all the prepared transaction list */
database_info* cur_db = NULL;
write_stderr("job%d: %s %s: 2PC transaction list.\n", num, formatLogTime(), progname);
for (cur_db = head_database_info; cur_db != NULL; cur_db = cur_db->next) {
txn_info* txn = NULL;
write_stderr("job%d: %s Database: \"%s\":\n", num, formatLogTime(), cur_db->database_name);
for (txn = cur_db->all_head_txn_info[num]; txn != NULL; txn = txn->next) {
write_stderr("job%d: %s localxid: %lu, gid: \"%s\", owner: %s\n",
num,
formatLogTime(),
txn->localxid,
txn->gid,
txn->owner);
for (int ii = 0; ii < pgxc_clean_node_count; ii++) {
write_stderr("job%d: %s node: %s, status: %s\n",
num,
formatLogTime(),
pgxc_clean_node_info[ii].node_name,
str_txn_stat(txn->txn_stat[ii]));
}
}
}
}
/*
* Then disconnect from the database.
* I need to login to specified databases which 2PC is issued for. Again, we assume
* that all the prepare is issued against the same database in each node, which
* current Coordinator does and there seems to be no way to violate this assumption.
*/
if (verbose_opt) {
write_stderr("job%d: %s %s: disconnecting\n", num, formatLogTime(), progname);
}
PQfinish(wkinfo->conn);
wkinfo->conn = NULL;
/*
* If --no-clean option is specified, we exit here.
*/
if (no_clean_opt) {
write_stderr("job%d: %s --no-clean opt is specified. Exiting.\n", num, formatLogTime());
return false;
} else {
return true;
}
}
static void* gs_clean_main(void* arg)
{
CleanWorkerInfo* wkinfo = (CleanWorkerInfo*)arg;
bool success_get_txn = false;
if (pthread_detach(pthread_self()) != 0) {
write_stderr("pthread detach failed.\n");
exit(1);
}
pthread_cleanup_push(cleanup_gs_clean_worker, wkinfo);
if (pthread_mutex_lock(&g_init_worker_lock) != 0) {
write_stderr("fail to lock init_worker_lock.\n");
exit(1);
}
wkinfo->worker_status = WRKR_WORKING;
if (pthread_mutex_unlock(&g_init_worker_lock) != 0) {
write_stderr("fail to release init_worker_lock.\n");
exit(1);
}
/* do actural work here */
success_get_txn = GetTxnInfo(wkinfo);
Assert(wkinfo->work_job >= 0);
Assert(wkinfo->work_job < g_gs_clean_worker_num);
/* if get Txn info success , we do clean job */
if (success_get_txn) {
/*
* Recover 2PC for specified databases
*/
if (clean_all_databases) {
database_info* cur_database_info = NULL;
for (cur_database_info = head_database_info; cur_database_info != NULL;
cur_database_info = cur_database_info->next) {
recover2PCForDatabase(cur_database_info, wkinfo);
}
} else {
database_info* cur_database_info = NULL;
database_names* cur_database_name = NULL;
for (cur_database_name = head_database_names; cur_database_name != NULL;
cur_database_name = cur_database_name->next) {
cur_database_info = find_database_info(cur_database_name->database_name);
if (cur_database_info != NULL) {
recover2PCForDatabase(cur_database_info, wkinfo);
}
}
}
}
pthread_cleanup_pop(1);
return ((void*)0);
}
static void gs_clean_start(void)
{
int i = 0;
int err = 0;
errno_t rc = EOK;
/* block sub threads until all start works done. */
if (pthread_mutex_lock(&g_init_worker_lock) != 0) {
write_stderr("fail to lock init_worker_lock.\n");
exit(1);
}
/* position 0 has been arranged to master. */
for (i = 1; i <= g_gs_clean_worker_num; i++) {
CleanWorkerInfo* wkinfo = NULL;
wkinfo = &(g_Threads->worker_info[i]);
rc = memset_s(wkinfo, sizeof(CleanWorkerInfo), 0, sizeof(CleanWorkerInfo));
securec_check_c(rc, "\0", "\0");
/* construct wkinfo */
wkinfo->is_main_thread = false;
wkinfo->worker_status = WRKR_IDLE;
/* ignore main thread */
wkinfo->work_job = i - 1;
err = pthread_create(&wkinfo->thr_id, NULL, gs_clean_main, wkinfo);
if (err != 0) {
(void)pthread_mutex_unlock(&g_init_worker_lock);
write_stderr("%s: failed to create a new worker thread: %d\n", formatLogTime(), err);
exit(1);
}
g_Threads->num_workers++;
}
Assert(g_Threads->num_workers - 1 == g_gs_clean_worker_num);
write_stderr("%s The active workers is : %d\n", formatLogTime(), g_Threads->num_workers - 1);
if (pthread_mutex_unlock(&g_init_worker_lock) != 0) {
write_stderr("fail to release init_worker_lock.\n");
exit(1);
}
}
static void gs_clean_end(void)
{
/* Only left main thread */
g_Threads->num_workers = 1;
}
void gs_clean_work(void)
{
gs_clean_start();
for (;;) {
if (g_receive_chld_exit_sig) {
write_stderr("%s recive child exit signal, all child workers exit.\n", formatLogTime());
}
if (check_gs_clean_done(true)) {
write_stderr("%s all 2pc clean workers have finished jobs.\n", formatLogTime());
break;
}
sleep(GS_CLEAN_CHECK_SECONDS);
}
gs_clean_end();
}
/* hold lock and check if all jobs have done */
static bool check_gs_clean_done(bool need_lock)
{
int i = 0;
int count = 0;
CleanWorkerInfo* wkinfo = NULL;
if (need_lock && (pthread_mutex_lock(&g_thread_lock) != 0)) {
write_stderr("fail to lock g_thread_lock.\n");
exit(1);
}
for (i = 0; i < g_Threads->num_workers; i++) {
wkinfo = &(g_Threads->worker_info[i]);
if (wkinfo->is_main_thread == false) {
if (wkinfo->worker_status != WRKR_TERMINATED)
count++;
}
}
if (need_lock && (pthread_mutex_unlock(&g_thread_lock) != 0)) {
write_stderr("fail to release g_thread_lock.\n");
exit(1);
}
if (count == 0) {
return true;
} else {
return false;
}
}
static void initGSCleanWorker(void)
{
int rc;
CleanWorkerInfo* maininfo = NULL;
g_Threads->worker_info = (CleanWorkerInfo*)calloc((g_gs_clean_worker_num + 1), sizeof(CleanWorkerInfo));
if (g_Threads->worker_info == NULL) {
write_stderr("out of memory, malloc failed.");
exit(1);
}
g_Threads->num_workers = 1;
/* set main thread worker info */
maininfo = &(g_Threads->worker_info[0]);
rc = memset_s(maininfo, sizeof(CleanWorkerInfo), 0, sizeof(CleanWorkerInfo));
securec_check_c(rc, "\0", "\0");
maininfo->worker_status = WRKR_WORKING;
maininfo->is_main_thread = true;
g_main_threadid = pthread_self();
if (pthread_mutex_init(&g_init_worker_lock, NULL) != 0) {
write_stderr("fail to init g_init_worker_lock.\n");
exit(1);
}
if (pthread_mutex_init(&g_thread_lock, NULL) != 0) {
write_stderr("fail to init g_thread_lock.\n");
exit(1);
}
}
static void GSCleanChildWorkerExit(int sig)
{
g_receive_chld_exit_sig = true;
return;
}
/*
* Connection to the Coordinator
*/
PGconn* coord_conn;
static inline int strtolSafe(const char* nptr, int base)
{
char* tmp = NULL;
int res = (int)strtol(nptr, &tmp, base);
if (unlikely(*tmp != '\0')) {
write_stderr("%s %s: strtol failed, input str:%s\n", formatLogTime(), progname, nptr);
exit(1);
}
return res;
}
static inline int64 strtollSafe(const char* nptr, int base)
{
char* tmp = NULL;
int64 res = strtoll(nptr, &tmp, base);
if (unlikely(*tmp != '\0')) {
write_stderr("%s %s: strtoll failed, input str:%s\n", formatLogTime(), progname, nptr);
exit(1);
}
return res;
}
static inline char* strrchrSafe(char* str, int c)
{
char* res = strrchr(str, c);
if (unlikely(res == NULL)) {
write_stderr("%s %s: strrchr failed, can't find '%c' in '%s'\n",
formatLogTime(), progname, (char)c, str);
exit(1);
}
return res;
}
/*
*
* Main
*
*/
int main(int argc, char* argv[])
{
errno_t errorno = EOK;
int rc;
bool twoPCExists = true;
if (signal(SIGCHLD, GSCleanChildWorkerExit) == SIG_ERR) {
write_stderr("Register signal 'SIFCHLD' failed.\n");
exit(1);
}
/* Should setup pglocale when it is supported by XC core */
if (argc > 1) {
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) {
usage();
exit(0);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) {
showVersion();
exit(0);
}
}
parse_pgxc_clean_options(argc, argv);
/*
* Check missing arguments
*/
if (clean_all_databases == false && head_database_names == NULL) {
write_stderr("%s %s: you must specify -a or -d option.\n", formatLogTime(), progname);
exit(1);
}
/* log output redirect */
init_log(PROG_NAME);
if (coordinator_host == NULL) {
char* pghost = NULL;
if ((pghost = getenv("PGHOST")) == NULL || strlen(pghost) > MAXPGPATH) {
coordinator_host = "";
} else {
check_env_value_c(pghost);
coordinator_host = pg_strdup(pghost);
}
}
if (coordinator_port == -1) {
/* Default Coordinator port */
char* pgport = NULL;
if ((pgport = getenv("PGPORT")) == NULL) {
coordinator_port = DEF_PGPORT; /* pg_config.h */
} else {
check_env_value_c(pgport);
coordinator_port = strtolSafe(pgport, 10);
}
}
if (username == NULL) {
errorno = strcpy_s(password_prompt, sizeof(password_prompt), "Password: ");
securec_check_c(errorno, "\0", "\0");
} else {
rc = snprintf_s(
password_prompt, sizeof(password_prompt), sizeof(password_prompt) - 1, "Password for user %s: ", username);
securec_check_ss_c(rc, "\0", "\0");
}
if (try_password_opt == TRI_YES && NULL == password)
password = simple_prompt(password_prompt, 100, false);
/* Tweak options --> should be improved in the next releases */
if (status_opt) {
verbose_opt = true;
}
if (verbose_opt) {
/* Print environments */
write_stderr("%s %s (%s): Cleanup outstanding 2PCs.\n", formatLogTime(), progname, PG_VERSION);
/* Target databaess */
write_stderr("%s Target databases:", formatLogTime());
if (clean_all_databases) {
write_stderr("(ALL)\n");
} else {
database_names* cur_name = NULL;
for (cur_name = head_database_names; cur_name != NULL; cur_name = cur_name->next)
write_stderr("%s %s", cur_name->database_name, formatLogTime());
write_stderr("\n");
}
/* Username to use */
write_stderr("%s Username: %s\n", formatLogTime(), username != NULL ? username : "default");
/* Status opt */
write_stderr("%s Status opt: %s\n", formatLogTime(), status_opt ? "on" : "off");
/* No-dlean opt */
write_stderr("%s no-clean: %s\n", formatLogTime(), no_clean_opt ? "on" : "off");
/* rollback_cn_name */
write_stderr("%s rollback-cnname: %s\n", formatLogTime(), rollback_cn_name != NULL ? rollback_cn_name : "off");
}
/* init */
initGSCleanWorker();
/* Connect to XC server */
if (verbose_opt) {
write_stderr("%s %s: connecting to database \"%s\", host: \"%s\", port: %d\n",
formatLogTime(),
progname,
clean_all_databases ? "postgres" : head_database_names->database_name,
coordinator_host,
coordinator_port);
}
coord_conn = loginDatabase(coordinator_host,
coordinator_port,
username,
password,
(char*)(clean_all_databases ? "postgres" : head_database_names->database_name),
progname,
"auto",
password_prompt);
if (NULL == coord_conn) {
write_stderr("%s %s: connect failed\n", formatLogTime(), progname);
exit(1);
}
if (verbose_opt) {
write_stderr("%s %s: connected successfully\n", formatLogTime(), progname);
}
/* Set maintenance mode for skipping xact sync wait */
if (!setMaintenanceMode(coord_conn, true)) {
/* Cannot recover */
write_stderr("%s can not set maintenance mode .\n", formatLogTime());
PQfinish(coord_conn);
exit(1);
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* Get my nodename (connected Coordinator) and gtm_mode
*/
getMyNodename(coord_conn);
getMyGtmMode(coord_conn);
if (verbose_opt) {
write_stderr("%s %s: Connected to the node \"%s\", current gtm_option: %s, option_num: %d.\n",
formatLogTime(),
progname,
my_nodename,
transfor_gtm_optoin(gtm_option, gtm_mode),
gtm_option_num);
}
#endif
/*
* Get available databases
*
* pgxc_clean assumes that all the database are available from the connecting Coordinator.
* Some (expert) DBA can create a database local to subset of the node by EXECUTE DIRECT.
* In this case, DBA may have to clean outstanding 2PC transactions manually or clean
* 2PC transactions by connecting pgxc_clean to different Coordinators.
*
* If such node-subset database is found to be used widely, pgxc_clean may need
* an extension to deal with this case.
*/
if (clean_all_databases)
getDatabaseList(coord_conn);
if (verbose_opt) {
database_info* cur_database = NULL;
write_stderr("%s %s: Databases visible from the node \"%s\": ", formatLogTime(), progname, my_nodename);
if (head_database_info != NULL) {
for (cur_database = head_database_info; cur_database != NULL; cur_database = cur_database->next) {
write_stderr("%s \"%s\"", formatLogTime(), cur_database->database_name);
}
write_stderr("\n");
}
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* Get list of Nodes
*
* As in the case of database, we clean transactions in visible nodes from the
* connecting Coordinator. DBA can also setup different node configuration
* at different Coordinators. In this case, DBA should be careful to choose
* appropriate Coordinator to clean up transactions.
*/
getNodeList(coord_conn);
if (verbose_opt) {
int ii;
write_stderr("%s %s: Node list visible from the node \"%s\"\n", formatLogTime(), progname, my_nodename);
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
write_stderr("%s Name: %s, host: %s, port: %d/%d, type: %s\n",
formatLogTime(),
pgxc_clean_node_info[ii].node_name,
pgxc_clean_node_info[ii].host,
pgxc_clean_node_info[ii].port,
pgxc_clean_node_info[ii].port + 1,
pgxc_clean_node_info[ii].type == NODE_TYPE_COORD ? "coordinator" : "datanode");
}
}
#endif
/*
* Only clean temp schema if clean_temp_schema_only == true,
* and then exit
*/
if (clean_temp_schema_only) {
PQfinish(coord_conn);
coord_conn = NULL;
if (!no_clean_opt) {
dropTempNamespace();
}
exit(0);
}
#ifndef ENABLE_MULTIPLE_NODES
else {
PQfinish(coord_conn);
coord_conn = NULL;
write_stderr("%s %s: We only clean temp schema in openGauss, please specify '-e' option.\n",
formatLogTime(), progname);
exit(0);
}
#endif
/*
* Only clean plan_table if clean_plan_table_only == true,
* and then exit
*/
if (clean_plan_table_only) {
PQfinish(coord_conn);
coord_conn = NULL;
if (!no_clean_opt) {
deletePlanTableDataForInactiveSession();
}
exit(0);
}
/*
* Get list of prepared statement
*/
getPreparedTxnList(coord_conn);
/*
* Check if there're any 2PC candidate to recover
*/
if (!check2PCExists()) {
write_stderr("%s %s: There's no prepared 2PC in this cluster.\n", formatLogTime(), progname);
twoPCExists = false;
}
if (twoPCExists) {
/*
* Then disconnect from the database.
* I need to login to specified databases which 2PC is issued for. Again, we assume
* that all the prepare is issued against the same database in each node, which
* current Coordinator does and there seems to be no way to violate this assumption.
*/
if (verbose_opt) {
write_stderr("%s %s: disconnecting\n", formatLogTime(), progname);
}
PQfinish(coord_conn);
coord_conn = NULL;
/* parallel gs clean work */
gs_clean_work();
/*
* If --no-clean option is specified, we exit here.
*/
if (no_clean_opt) {
write_stderr("%s --no-clean opt is specified. Exiting.\n", formatLogTime());
exit(0);
}
}
PQfinish(coord_conn);
coord_conn = NULL;
if (!no_clean_opt && !no_clean_temp_schema_tbspc) {
/* Drop non-active temp schema of each database. */
dropTempNamespace();
/* Delete non-active session data in 'plan_table_data' for each database. */
deletePlanTableDataForInactiveSession();
}
/* clear sensitive info when exit */
if (password != NULL) {
rc = memset_s(password, strlen(password), 0, strlen(password));
securec_check_c(rc, "\0", "\0");
free(password);
password = NULL;
}
if (do_commit_abort_failed)
exit(1);
else
exit(0);
}
/*
* @Description: check if 'plan_table_data' exist.
* @in: void
* @return: if 'plan_table_data' does not exist, return true. Then return false.
*/
static bool tableIsNotExist(PGconn* conn)
{
PGresult* res = NULL;
errno_t rc = EOK;
#define stmt_len 60
/* make up SQL delete stmt */
char* stmt_select_pt_from_pgclass = (char*)malloc(stmt_len + 1);
checkAllocMem(stmt_select_pt_from_pgclass);
rc = snprintf_s(stmt_select_pt_from_pgclass,
stmt_len + 1,
stmt_len,
"SELECT OID FROM pg_class where relname='plan_table_data';");
securec_check_ss_c(rc, "\0", "\0");
res = PQexec(conn, stmt_select_pt_from_pgclass);
/* 'plan_table_data' does not exist, return true. */
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) {
free(stmt_select_pt_from_pgclass);
PQclear(res);
return true;
}
/* 'plan_table_data' already exist, return false. */
free(stmt_select_pt_from_pgclass);
PQclear(res);
return false;
}
/*
* @Description: Main entrance for delete inactive backend data in plan_table_data of current CN for each database.
* @in: void
* @return: void
*/
static void deletePlanTableDataForInactiveSession()
{
if (head_database_info == NULL)
return;
database_info* cur_database = NULL;
for (cur_database = head_database_info; cur_database != NULL; cur_database = cur_database->next) {
coord_conn = loginDatabase(coordinator_host,
coordinator_port,
username,
password,
cur_database->database_name,
progname,
"auto",
password_prompt);
if (coord_conn == NULL) {
write_stderr("%s could not connect to the database %s.\n", formatLogTime(), cur_database->database_name);
return;
}
if (tableIsNotExist(coord_conn))
return;
if (verbose_opt)
write_stderr("%s %s: ready to delete from plan_table_data for database \"%s\"\n",
formatLogTime(),
progname,
cur_database->database_name);
if (!setMaintenanceMode(coord_conn, false)) {
/* Cannot recover */
write_stderr("%s can not set maintenance mode.\n", formatLogTime());
PQfinish(coord_conn);
coord_conn = NULL;
return;
}
if (verbose_opt)
write_stderr(
"%s %s: connected to the database \"%s\"\n", formatLogTime(), progname, cur_database->database_name);
(void)getSessionIdListInPlantable(coord_conn);
/* we only can get the backend pid of the current cn.*/
(void)getActiveBackendListOnCN(coord_conn);
(void)deletePlanTableDataS(coord_conn, false);
(void)cleanBackendList();
if (verbose_opt)
write_stderr("%s %s: delete from plan_table_data for database \"%s\" finished\n",
formatLogTime(),
progname,
cur_database->database_name);
PQfinish(coord_conn);
coord_conn = NULL;
}
}
static bool checkDangerCharacter(const char* input)
{
const char* dangerCharacterList[] = {";", "`", "\\", "'", "\"", ">", "<", "&", "|", "!", NULL};
for (int i = 0; dangerCharacterList[i] != NULL; i++) {
if (strstr(input, dangerCharacterList[i]) != NULL) {
return true;
}
}
return false;
}
/*
* @Description: get the session_id_list from plan_table_data of current database.
* @in conn: connection to the database.
* @return: void
*/
static void getSessionIdListInPlantable(PGconn* conn)
{
int sessid_count;
int i;
PGresult* res = NULL;
errno_t rc = EOK;
char* session_id = NULL;
#define STMT_LEN 48
char* stmt_get_sessid_list = (char*)malloc(STMT_LEN + 1);
checkAllocMem(stmt_get_sessid_list);
(void)memset_s(stmt_get_sessid_list, STMT_LEN + 1, '\0', STMT_LEN + 1);
rc = snprintf_s(stmt_get_sessid_list, STMT_LEN + 1, STMT_LEN, "SELECT DISTINCT(session_id) FROM plan_table_data");
securec_check_ss_c(rc, "\0", "\0");
if (verbose_opt)
write_stderr("%s start getting sessiond_id from PLAN_TABLE_DATA on %s\n", formatLogTime(), my_nodename);
res = PQexec(conn, stmt_get_sessid_list);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s could not obtain sessiond_id from PLAN_TABLE_DATA.\n", formatLogTime());
PQclear(res);
free(stmt_get_sessid_list);
exit(1);
}
sessid_count = PQntuples(res);
/* get sission id list */
for (i = 0; i < sessid_count; i++) {
session_id = PQgetvalue(res, i, 0);
if (checkDangerCharacter(session_id)) {
write_stderr("%s session id contains invalid token:%s\n", formatLogTime(), session_id);
PQclear(res);
free(stmt_get_sessid_list);
exit(1);
}
addSessionIdInfo(session_id);
}
PQclear(res);
free(stmt_get_sessid_list);
}
/*
* @Description: add session id info into session id list.
* @in sessid: session id is made up of thread start time and pid.
* @return: void
*/
static void addSessionIdInfo(char* sessid)
{
sessionid_info* add_sessid = NULL;
add_sessid = (sessionid_info*)malloc(sizeof(sessionid_info));
checkAllocMem(add_sessid);
add_sessid->next = NULL;
add_sessid->session_id = strdup(sessid);
checkAllocMem(add_sessid->session_id);
if (verbose_opt)
write_stderr("%s add session id: %s\n", formatLogTime(), sessid);
if (session_id_info == NULL)
session_id_info = add_sessid;
else {
add_sessid->next = session_id_info->next;
session_id_info->next = add_sessid;
}
}
/*
* @Description: delete plan table data for all thread if need.
* @in conn: connection to the database.
* @in missing_ok: missing is not ok.
* @return: void
*/
static void deletePlanTableDataS(PGconn* conn, bool missing_ok)
{
sessionid_info* sessid_cell = session_id_info;
activebackend_info* bkid_cell = NULL;
if (session_id_info == NULL || active_backend_info == NULL)
return;
while (sessid_cell != NULL) {
int64 plan_table_data_inserted_thread = strtollSafe(strrchrSafe(sessid_cell->session_id, '.') + 1, 10);
bkid_cell = active_backend_info;
while (bkid_cell != NULL) {
if (plan_table_data_inserted_thread == bkid_cell->sessionID)
break;
bkid_cell = bkid_cell->next;
}
/* Now, we have find the inactive session id in plan_table_data */
if (bkid_cell == NULL) {
/*
* We don`t need to make sure the thread may be active on other cn.
* because the data store in each CN is not shared among others.
* We should only delete data on this node, now we just done like we want.
*/
deletePlanTableData(conn, sessid_cell->session_id, missing_ok);
}
sessid_cell = sessid_cell->next;
}
/* free session_id list. */
cleanPlanTableSessidList();
}
/*
* @Description: excute the sql to delete plan table data.
* @in conn: connection to the database.
* @in sessid: session_id that should be delete.
* @in missing_ok: missing is not ok.
* @return: void
*/
static void deletePlanTableData(PGconn* conn, char* sessid, bool missing_ok)
{
PGresult* res = NULL;
errno_t rc = EOK;
/* 32 is session_id length. */
#define delete_stmt_len (45 + 32)
/* make up SQL delete stmt */
char* stmt_delete_plan_table_data = (char*)malloc(delete_stmt_len + 1);
checkAllocMem(stmt_delete_plan_table_data);
rc = snprintf_s(stmt_delete_plan_table_data,
delete_stmt_len + 1,
delete_stmt_len,
"DELETE FROM plan_table_data where session_id=%s;",
sessid);
securec_check_ss_c(rc, "\0", "\0");
if (verbose_opt)
write_stderr("%s %s\n", formatLogTime(), stmt_delete_plan_table_data);
res = PQexec(conn, stmt_delete_plan_table_data);
/* If execution is not successful, give an log and go on.*/
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK) {
write_stderr("%s could not delete data from plan_table_data: %s\n", formatLogTime(), PQresultErrorMessage(res));
}
free(stmt_delete_plan_table_data);
PQclear(res);
}
/*
* @Description: free the session_id list.
* @return: void
*/
static void cleanPlanTableSessidList()
{
sessionid_info* sessid_cell = session_id_info;
sessionid_info* delete_cell = NULL;
while (sessid_cell != NULL) {
delete_cell = sessid_cell;
sessid_cell = sessid_cell->next;
if (delete_cell->session_id != NULL) {
free(delete_cell->session_id);
}
free(delete_cell);
}
session_id_info = NULL;
}
/*
* @Description: Drop inactive temp schemas of each database.
* @in: void
* @return: void
*/
static void dropTempNamespace()
{
database_info* cur_database = NULL;
#ifdef ENABLE_MULTIPLE_NODES
int datanode_no = -1;
#endif
if (head_database_info != NULL) {
for (cur_database = head_database_info; cur_database != NULL; cur_database = cur_database->next) {
if (verbose_opt)
write_stderr("%s %s: drop temp namespace for database \"%s\"\n",
formatLogTime(),
progname,
cur_database->database_name);
coord_conn = loginDatabase(coordinator_host,
coordinator_port,
username,
password,
cur_database->database_name,
progname,
"auto",
password_prompt);
if (coord_conn == NULL) {
write_stderr(
"%s Could not connect to the database %s.\n", formatLogTime(), cur_database->database_name);
return;
}
if (!setMaintenanceMode(coord_conn, true)) {
/* Cannot recover */
write_stderr("%s can not set maintenance mode.\n", formatLogTime());
PQfinish(coord_conn);
coord_conn = NULL;
return;
}
if (!setEnableParallelDDL(coord_conn, false)) {
/* Cannot recover */
write_stderr("%s can not set enable_parallel_ddl.\n", formatLogTime());
PQfinish(coord_conn);
coord_conn = NULL;
return;
}
if (verbose_opt)
write_stderr("%s %s: connected to the database \"%s\"\n",
formatLogTime(),
progname,
cur_database->database_name);
(void)getTempSchemaListOnCN(coord_conn);
(void)getActiveBackendListOnCN(coord_conn);
(void)dropTempSchemas(coord_conn, true);
(void)cleanBackendList();
#ifdef ENABLE_MULTIPLE_NODES
datanode_no = getTempSchemaListOnDN(coord_conn);
(void)getActiveBackendListOnCN(coord_conn, datanode_no);
(void)dropTempSchemas(coord_conn, true);
(void)cleanBackendList();
#endif
if (verbose_opt)
write_stderr("%s %s: drop temp namespace for database \"%s\" finished\n",
formatLogTime(),
progname,
cur_database->database_name);
PQfinish(coord_conn);
coord_conn = NULL;
}
}
}
static void dropTempSchemas(PGconn* conn, bool missing_ok)
{
tempschema_info* cell1 = temp_schema_info;
activebackend_info* cell2 = NULL;
char temp_buffer[NAMEDATALEN] = {0};
char temp_buffer2[NAMEDATALEN] = {0};
#ifdef ENABLE_MULTIPLE_NODES
char node_name[NAMEDATALEN];
bool onOtherCNNode = false;
#endif
errno_t rc;
if (temp_schema_info == NULL || active_backend_info == NULL)
return;
while (cell1 != NULL) {
/*
* Get threadID, tempID, timelineID from end to start. Temp schema name is pg_temp_%s_%u_%u_%lu.
* These items are CN'name, timelineID, tempID, threadID.
*/
const char* last_pos = strrchrSafe(cell1->tempschema_name, '_');
int64 temp_table_created_session = strtollSafe(last_pos + 1, 10);
rc = strncpy_s(temp_buffer, sizeof(temp_buffer), cell1->tempschema_name, last_pos - cell1->tempschema_name);
securec_check_c(rc, "\0", "\0");
int temp_table_lwtid = 0;
temp_table_lwtid = strtolSafe(strrchrSafe(temp_buffer, '_') + 1, 10);
const char* second_last_pos = strrchrSafe(temp_buffer, '_');
uint32 tempID = strtolSafe(second_last_pos + 1, 10);
rc = strncpy_s(temp_buffer2, sizeof(temp_buffer2), temp_buffer, second_last_pos - temp_buffer);
securec_check_c(rc, "\0", "\0");
uint32 timeLineID = strtolSafe(strrchrSafe(temp_buffer2, '_') + 1, 10);
cell2 = active_backend_info;
/*
* Tid is obtained from pthread_self, which is reuse often.
* When the session of a temp table is actually out-of-date, and a new session reuse the tid,
* we cannot drop the temp table although it is out-of-data.
* Thus, We use tid, timeLineID and tempID together to judge which session a temp table belongs to, instead of
* tid only.
*/
while (cell2 != NULL) {
if (temp_table_created_session == cell2->sessionID && tempID == cell2->tempID &&
timeLineID == cell2->timeLineID)
break;
cell2 = cell2->next;
}
if (cell2 == NULL) {
#ifdef ENABLE_MULTIPLE_NODES
/*
* now it is not the active backend, furthermore, we need to
* check whether the schema on the other node:
* if not
* (there are two scenarios:
* 1. the schema built on the current CN or DN node
* 2. the schema built on a dropped node
* ),
* drop it;
*
* otherwise, keep it untouched.
*/
onOtherCNNode = false;
rc = memset_s(node_name, sizeof(node_name), 0, sizeof(node_name));
securec_check_c(rc, "", "");
/*
* tempschema format: pg_temp_nodename_xx_xx_PID
*
* to get node name, we set char '_' just behind nodename as '\0',
* recall that there are three '_' behind the nodename, and copy
* node name start from &tempschema_name[8].
*
* Note: nodename may contain several chars '_' itself, but there
* are specific numbers(i.e. 3) of '_' behind the nodename.
*/
char* p = NULL;
int j = 0;
rc = strcpy_s(node_name, sizeof(node_name), &cell1->tempschema_name[8]);
securec_check_c(rc, "\0", "\0");
while (j < 3) {
p = strrchr(node_name, '_');
if (p == NULL) {
write_stderr("%s Check temp schema: %s, Expected format: pg_temp_nodename_xx_xx_PID.\n",
formatLogTime(),
cell1->tempschema_name);
exit(1);
}
*p = '\0';
j++;
}
Assert(*node_name != '\0');
if (strlen(my_nodename) != strlen(node_name)) {
write_stderr("%s Node name %s in temp schema: %s could be truncated in InitTempTableNamespace.\n",
formatLogTime(),
node_name,
cell1->tempschema_name);
}
/* if it is current CN node, just drop it (recall that it is non-active backend one) */
if (strcmp(my_nodename, node_name) == 0)
dropTempSchema(conn, cell1->tempschema_name, missing_ok);
else {
/*
* otherwise, if it is other CN node(do not include dropped node), untouch it
* since these temp schemas will be dropped by their CN elsewhere
*/
for (int i = 0; i < pgxc_clean_node_count; i++) {
if (pgxc_clean_node_info[i].type == NODE_TYPE_COORD &&
strcmp(pgxc_clean_node_info[i].node_name, node_name) == 0) {
/*
* find the schema on other CN node, if find, we do not drop the schema.
* here, the case 'node_name == my_nodename' can't not happen
*/
onOtherCNNode = true;
break;
}
}
/* only drop schemas related to this node (include the obsolete node) */
if (!onOtherCNNode)
dropTempSchema(conn, cell1->tempschema_name, missing_ok);
}
#else
dropTempSchema(conn, cell1->tempschema_name, missing_ok);
#endif
}
cell1 = cell1->next;
}
cleanTempSchemaList();
}
static void cleanTempSchemaList()
{
tempschema_info* cell1 = temp_schema_info;
tempschema_info* saved_cell = NULL;
while (cell1 != NULL) {
saved_cell = cell1;
cell1 = cell1->next;
free(saved_cell->tempschema_name);
free(saved_cell);
}
temp_schema_info = NULL;
}
static void cleanBackendList()
{
activebackend_info* cell1 = active_backend_info;
activebackend_info* saved_cell = NULL;
while (cell1 != NULL) {
saved_cell = cell1;
cell1 = cell1->next;
free(saved_cell);
}
active_backend_info = NULL;
}
static void dropTempSchema(PGconn* conn, char* nspname, bool missing_ok)
{
#define dropSchemaStmtLen (2 * NAMEDATALEN + 64)
PGresult* res = NULL;
int rc;
char* toastnspname = (char*)malloc(NAMEDATALEN);
checkAllocMem(toastnspname);
/* SQL Statement */
char* STMT_DROP_TEMP_SCHEMA = (char*)malloc(dropSchemaStmtLen);
checkAllocMem(STMT_DROP_TEMP_SCHEMA);
rc = snprintf_s(toastnspname, NAMEDATALEN, strlen(nspname) + 7, "pg_toast_temp_%s", nspname + 8);
securec_check_ss_c(rc, "\0", "\0");
rc = snprintf_s(STMT_DROP_TEMP_SCHEMA,
dropSchemaStmtLen,
2 * strlen(nspname) + 30,
"DROP SCHEMA %s, %s CASCADE;",
nspname,
toastnspname);
securec_check_ss_c(rc, "\0", "\0");
if (missing_ok)
rc = snprintf_s(STMT_DROP_TEMP_SCHEMA,
dropSchemaStmtLen,
2 * strlen(nspname) + 40,
"DROP SCHEMA IF EXISTS %s, %s CASCADE;",
nspname,
toastnspname);
else
rc = snprintf_s(STMT_DROP_TEMP_SCHEMA,
dropSchemaStmtLen,
2 * strlen(nspname) + 30,
"DROP SCHEMA %s, %s CASCADE;",
nspname,
toastnspname);
securec_check_ss_c(rc, "\0", "\0");
if (verbose_opt)
write_stderr("%s %s\n", formatLogTime(), STMT_DROP_TEMP_SCHEMA);
res = PQexec(conn, STMT_DROP_TEMP_SCHEMA);
/* If exec is not success, give an log and go on. */
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK) {
write_stderr("%s Could not drop temp schema %s, %s: %s\n",
formatLogTime(),
nspname,
toastnspname,
PQresultErrorMessage(res));
}
free(STMT_DROP_TEMP_SCHEMA);
free(toastnspname);
PQclear(res);
}
static char* pg_strdup(const char* string)
{
char* tmp = NULL;
if (string == NULL) {
write_stderr("%s pg_strdup: cannot duplicate null pointer (internal error)\n", formatLogTime());
exit(1);
}
tmp = strdup(string);
if (tmp == NULL) {
write_stderr("%s pg_strdup: out of memory\n", formatLogTime());
exit(1);
}
return tmp;
}
#ifdef ENABLE_MULTIPLE_NODES
static void getMyNodename(PGconn* conn)
{
static const char* stmt = "SELECT pgxc_node_str()";
PGresult* res = NULL;
res = PQexec(conn, stmt);
/* Error handling here */
if (res != NULL && (PQresultStatus(res) == PGRES_TUPLES_OK))
my_nodename = pg_strdup(PQgetvalue(res, 0, 0));
else
my_nodename = pg_strdup("unknown");
PQclear(res);
}
static void getMyGtmMode(PGconn* conn)
{
static const char* stmt = "SHOW ENABLE_GTM_FREE";
static const char* stmt_gtm_option = "SHOW GTM_OPTION";
PGresult* res = NULL;
res = PQexec(conn, stmt);
/* Error handling here */
if (res != NULL && (PQresultStatus(res) == PGRES_TUPLES_OK)) {
gtm_mode = pg_strdup(PQgetvalue(res, 0, 0));
} else {
gtm_mode = pg_strdup("unknown");
}
PQclear(res);
res = PQexec(conn, stmt_gtm_option);
/* Error handling here */
if (res != NULL && (PQresultStatus(res) == PGRES_TUPLES_OK)) {
gtm_option = pg_strdup(PQgetvalue(res, 0, 0));
gtm_option_num = atoi(gtm_option);
} else {
gtm_option = pg_strdup("unknown");
gtm_option_num = -1;
}
PQclear(res);
}
#endif
static void recover2PCForDatabase(database_info* db_info, CleanWorkerInfo* wkinfo)
{
txn_info* cur_txn = NULL;
int num = wkinfo->work_job;
if (verbose_opt)
write_stderr("job%d: %s %s: recovering 2PC for database \"%s\"\n",
num,
formatLogTime(),
progname,
db_info->database_name);
wkinfo->conn = loginDatabase(coordinator_host,
coordinator_port,
username,
password,
db_info->database_name,
progname,
"auto",
password_prompt);
if (wkinfo->conn == NULL) {
write_stderr("job%d: %s Could not connect to the database %s.\n", num, formatLogTime(), db_info->database_name);
return;
}
if (!setMaintenanceMode(wkinfo->conn, true)) {
/* Cannot recover */
write_stderr("job%d: %s Skipping database %s.\n", num, formatLogTime(), db_info->database_name);
PQfinish(wkinfo->conn);
wkinfo->conn = NULL;
return;
}
if (verbose_opt)
write_stderr(
"job%d: %s %s: connected to the database \"%s\"\n", num, formatLogTime(), progname, db_info->database_name);
for (cur_txn = db_info->all_head_txn_info[num]; cur_txn != NULL; cur_txn = cur_txn->next) {
recover2PC(wkinfo->conn, cur_txn, num);
}
PQfinish(wkinfo->conn);
wkinfo->conn = NULL;
}
static void recover2PC(PGconn* conn, txn_info* txn, int num)
{
TXN_STATUS txn_stat;
txn_stat = check_txn_global_status(txn, commit_all_prepared, rollback_all_prepared);
if (verbose_opt) {
write_stderr("job%d: %s Recovering TXN: localxid: %lu, gid: \"%s\", owner: \"%s\", global status: %s\n",
num,
formatLogTime(),
txn->localxid,
txn->gid,
txn->owner,
str_txn_stat(txn_stat));
}
switch (txn_stat) {
case TXN_STATUS_FAILED:
if (verbose_opt)
write_stderr("job%d: %s Recovery not needed.\n", num, formatLogTime());
return;
case TXN_STATUS_PREPARED:
if (commit_all_prepared)
do_commit(conn, txn, num);
else if (rollback_all_prepared)
do_abort(conn, txn, num);
else if (verbose_opt)
write_stderr(
"job%d: %s Recovery not needed, transaction status: all prepared.\n", num, formatLogTime());
return;
case TXN_STATUS_COMMITTED:
do_commit(conn, txn, num);
return;
case TXN_STATUS_ABORTED:
do_abort(conn, txn, num);
return;
case TXN_STATUS_UNCONNECT:
if (verbose_opt)
write_stderr("job%d: %s some node connect bad, do nothing for the prepared xid %lu.\n",
num,
formatLogTime(),
txn->localxid);
return;
case TXN_STATUS_RUNNING:
if (verbose_opt)
write_stderr(
"job%d: %s the prepared transaction is running, do nothing for the prepared xid %lu.\n",
num,
formatLogTime(),
txn->localxid);
return;
default:
write_stderr("job%d: %s Unknown TXN status, pgxc_clean error.\n", num, formatLogTime());
exit(1);
}
return;
}
static void do_commit(PGconn* conn, txn_info* txn, int num)
{
do_commit_abort(conn, txn, true, num);
}
static void do_abort(PGconn* conn, txn_info* txn, int num)
{
do_commit_abort(conn, txn, false, num);
}
#define COMMITSEQNO_COMMIT_INPROGRESS (UINT64CONST(1) << 62)
#define COMMITSEQNO_SUBTRANS_BIT (UINT64CONST(1) << 63)
#define MAX_COMMITSEQNO UINT64CONST((UINT64CONST(1) << 60) - 1)
#define COMMITSEQNO_FROZEN UINT64CONST(0x2)
#define COMMITSEQNO_IS_COMMITTING(csn) (((csn)&COMMITSEQNO_COMMIT_INPROGRESS) == COMMITSEQNO_COMMIT_INPROGRESS)
#define COMMITSEQNO_IS_SUBTRANS(csn) (((csn)&COMMITSEQNO_SUBTRANS_BIT) == COMMITSEQNO_SUBTRANS_BIT)
#define COMMITSEQNO_IS_COMMITTED(csn) \
(((csn)&MAX_COMMITSEQNO) >= COMMITSEQNO_FROZEN && !COMMITSEQNO_IS_SUBTRANS(csn) && !COMMITSEQNO_IS_COMMITTING(csn))
const char* const EXEC_STMT_FMT_CSN =
"SELECT * FROM GLOBAL_CLEAN_PREPARED_XACTS('%s PREPARED ''%s'' WITH ''%s'';','%s');";
const char* const GLOBAL_STMT_FMT_CSN = "%s PREPARED '%s' WITH '%s';";
const char* const EXEC_STMT_FMT = "SELECT * FROM GLOBAL_CLEAN_PREPARED_XACTS('%s PREPARED ''%s'';','%s');";
const char* const GLOBAL_STMT_FMT = "%s PREPARED '%s';";
const char* const NEW_GID_EXEC_STMT_FMT_CSN =
"SELECT * FROM GLOBAL_CLEAN_PREPARED_XACTS('N%s PREPARED ''%%s'' WITH ''%s'';','%s');";
const char* const NEW_GID_EXEC_STMT_FMT = "SELECT * FROM GLOBAL_CLEAN_PREPARED_XACTS('N%s PREPARED ''%%s'';','%s');";
const int MAXSTMTLEN = 4096;
const int MAXNODELISTLEN = (4096 - 256);
static void do_commit_abort(PGconn* conn, txn_info* txn, bool is_commit, int num)
{
int ii;
char stmt[MAXSTMTLEN];
char node_list[MAXNODELISTLEN];
PGresult* res = NULL;
ExecStatusType res_status;
int rc;
int node_id = 0;
char* csn = NULL;
uint64 csnll = 0;
bool meet_error = false;
bool enable_gtm_free = (strcmp(gtm_mode, "on") == 0);
/*
* Get the CSN value when commit(and not gtm_free, because the node will use the
* local csn for gtm free), send it to the other nodes.
*/
if (is_commit && !enable_gtm_free && gtm_option_num != 2) {
bool found = false;
if (txn->cn_xid != 0) {
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
/* always reference matched coordiantor */
if (strcmp(pgxc_clean_node_info[ii].node_name, txn->cn_nodename) == 0) {
node_id = ii;
found = true;
break;
}
}
}
/* First get the CSN value from primary CN */
if (found) {
csn = fetchCsnInfo(conn, txn->cn_xid, node_id, &meet_error);
csnll = convertCsnStr(csn);
Assert(csnll != 1);
}
/*
* If the primary CN is missing or meet error(failed to get a valid csn
* from CN), we need to search the other nodes.
*/
if (!COMMITSEQNO_IS_COMMITTED(csnll)) {
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
if (strcmp(pgxc_clean_node_info[ii].node_name, txn->cn_nodename) != 0) {
char* dnCsn = NULL;
uint64 dnCsnll = 0;
if (gtm_option_num == 0) { /* gtm mode, direct fetch csn from other node */
dnCsn = fetchCsnInfo(conn, txn->cn_xid, ii, &meet_error);
dnCsnll = convertCsnStr(dnCsn);
} else if (gtm_option_num == 1) { /* gtm lite mode, fetch csn from next node' xid */
int next_node_idx = txn->txn_gid_info[ii].next_node_idx;
GlobalTransactionId next_node_xid = txn->txn_gid_info[ii].next_node_xid;
if (next_node_xid == 0)
continue;
dnCsn = fetchCsnInfo(conn, next_node_xid, next_node_idx, &meet_error);
dnCsnll = convertCsnStr(dnCsn);
}
/*
* If we find a valid csn(not frozen) from other nodes,
* then break and use it.
*/
if (COMMITSEQNO_IS_COMMITTED(dnCsnll)) {
/* free old csn memory */
if (csn != NULL) {
free(csn);
}
csn = dnCsn;
csnll = dnCsnll;
break;
} else if (dnCsn != NULL) {
free(dnCsn);
}
/* every one has the same next_node_idx, just fetch once */
if (txn->new_ddl_version) {
break;
}
}
}
}
}
/*
* For commit gtm transaction, go on with gs_clean when the csn is comitted,
* or we just ignore this gs_clean for the current transaction.
*/
if ((is_commit && !enable_gtm_free && gtm_option_num != 2) && !COMMITSEQNO_IS_COMMITTED((uint64)csnll)) {
if (csn != NULL) {
free(csn);
}
do_commit_abort_failed = true;
return;
}
if (verbose_opt)
write_stderr("job%d: %s %s...\n", num, formatLogTime(), is_commit ? "committing" : "aborting");
/* node_name */
int use_lenth = 0;
bool send_query = false;
bool need_send = false;
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
if (txn->txn_stat[ii] == TXN_STATUS_PREPARED && ii != my_nodeidx) {
int left_space = MAXNODELISTLEN - 1 - use_lenth;
int node_name_len = strlen(pgxc_clean_node_info[ii].node_name);
int gid_len = (txn->new_version && txn->txn_gid_info[ii].my_gid != NULL) ?
strlen(txn->txn_gid_info[ii].my_gid) + 1 : 0;
int src_lenth = node_name_len + gid_len; /* append space of ',' */
need_send = true;
Assert(left_space >= 0);
if (left_space < src_lenth + 1) {
/* have no enough space */
send_query = true;
ii--;
} else if (use_lenth == 0) {
rc = strcpy_s(node_list + use_lenth, left_space, pgxc_clean_node_info[ii].node_name);
securec_check_c(rc, "\0", "\0");
if (txn->new_version) {
rc = strcpy_s(node_list + use_lenth + node_name_len, left_space, ",");
securec_check_c(rc, "\0", "\0");
rc = strcpy_s(node_list + use_lenth + node_name_len + 1, left_space,
txn->txn_gid_info[ii].my_gid);
securec_check_c(rc, "\0", "\0");
}
use_lenth += src_lenth;
} else {
rc = strcpy_s(node_list + use_lenth, left_space, ",");
securec_check_c(rc, "\0", "\0");
rc = strcpy_s(node_list + use_lenth + 1, left_space, pgxc_clean_node_info[ii].node_name);
securec_check_c(rc, "\0", "\0");
if (txn->new_version) {
rc = strcpy_s(node_list + use_lenth + 1 + node_name_len, left_space, ",");
securec_check_c(rc, "\0", "\0");
rc = strcpy_s(node_list + use_lenth + 1 + node_name_len + 1, left_space,
txn->txn_gid_info[ii].my_gid);
securec_check_c(rc, "\0", "\0");
}
use_lenth += src_lenth + 1;
}
}
if (send_query || (ii == pgxc_clean_node_count - 1 && need_send)) {
if (csn != NULL) {
if (txn->new_version) {
rc = sprintf_s(stmt, sizeof(stmt), NEW_GID_EXEC_STMT_FMT_CSN,
is_commit ? "COMMIT" : "ROLLBACK", csn, node_list);
} else {
rc = sprintf_s(stmt, sizeof(stmt), EXEC_STMT_FMT_CSN,
is_commit ? "COMMIT" : "ROLLBACK", txn->gid, csn, node_list);
}
} else {
if (txn->new_version) {
rc = sprintf_s(stmt, sizeof(stmt), NEW_GID_EXEC_STMT_FMT,
is_commit ? "COMMIT" : "ROLLBACK", node_list);
} else {
rc = sprintf_s(stmt, sizeof(stmt), EXEC_STMT_FMT,
is_commit ? "COMMIT" : "ROLLBACK", txn->gid, node_list);
}
}
securec_check_ss_c(rc, "\0", "\0");
res = PQexec(conn, stmt);
res_status = PQresultStatus(res);
if (verbose_opt) {
if (res_status == PGRES_COMMAND_OK || res_status == PGRES_TUPLES_OK)
write_stderr("job%d: %s succeeded (%s)\n", num, formatLogTime(), node_list);
else
write_stderr("job%d: %s send on (%s), failed message (%s)\n",
num,
formatLogTime(),
node_list,
PQresultErrorMessage(res));
} else {
if (res_status != PGRES_COMMAND_OK && res_status != PGRES_TUPLES_OK) {
write_stderr("job%d: %s send on (%s), failed message (%s)\n",
num,
formatLogTime(),
node_list,
PQresultErrorMessage(res));
}
}
PQclear(res);
send_query = false;
rc = memset_s(node_list, MAXNODELISTLEN, 0, MAXNODELISTLEN);
securec_check_c(rc, "\0", "\0");
use_lenth = 0;
need_send = false;
}
}
if (txn->txn_stat[my_nodeidx] == TXN_STATUS_PREPARED) {
/* Issue global statment */
char *finish_prepared_gid = (txn->new_version ? txn->txn_gid_info[my_nodeidx].my_gid : txn->gid);
if (csn != NULL) {
rc = sprintf_s(stmt, sizeof(stmt), GLOBAL_STMT_FMT_CSN,
is_commit ? "COMMIT" : "ROLLBACK", finish_prepared_gid, csn);
} else {
rc = sprintf_s(stmt, sizeof(stmt), GLOBAL_STMT_FMT,
is_commit ? "COMMIT" : "ROLLBACK", finish_prepared_gid);
}
securec_check_ss_c(rc, "\0", "\0");
res = PQexec(conn, stmt);
res_status = PQresultStatus(res);
if (verbose_opt) {
if (res_status == PGRES_COMMAND_OK || res_status == PGRES_TUPLES_OK)
write_stderr("job%d: %s succeeded (%s)\n", num, formatLogTime(), my_nodename);
else {
do_commit_abort_failed = true;
write_stderr(
"job%d: %s failed (%s: %s)\n", num, formatLogTime(), my_nodename, PQresultErrorMessage(res));
}
}
else if (res_status != PGRES_COMMAND_OK && res_status != PGRES_TUPLES_OK) {
do_commit_abort_failed = true;
write_stderr(
"job%d: %s Failed to recover TXN, localxid: %lu, gid: \"%s\", owner: \"%s\", node: \"%s\" (%s)\n",
num,
formatLogTime(),
txn->localxid,
txn->gid,
txn->owner,
my_nodename,
PQresultErrorMessage(res));
}
PQclear(res);
}
if (csn != NULL)
free(csn);
}
static int64 convertCsnStr(const char* csn)
{
int64 ret = 0;
if (csn != NULL)
ret = atoll(csn);
return ret;
}
static char* fetchCsnInfo(PGconn* conn, GlobalTransactionId gxid, int node_id, bool* meet_error)
{
Assert(gxid != 0);
PGresult* res = NULL;
char* res_s = NULL;
int rc;
char* csn = NULL;
char stmt[1024];
char* node_name = pgxc_clean_node_info[node_id].node_name;
static const char* STMT_FORM = "EXECUTE DIRECT ON (%s) 'SELECT "
"pgxc_get_csn(''%lu''::xid);'";
rc = sprintf_s(stmt, sizeof(stmt), STMT_FORM, node_name, gxid);
securec_check_ss_c(rc, "\0", "\0");
res = PQexec(conn, stmt);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not obtain transaction csn for node "
"%s, gxid %lu\n",
formatLogTime(),
node_name,
gxid);
PQclear(res);
*meet_error = true;
return csn;
}
if (PQgetisnull(res, 0, 0)) {
PQclear(res);
return csn;
}
res_s = PQgetvalue(res, 0, 0);
if (res_s != NULL) {
csn = pg_strdup(res_s);
}
PQclear(res);
return csn;
}
static PGconn* loginDatabase(char* host, int port, char* user, char* password, char* dbname, const char* progname,
char* encoding, const char* password_prompt)
{
bool new_pass = false;
PGconn* coord_conn = NULL;
char port_s[32];
#define PARAMS_ARRAY_SIZE 10
const char* keywords[PARAMS_ARRAY_SIZE];
const char* values[PARAMS_ARRAY_SIZE];
int rc;
bool need_clear_pass = false;
rc = sprintf_s(port_s, sizeof(port_s), "%d", port);
securec_check_ss_c(rc, "\0", "\0");
keywords[0] = "host";
values[0] = host;
keywords[1] = "port";
values[1] = port_s;
keywords[2] = "user";
values[2] = user;
keywords[3] = "password";
keywords[4] = "dbname";
values[4] = dbname;
keywords[5] = "fallback_application_name";
values[5] = progname;
keywords[6] = "client_encoding";
values[6] = encoding;
keywords[7] = "connect_timeout";
values[7] = connect_timeout;
keywords[8] = "options";
if (clean_temp_schema_only == false) {
/* this mode: set timeout = 180 seconds */
values[8] = "-c xc_maintenance_mode=on -c statement_timeout=180000 -c max_query_retry_times=0";
} else {
/* this mode: remove timeout */
values[8] = "-c xc_maintenance_mode=on";
}
keywords[9] = NULL;
values[9] = NULL;
/* Loop until we have a password if requested by backend */
do {
values[3] = password;
new_pass = false;
coord_conn = PQconnectdbParams(keywords, values, true);
if (PQstatus(coord_conn) == CONNECTION_BAD && PQconnectionNeedsPassword(coord_conn) && password == NULL &&
try_password_opt != TRI_NO) {
PQfinish(coord_conn);
password = simple_prompt(password_prompt, 100, false);
new_pass = true;
need_clear_pass = true;
}
} while (new_pass);
/* if the password is get from simple_prompt in the above instead of global password, it should be free avoid
* memmory leak */
if (need_clear_pass && password != NULL) {
rc = memset_s(password, strlen(password), 0, strlen(password));
securec_check_c(rc, "\0", "\0");
free(password);
password = NULL;
values[3] = NULL;
}
if ((coord_conn != NULL) && PQstatus(coord_conn) == CONNECTION_BAD) {
PQfinish(coord_conn);
return NULL;
}
return (coord_conn);
}
static TXN_STATUS getTxnStatus(PGconn* conn, GlobalTransactionId gxid, int node_idx, bool primary_cn)
{
char stmt[1024];
char* res_s = NULL;
int rc;
PGresult* res1 = NULL;
PGresult* res2 = NULL;
char* node_name = pgxc_clean_node_info[node_idx].node_name;
unsigned int count = 0;
if (primary_cn) {
/* Check if the prepared transaction is running on this node */
static const char* STMT_RUNNING = "EXECUTE DIRECT ON (%s) 'select "
"count(1) from pg_running_xacts where prepare_xid::text = %lu;' ";
rc = sprintf_s(stmt, sizeof(stmt), STMT_RUNNING, node_name, gxid);
securec_check_ss_c(rc, "\0", "\0");
res1 = PQexec(conn, stmt);
if (res1 == NULL || (PQresultStatus(res1) != PGRES_TUPLES_OK)) {
write_stderr("%s Could not get the status of prepared transaction %lu for node %s.(%s)\n",
formatLogTime(),
gxid,
node_name,
res1 != NULL ? PQresultErrorMessage(res1) : "");
PQclear(res1);
return TXN_STATUS_UNCONNECT;
}
if (PQntuples(res1) > 0) {
count = atoi(PQgetvalue(res1, 0, 0));
} else {
count = 1; /* treat it as running if doubt */
}
PQclear(res1);
/*
* Prepared transaction that is running, need not clear it, coordinator will finish it then,
* otherwise, we treat it as remained/pending, we should clear it.
*/
if (count > 0)
return TXN_STATUS_RUNNING;
}
/* Not running, but remained prepared transaction, check if committed */
static const char* STMT_FORM = "EXECUTE DIRECT ON (%s) 'SELECT "
"pgxc_is_committed(''%lu''::xid);'";
rc = sprintf_s(stmt, sizeof(stmt), STMT_FORM, node_name, gxid);
securec_check_ss_c(rc, "\0", "\0");
res2 = PQexec(conn, stmt);
if (res2 == NULL || PQresultStatus(res2) != PGRES_TUPLES_OK) {
write_stderr(
"%s Could not obtain transaction status for node %s, gxid %lu\n", formatLogTime(), node_name, gxid);
PQclear(res2);
return TXN_STATUS_UNCONNECT;
}
if (PQgetisnull(res2, 0, 0)) {
PQclear(res2);
return TXN_STATUS_UNKNOWN;
}
res_s = PQgetvalue(res2, 0, 0);
if (strcmp(res_s, "t") == 0) {
PQclear(res2);
return TXN_STATUS_COMMITTED;
} else if (strcmp(res_s, "f") == 0) {
PQclear(res2);
return TXN_STATUS_ABORTED;
} else {
/* we should not fall down here in normal case */
write_stderr("%s Obtain unknown transaction status: %s for node %s, gxid %lu\n",
formatLogTime(),
res_s,
node_name,
gxid);
PQclear(res2);
/* something wrong happend, we don't use gs_clean to clear it */
return TXN_STATUS_RUNNING;
}
}
/* Get txn status from primary cn node, called by getTxnInfoOnOtherNodes() */
static bool getTxnStatOnPrimaryCN(PGconn* conn, txn_info* txn, int cnid, bool enable_gtm_free)
{
TXN_STATUS tmp;
tmp = getTxnStatus(conn, txn->cn_xid, cnid, true);
if (txn->txn_stat[cnid] == TXN_STATUS_INITIAL)
txn->txn_stat[cnid] = tmp;
if (txn->txn_stat[cnid] == TXN_STATUS_PREPARED) {
/* if more clear status */
if (tmp != TXN_STATUS_UNKNOWN) {
txn->txn_stat[cnid] = tmp;
} else {
txn->is_exec_cn_prepared = true;
}
}
if (tmp == TXN_STATUS_UNCONNECT || tmp == TXN_STATUS_UNKNOWN) {
return false;
}
return true;
}
/* Get txn status from other nodes, called by getTxnInfoOnOtherNodes() */
static void getTxnStatOnOtherNodes(PGconn* conn, txn_info* txn, int cnid, bool enable_gtm_free)
{
TXN_STATUS tmp;
int ii;
/* gtm branch */
if (!enable_gtm_free && (gtm_option_num == 0)) {
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
tmp = getTxnStatus(conn, txn->localxid, ii, (ii == cnid));
if (tmp == TXN_STATUS_COMMITTED || tmp == TXN_STATUS_ABORTED || tmp == TXN_STATUS_RUNNING) {
txn->txn_stat[ii] = tmp;
break;
}
if (tmp == TXN_STATUS_UNCONNECT) {
txn->txn_stat[ii] = tmp;
}
}
} else if (txn->new_version) {
/* new_gid_version, gtm lite or gtm free */
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
/* every one has next member's xid, try to get txn from next member */
int next_node_idx = txn->txn_gid_info[ii].next_node_idx;
GlobalTransactionId next_node_xid = txn->txn_gid_info[ii].next_node_xid;
if (next_node_xid == 0) {
continue;
}
tmp = getTxnStatus(conn, next_node_xid, next_node_idx, (next_node_idx == cnid));
if (tmp == TXN_STATUS_COMMITTED || tmp == TXN_STATUS_ABORTED || tmp == TXN_STATUS_RUNNING) {
txn->txn_stat[next_node_idx] = tmp;
break;
}
if (tmp == TXN_STATUS_UNCONNECT) {
txn->txn_stat[next_node_idx] = tmp;
}
/* every one has the same next_node_idx, just fetch once */
if (txn->new_ddl_version) {
break;
}
}
}
}
static void getTxnInfoOnOtherNodes(PGconn* conn, txn_info* txn)
{
int ii;
int cnid = -1;
bool found = false;
bool enable_gtm_free = (strcmp(gtm_mode, "on") == 0);
bool is_cn_deleted = true; /* cn deleted, cn unconnected have different meaning */
if (txn->cn_xid != 0) {
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
/* always reference matched coordiantor */
if (strcmp(pgxc_clean_node_info[ii].node_name, txn->cn_nodename) == 0) {
cnid = ii;
found = true;
is_cn_deleted = false;
break;
}
}
/* get txn status from primary cn node*/
if (found) {
found = getTxnStatOnPrimaryCN(conn, txn, cnid, enable_gtm_free);
}
}
/* try get txn status from other nodes*/
/*
* if enable_gtm_free is true, we cannot do anything to xact, so set status to unconnect,
* otherwise try get txn status from other nodes.
* only do below logic if exec cn deleted, if cn is unconneted, we do nothing.
*/
if (is_cn_deleted) {
getTxnStatOnOtherNodes(conn, txn, cnid, enable_gtm_free);
write_stderr(
"%s Could not get the status of prepared transaction %s from node %s,"
" current gtm_option: %s, option_num: %d.\n",
formatLogTime(),
txn->gid,
txn->cn_nodename,
transfor_gtm_optoin(gtm_option, gtm_mode),
gtm_option_num);
}
}
static void getTxnInfoOnOtherNodesForDatabase(CleanWorkerInfo* wkinfo, database_info* database)
{
txn_info* cur_txn = NULL;
PGconn* conn = wkinfo->conn;
int work_index = wkinfo->work_job;
for (cur_txn = database->all_head_txn_info[work_index]; cur_txn != NULL; cur_txn = cur_txn->next) {
getTxnInfoOnOtherNodes(conn, cur_txn);
}
}
static void getTxnInfoOnOtherNodesAll(CleanWorkerInfo* wkinfo)
{
database_info* cur_database = NULL;
for (cur_database = head_database_info; cur_database != NULL; cur_database = cur_database->next) {
getTxnInfoOnOtherNodesForDatabase(wkinfo, cur_database);
}
}
static void form_get_prepared_list_sql(char* stmt, int len, bool fetch_all, int idx)
{
int rc;
/* SQL Statement */
static const char* SQL_TEMP1_FETCHALL = "SELECT TRANSACTION, GID, OWNER, DATABASE, NODE_NAME "
"FROM GET_GLOBAL_PREPARED_XACTS;";
static const char* SQL_TEMP2_FETCHALL = "SELECT TRANSACTION, GID, OWNER, DATABASE, NODE_NAME "
"FROM GET_GLOBAL_PREPARED_XACTS WHERE GID LIKE 'T%%_%s';";
static const char* SQL_TEMP1 = "EXECUTE DIRECT ON (%s) 'SELECT TRANSACTION, GID, OWNER,"
"DATABASE FROM PG_PREPARED_XACTS;'";
static const char* SQL_TEMP2 = "EXECUTE DIRECT ON (%s) 'SELECT TRANSACTION, GID, OWNER,"
"DATABASE FROM PG_PREPARED_XACTS WHERE GID LIKE ''T%%_%s'';'";
if (fetch_all == true) {
if (rollback_cn_name != NULL) {
rc = sprintf_s(stmt, len, SQL_TEMP2_FETCHALL, rollback_cn_name);
} else {
rc = sprintf_s(stmt, len, SQL_TEMP1_FETCHALL);
}
} else {
if (rollback_cn_name != NULL) {
rc = sprintf_s(stmt, len, SQL_TEMP2, pgxc_clean_node_info[idx].node_name, rollback_cn_name);
} else {
rc = sprintf_s(stmt, len, SQL_TEMP1, pgxc_clean_node_info[idx].node_name);
}
}
securec_check_ss_c(rc, "\0", "\0");
}
/*
* first we try to fetch all infomations once time using PGXC_GET_PREPARED_XACTS,
* If fails, we fetch result one by one.
*/
static bool getPreparedTxnListOfNode(PGconn* conn, bool fetch_all, int idx = 0)
{
#define MAX_STMT_LEN 1024
char stmt[MAX_STMT_LEN];
int prep_txn_count;
int ii;
PGresult* res = NULL;
ExecStatusType pq_status;
/* form query of getting prepared txn list */
form_get_prepared_list_sql(stmt, MAX_STMT_LEN, fetch_all, idx);
res = PQexec(conn, stmt);
/* ignore this node */
if (res == NULL || (pq_status = PQresultStatus(res)) != PGRES_TUPLES_OK) {
if (fetch_all == true) {
write_stderr("%s Could not obtain prepared transaction list once time.(%s)\n",
formatLogTime(),
res != NULL ? PQresultErrorMessage(res) : "");
} else {
write_stderr("%s Could not obtain prepared transaction list for node %s.(%s)\n",
formatLogTime(),
pgxc_clean_node_info[idx].node_name,
res != NULL ? PQresultErrorMessage(res) : "");
}
PQclear(res);
return false;
}
prep_txn_count = PQntuples(res);
for (ii = 0; ii < prep_txn_count; ii++) {
GlobalTransactionId localxid;
char* gid = NULL;
char* owner = NULL;
char* database_name = NULL;
char* node_name = NULL;
localxid = atoxid(PQgetvalue(res, ii, 0));
gid = pg_strdup(PQgetvalue(res, ii, 1));
owner = pg_strdup(PQgetvalue(res, ii, 2));
database_name = pg_strdup(PQgetvalue(res, ii, 3));
node_name = fetch_all ? (pg_strdup(PQgetvalue(res, ii, 4))) : pgxc_clean_node_info[idx].node_name;
add_txn_info(database_name, node_name, localxid, gid, owner, TXN_STATUS_PREPARED);
/* free resource, these variables have been checked for not NULL */
free(gid);
free(owner);
free(database_name);
if (fetch_all)
free(node_name);
}
PQclear(res);
return true;
}
static void getPreparedTxnList(PGconn* conn)
{
if (getPreparedTxnListOfNode(conn, true)) {
write_stderr("%s get prepared transaction on all nodes\n", formatLogTime());
} else {
/* something wrong in some node, fetch information in nodes one by one */
for (int ii = 0; ii < pgxc_clean_node_count; ii++) {
if (getPreparedTxnListOfNode(conn, false, ii))
write_stderr(
"%s get prepared transaction on node(%s)\n", formatLogTime(), pgxc_clean_node_info[ii].node_name);
}
}
}
static void getDatabaseList(PGconn* conn)
{
int database_count;
int ii;
PGresult* res = NULL;
char* dbname = NULL;
/* SQL Statement */
static const char* STMT_GET_DATABASE_LIST = "SELECT DATNAME FROM PG_DATABASE;";
/*
* Get database list
*/
res = PQexec(conn, STMT_GET_DATABASE_LIST);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not obtain database list.\n", formatLogTime());
PQclear(res);
exit(1);
}
database_count = PQntuples(res);
for (ii = 0; ii < database_count; ii++) {
dbname = PQgetvalue(res, ii, 0);
if (strcmp(dbname, "template0") == 0 || strcmp(dbname, "template1") == 0) {
/* Skip template0 and template1 database */
continue;
}
add_database_info(dbname);
}
PQclear(res);
}
static void add_temp_schema_info(char* temp_schema_name)
{
tempschema_info* rv = NULL;
rv = (tempschema_info*)malloc(sizeof(tempschema_info));
checkAllocMem(rv);
rv->next = NULL;
rv->tempschema_name = strdup(temp_schema_name);
checkAllocMem(rv->tempschema_name);
if (verbose_opt)
write_stderr("%s add temp schema: %s\n", formatLogTime(), temp_schema_name);
if (temp_schema_info == NULL)
temp_schema_info = rv;
else {
rv->next = temp_schema_info->next;
temp_schema_info->next = rv;
}
}
static void add_active_backend_info(int64 sessionID, uint32 tempID, uint32 timeLineID)
{
activebackend_info* rv = NULL;
rv = (activebackend_info*)malloc(sizeof(activebackend_info));
checkAllocMem(rv);
rv->next = NULL;
rv->sessionID = sessionID;
rv->tempID = tempID;
rv->timeLineID = timeLineID;
if (verbose_opt)
write_stderr("%s add backendID: %lld\n", formatLogTime(), (long long int)sessionID);
if (active_backend_info == NULL)
active_backend_info = rv;
else {
rv->next = active_backend_info->next;
active_backend_info->next = rv;
}
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* @Description: get temp schema list of a random DN.
* @param[IN] conn: node connection handle
* @return: int - the random datanode number.
*/
static int getTempSchemaListOnDN(PGconn* conn)
{
const char* STMT_GET_RANDOM = "SELECT RANDOM();";
PGresult* res = NULL;
double random_value;
int random_datanode_no;
for (;;) {
res = PQexec(conn, STMT_GET_RANDOM);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not execute query.\n", formatLogTime());
PQclear(res);
exit(1);
}
random_value = atof(PQgetvalue(res, 0, 0));
random_datanode_no = (int)(pgxc_clean_node_count * random_value);
PQclear(res);
if (pgxc_clean_node_info[random_datanode_no].type == NODE_TYPE_DATANODE) {
if (verbose_opt)
write_stderr("%s starting get temp schema on %s\n",
formatLogTime(),
pgxc_clean_node_info[random_datanode_no].node_name);
getTempSchemaOnOneDN(conn, random_datanode_no);
break;
}
}
return random_datanode_no;
}
/*
* @Description: get temp schemas' name on a random DN. This is to avoid node
* replacement/xc_maintenance_mode causing CN/DN temp schema inconsistency. When
* inconsistency, we cann't get the temp schema name on CN, so we
* must get it from DN directly.
*
* @param[IN] conn: node connection handle
* @return: void
*/
static void getTempSchemaOnOneDN(PGconn* conn, int no)
{
char STMT_GET_DN_TEMP_SCHEMA[NAMEDATALEN * 3 + 128] = {0};
PGresult* res = NULL;
int i;
int temp_schema_count;
char* nspname = NULL;
int rc;
/*
* Here we handle two scenarios:
* 1. The coordinator is replaced because of old coodinator broken, so we can only find temp schemas
on datanode;
2. The DN is doing build, and the build uses temp table, then the DN is killed, so the temp schmea
will be exist on this DN only.
*
* Note: we get all temp schemas like 'pg_temp_%' (include temp schemas:
* a. created on all alive CN;
* b. created on current DN
* c. created on dropped CN
* ).
*/
rc = snprintf_s(STMT_GET_DN_TEMP_SCHEMA,
sizeof(STMT_GET_DN_TEMP_SCHEMA),
sizeof(STMT_GET_DN_TEMP_SCHEMA) - 1,
"EXECUTE DIRECT on (%s) 'SELECT * FROM PG_NAMESPACE WHERE NSPNAME LIKE ''pg_temp_%%''';",
pgxc_clean_node_info[no].node_name);
securec_check_ss_c(rc, "\0", "\0");
res = PQexec(conn, STMT_GET_DN_TEMP_SCHEMA);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not execute query.\n", formatLogTime());
PQclear(res);
exit(1);
}
temp_schema_count = PQntuples(res);
for (i = 0; i < temp_schema_count; i++) {
nspname = PQgetvalue(res, i, 0);
add_temp_schema_info(nspname);
}
PQclear(res);
}
#endif
static void getTempSchemaListOnCN(PGconn* conn)
{
int temp_schema_count;
int i;
PGresult* res = NULL;
char* nspname = NULL;
int rc;
char STMT_GET_TEMP_SCHEMA_LIST[NAMEDATALEN + 128] = {0};
#ifdef ENABLE_MULTIPLE_NODES
rc = snprintf_s(STMT_GET_TEMP_SCHEMA_LIST,
sizeof(STMT_GET_TEMP_SCHEMA_LIST),
sizeof(STMT_GET_TEMP_SCHEMA_LIST) - 1,
"SELECT NSPNAME FROM PG_NAMESPACE WHERE NSPNAME LIKE 'pg_temp_%s_%%'",
my_nodename);
#else
rc = snprintf_s(STMT_GET_TEMP_SCHEMA_LIST,
sizeof(STMT_GET_TEMP_SCHEMA_LIST),
sizeof(STMT_GET_TEMP_SCHEMA_LIST) - 1,
"SELECT NSPNAME FROM PG_NAMESPACE WHERE NSPNAME LIKE 'pg_temp_%%'");
#endif
securec_check_ss_c(rc, "\0", "\0");
if (verbose_opt)
write_stderr("%s start getting temp schema on %s\n", formatLogTime(), my_nodename);
/*
* Get database list
*/
res = PQexec(conn, STMT_GET_TEMP_SCHEMA_LIST);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not obtain temp schema list.\n", formatLogTime());
PQclear(res);
exit(1);
}
temp_schema_count = PQntuples(res);
for (i = 0; i < temp_schema_count; i++) {
nspname = PQgetvalue(res, i, 0);
if (strchr(&nspname[7], '_') == NULL) {
write_stderr("%s Error when parse schema name.\n", formatLogTime());
}
if (strncasecmp(my_nodename, &nspname[7], strchr(&nspname[7], '_') - &nspname[7]) == 0)
add_temp_schema_info(nspname);
}
PQclear(res);
}
/*
* @Description: get active backend list, to avoid deleting active temp namespace.
* There are two scenarios:
* 1. The temp schema name is gotten from CN, then we should get active threadid
* list directly by select pg_stat_activity from CN.
* 2. The temp schema name is gotten from DN, then the temp schema may be
* created by CN, or datanode itself(for example, in maintenance mode), then
* we should first do step1, and then get active threadid list by
* 'executing direct on datanode'.
*
* @param[IN] conn: node connection handle
* @param[IN] datanode_no: datanode number. -1 if temp schema is gotten from CN,
* actual DN no if temp schema is gotten from DN.
* @return: void
*/
static void getActiveBackendListOnCN(PGconn* conn, int datanode_no)
{
#define MAX_SQL_LEN (2 * NAMEDATALEN + 128)
int rc;
/* SQL Statement */
char STMT_ACTIVE_BACKEND_LIST[MAX_SQL_LEN] = {0};
if (datanode_no != -1) {
rc = sprintf_s(STMT_ACTIVE_BACKEND_LIST,
MAX_SQL_LEN,
"EXECUTE DIRECT ON (%s) 'SELECT SESSIONID, TEMPID, TIMELINEID FROM PG_DATABASE D, "
"PG_STAT_GET_ACTIVITY_FOR_TEMPTABLE() AS S WHERE S.DATID = D.OID AND D.DATNAME = ''%s'''",
pgxc_clean_node_info[datanode_no].node_name,
PQdb(conn));
securec_check_ss_c(rc, "\0", "\0");
getActiveBackendList(conn, STMT_ACTIVE_BACKEND_LIST);
}
rc = sprintf_s(STMT_ACTIVE_BACKEND_LIST,
MAX_SQL_LEN,
"SELECT SESSIONID, TEMPID, TIMELINEID FROM PG_DATABASE D, PG_STAT_GET_ACTIVITY_FOR_TEMPTABLE() AS S WHERE "
"S.DATID = D.OID AND D.DATNAME = '%s'",
PQdb(conn));
securec_check_ss_c(rc, "\0", "\0");
getActiveBackendList(conn, STMT_ACTIVE_BACKEND_LIST);
}
/*
* @Description: execute the given query to get active backend id list from CN
* @param[IN] conn: node connection handle
* @param[IN] query: get cn-created temp namespace's backend id or dn-created temp namespace's backend id,
using different sql query.
* @return: void
*/
static void getActiveBackendList(PGconn* conn, const char* query)
{
int active_backend_count;
int i;
PGresult* res = NULL;
int64 sessionID;
uint32 tempID;
uint32 timeLineID;
/*
* Get database list
*/
res = PQexec(conn, query);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not obtain active backend thread id list.\n", formatLogTime());
PQclear(res);
exit(1);
}
active_backend_count = PQntuples(res);
for (i = 0; i < active_backend_count; i++) {
char* result = PQgetvalue(res, i, 0);
sessionID = atoll(result);
result = PQgetvalue(res, i, 1);
tempID = atoi(result);
result = PQgetvalue(res, i, 2);
timeLineID = atoi(result);
(void)add_active_backend_info(sessionID, tempID, timeLineID);
}
PQclear(res);
}
static void checkAllocMem(void* p)
{
if (p == NULL) {
write_stderr("%s No more memory, FILE:%s, LINE:%d.\n", formatLogTime(), __FILE__, __LINE__);
exit(1);
}
}
#ifdef ENABLE_MULTIPLE_NODES
static void getNodeList(PGconn* conn)
{
int ii;
PGresult* res = NULL;
/* SQL Statement */
static const char* STMT_GET_NODE_INFO = "SELECT NODE_NAME, NODE_TYPE, NODE_PORT, NODE_HOST FROM PGXC_NODE WHERE "
"NODE_TYPE IN ('C','D') AND nodeis_active = true;";
res = PQexec(conn, STMT_GET_NODE_INFO);
if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) {
write_stderr("%s Could not obtain node list.\n", formatLogTime());
goto error_exit;
}
pgxc_clean_node_count = PQntuples(res);
pgxc_clean_node_info = (node_info*)calloc(pgxc_clean_node_count, sizeof(node_info));
if (pgxc_clean_node_info == NULL) {
write_stderr("%s No more memory.\n", formatLogTime());
goto error_exit;
}
for (ii = 0; ii < pgxc_clean_node_count; ii++) {
char* node_name = NULL;
char* node_type_c = NULL;
NODE_TYPE node_type;
int port;
char* host = NULL;
node_name = pg_strdup(PQgetvalue(res, ii, 0));
node_type_c = pg_strdup(PQgetvalue(res, ii, 1));
switch (node_type_c[0]) {
case 'C':
/* pgxc_clean has to connect to the Coordinator */
node_type = NODE_TYPE_COORD;
if (strcmp(node_name, my_nodename) == 0) {
my_nodeidx = ii;
}
break;
case 'D':
node_type = NODE_TYPE_DATANODE;
break;
default:
write_stderr("%s Invalid catalog data (node_type), node_name: %s, node_type: %s\n",
formatLogTime(),
node_name,
node_type_c);
/* free memory before exit */
if (node_name != NULL)
free(node_name);
if (node_type_c != NULL)
free(node_type_c);
goto error_exit;
}
port = atoi(PQgetvalue(res, ii, 2));
host = pg_strdup(PQgetvalue(res, ii, 3));
bool set_node_info_fail = (set_node_info(node_name, port, host, node_type, ii) == -1);
/* free memory before check result */
if (node_name != NULL)
free(node_name);
if (node_type_c != NULL)
free(node_type_c);
if (host != NULL)
free(host);
if (set_node_info_fail) {
goto error_exit;
}
}
/* Check if local Coordinator has been found */
if (my_nodeidx == -1) {
write_stderr("%s Failed to identify the coordinator which %s is connecting to. ", formatLogTime(), progname);
write_stderr("%s Connecting to a wrong node.\n", formatLogTime());
goto error_exit;
}
PQclear(res);
return;
error_exit:
PQclear(res);
exit(1);
}
#endif
static char* transfor_gtm_optoin(const char* option, const char* gtm_free_mode)
{
if (strcmp(option, "0") == 0 && strcmp(gtm_free_mode, "off") == 0) {
return "GTM";
} else if (strcmp(option, "1") == 0 && strcmp(gtm_free_mode, "off") == 0) {
return "GTM_LITE";
} else if (strcmp(option, "2") == 0 || strcmp(gtm_free_mode, "on") == 0) {
return "GTM_FREE";
} else {
return "UnKNown";
}
}
static void showVersion(void)
{
puts("gs_clean " DEF_GS_VERSION);
}
static void add_to_database_list(char* dbname)
{
if (head_database_names == NULL) {
head_database_names = last_database_names = (database_names*)malloc(sizeof(database_names));
if (head_database_names == NULL) {
write_stderr("%s No more memory, FILE:%s, LINE:%d.\n", formatLogTime(), __FILE__, __LINE__);
exit(1);
}
} else {
last_database_names->next = (database_names*)malloc(sizeof(database_names));
if (last_database_names->next == NULL) {
write_stderr("%s No more memory, FILE:%s, LINE:%d.\n", formatLogTime(), __FILE__, __LINE__);
exit(1);
}
last_database_names = last_database_names->next;
}
last_database_names->next = NULL;
last_database_names->database_name = dbname;
}
static void parse_pgxc_clean_options(int argc, char* argv[])
{
static struct option long_options[] = {{"all", no_argument, NULL, 'a'},
{"commit", no_argument, NULL, 'c'},
{"dbname", required_argument, NULL, 'd'},
{"host", required_argument, NULL, 'h'},
{"node", required_argument, NULL, 'n'},
{"no-clean", no_argument, NULL, 'N'},
{"port", required_argument, NULL, 'p'},
{"quiet", no_argument, NULL, 'q'},
{"rollback", no_argument, NULL, 'r'},
{"rollback-cnname", required_argument, NULL, 'R'},
{"timeout", required_argument, NULL, 't'},
{"username", required_argument, NULL, 'U'},
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
{"no-password", no_argument, NULL, 'w'},
{"password", required_argument, NULL, 'W'},
{"help", no_argument, NULL, '?'},
{"status", no_argument, NULL, 's'},
{"exclusive", no_argument, NULL, 'e'},
{"cleaning", no_argument, NULL, 'C'},
{"Exclusive", no_argument, NULL, 'E'},
{"jobs", required_argument, NULL, 'j'},
{NULL, 0, NULL, 0}};
int optindex;
extern char* optarg;
extern int optind;
int c;
errno_t rc = EOK;
progname = get_progname(argv[0]); /* Should be more fancy */
while ((c = getopt_long(argc, argv, "acd:h:n:Np:qrR:t:U:vVwW:seCEj:?", long_options, &optindex)) != -1) {
switch (c) {
case 'a':
clean_all_databases = true;
break;
case 'c':
commit_all_prepared = true;
break;
case 'd':
check_env_name_c(optarg);
add_to_database_list(optarg);
break;
case 'h':
check_env_value_c(optarg);
coordinator_host = optarg;
break;
case 'n':
check_env_value_c(optarg);
target_node = optarg;
break;
case 'N':
no_clean_opt = true;
break;
case 'p':
check_env_value_c(optarg);
if (optarg != NULL) {
coordinator_port = strtolSafe(optarg, 10);
}
break;
case 'q':
verbose_opt = false;
break;
case 'r':
rollback_all_prepared = true;
break;
case 'R':
check_env_value_c(optarg);
rollback_cn_name = optarg;
break;
case 't':
check_env_value_c(optarg);
connect_timeout = optarg;
break;
case 'U':
check_env_name_c(optarg);
username = optarg;
break;
case 'V':
version_opt = 0;
break;
case 'v':
verbose_opt = true;
break;
case 'w':
try_password_opt = TRI_NO;
break;
case 'W':
try_password_opt = TRI_YES;
if (optarg != NULL) {
password = pg_strdup(optarg);
rc = memset_s(optarg, strlen(optarg), 0, strlen(optarg));
securec_check_c(rc, "\0", "\0");
} else {
password = NULL;
}
break;
case 's':
status_opt = true;
break;
case 'e':
clean_temp_schema_only = true;
break;
case 'C':
clean_plan_table_only = true;
break;
case 'E':
clean_plan_table_only = false;
clean_temp_schema_only = false;
no_clean_temp_schema_tbspc = true;
break;
case 'j':
check_env_value_c(optarg);
g_gs_clean_worker_num = atoi(optarg);
if (g_gs_clean_worker_num < MIN_GS_CLEAN_WORK_NUM || g_gs_clean_worker_num > MAX_GS_CLEAN_WORK_NUM) {
write_stderr("ERROR: parallel worker number out of range(%d ~ %d).\n",
MIN_GS_CLEAN_WORK_NUM,
MAX_GS_CLEAN_WORK_NUM);
exit(1);
}
break;
case '?':
if (strcmp(argv[optind - 1], "-?") == 0 || strcmp(argv[optind - 1], "--help") == 0) {
usage();
exit(0);
} else {
write_stderr("%s Try \"%s --help\" for more information.\n", formatLogTime(), progname);
exit(1);
}
break;
default:
write_stderr("%s Try \"%s --help\" for more information.\n", formatLogTime(), progname);
exit(1);
break;
}
}
while (argc - optind >= 1) {
if (head_database_names == NULL) {
if (strcmp(argv[optind], "template0") == 0) {
write_stderr("%s %s: You should not clean template0 database.\n", formatLogTime(), progname);
exit(1);
}
add_to_database_list(argv[optind]);
}
if (username == NULL) {
username = argv[optind];
} else {
write_stderr("%s %s: warning: extra command-line argument \"%s\" ignored\n",
formatLogTime(),
progname,
argv[optind]);
}
optind++;
}
if (!clean_all_databases && head_database_names == NULL) {
write_stderr("%s Please specify at least one database or -a for all\n", formatLogTime());
exit(1);
}
if (commit_all_prepared && rollback_all_prepared) {
write_stderr(" Parameter -c and -r cannot be used in one time,\n"
" because we cannot both commit and rollback one transaction\n");
exit(1);
}
}
static bool setMaintenanceMode(PGconn* conn, bool on)
{
PGresult* res = NULL;
ExecStatusType res_status;
res = on ? PQexec(conn, "SET xc_maintenance_mode = on;") : PQexec(conn, "SET xc_maintenance_mode = off;");
res_status = PQresultStatus(res);
if (res_status == PGRES_COMMAND_OK || res_status == PGRES_TUPLES_OK) {
PQclear(res);
return true;
} else
write_stderr("%s Failed to set xc_maintenance_mode. (%s)\n", formatLogTime(), PQresultErrorMessage(res));
PQclear(res);
return false;
}
static bool setEnableParallelDDL(PGconn* conn, bool on)
{
PGresult* res = NULL;
ExecStatusType res_status;
res = on ? PQexec(conn, "SET enable_parallel_ddl = on;") : PQexec(conn, "SET enable_parallel_ddl = off;");
res_status = PQresultStatus(res);
if (res_status == PGRES_COMMAND_OK || res_status == PGRES_TUPLES_OK) {
PQclear(res);
return true;
} else
write_stderr("%s Failed to set enable_parallel_ddl. (%s)\n", formatLogTime(), PQresultErrorMessage(res));
PQclear(res);
return false;
}
static char* formatLogTime()
{
time_t now = time(NULL);
struct tm* nowtm = localtime(&now);
if (nowtm == NULL) {
return "invalid local time";
}
strftime(m_formatted_log_time, FORMATTED_TS_LEN, "%Y-%m-%d %H:%M:%S ", nowtm);
m_formatted_log_time[FORMATTED_TS_LEN - 1] = '\0';
return m_formatted_log_time;
}
static void usage(void)
{
char* env = NULL;
printf("gs_clean cleans up outstanding 2PCs after failed node is recovered.\n"
"Usage:\n"
"gs_clean [OPTION ...] [DBNAME [USERNAME]]\n\n"
"Options:\n");
printf(" -a, --all cleanup all the databases available.\n");
printf(" -c, --commit commit all prepared transaction.\n");
printf(" -d, --dbname=DBNAME database name to clean up.\n");
env = getenv("PGHOST");
check_env_value_c(env);
printf(" -h, --host=HOSTNAME target coordinator host address, (default: \"%s\")\n",
env != NULL ? env : "local socket");
printf(" -n, --node=NODENAME only recover prepared transaction on this node\n");
printf(" -N, --no-clean only collect 2PC information. Do not recover them\n");
env = getenv("PGPORT");
check_env_value_c(env);
printf(" -p, --port=PORT port number of the coordinator (default: \"%s\")\n",
env != NULL ? env : DEF_PGPORT_STR);
printf(" -q, --quiet quiet mode. do not print anything but error information.\n");
printf(" -r, --rollback rollback all prepared transaction.\n");
printf(" -R, --rollback-cnname rollback all prepared transaction when CN is disconnected.\n");
printf(" -s, --status prints out 2PC status\n");
printf(" -t, --timeout=SECS seconds to wait when attempting connection, 0 disables (default: %s)\n",
DEFAULT_CONNECT_TIMEOUT);
printf(" -U, --username=USERNAME database user name.\n");
printf(" -v, --verbose print recovery information.\n");
printf(" -V, --version prints out the version.\n");
printf(" -w, --no-password never prompt for the password.\n");
printf(" -W, --password=PASSWORD prompt for the password.\n");
printf(" -e, --exclusive only clean temp table and remove clean's timeout\n");
printf(" -C, --clean only clean plan_table data and clean's timeout\n");
printf(" -E, --Exclusive only clean 2pc transaction. \n");
printf(" -j, --jobs parallel number of jobs to do the 2PC clean (default: %d, range: %d ~ %d). \n",
DEFAULT_GS_CLEAN_WORK_NUM,
MIN_GS_CLEAN_WORK_NUM,
MAX_GS_CLEAN_WORK_NUM);
printf(" -?, --help print this message.\n");
}