pgpool2.git / summary / log / commit / refs

commit    35037bcd837472f04b92008362db7f3cefa88bec
Author:   Muhammad Usama <m.usama@HighGo.ca>
Commit:   Muhammad Usama <m.usama@gmail.com>
Date:     Fri Jun 26 19:08:24 2020 +0000

    Adding log collector
    
    Molding the syslogger of PostgreSQL to work with
    Pgpool-II so that we can have log rotation
    
    Documentation updates will follow soon.


configure | 85 ++ src/Makefile.am | 1 + src/Makefile.in | 30 +- src/config/pool_config_variables.c | 68 ++ src/include/main/pgpool_logger.h | 96 ++ src/include/pool.h | 2 + src/include/pool_config.h | 9 + src/include/utils/timestamp.h | 214 ++++ src/libs/pcp/Makefile.in | 2 +- src/main/main.c | 2 + src/main/pgpool_logger.c | 1176 ++++++++++++++++++++ src/main/pgpool_main.c | 216 ++-- src/main/pool_globals.c | 1 + src/pcp_con/pcp_child.c | 1 + src/sample/pgpool.conf.sample-logical | 34 +- src/sample/pgpool.conf.sample-raw | 42 +- src/sample/pgpool.conf.sample-replication | 34 +- src/sample/pgpool.conf.sample-slony | 44 +- src/sample/pgpool.conf.sample-snapshot | 44 +- src/sample/pgpool.conf.sample-stream | 33 +- .../tests/010.rewrite_timestamp/timestamp/Makefile | 2 + .../tests/010.rewrite_timestamp/timestamp/main.c | 2 +- src/tools/pcp/Makefile.in | 18 +- src/utils/error/elog.c | 74 +- src/watchdog/watchdog.c | 1 + src/watchdog/wd_escalation.c | 2 + src/watchdog/wd_heartbeat.c | 2 + src/watchdog/wd_if.c | 1 + src/watchdog/wd_lifecheck.c | 1 + src/watchdog/wd_ping.c | 1 + 30 files changed, 2092 insertions(+), 146 deletions(-) diff --git a/configure b/configure index 941a1b25c..cd9b12385 100755 --- a/configure +++ b/configure @@ -793,6 +793,7 @@ with_pgsql_includedir with_pgsql_libdir with_sunifdef with_openssl +with_ldap with_pam with_memcached enable_rpath @@ -1462,6 +1463,7 @@ Optional Packages: --with-pgsql-libdir=DIR site library files for PostgreSQL in DIR --with-sunifdef=DIR install path for sunifdef utility --with-openssl build with OpenSSL support + --with-ldap build with LDAP support --with-pam build with PAM support --with-memcached=DIR site header files for libmemcached in DIR @@ -14878,6 +14880,89 @@ fi fi +# Check whether --with-ldap was given. +if test "${with_ldap+set}" = set; then : + withval=$with_ldap; +$as_echo "#define USE_LDAP 1" >>confdefs.h + +fi + +if test "$with_ldap" = yes ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_bind in -lldap" >&5 +$as_echo_n "checking for ldap_bind in -lldap... " >&6; } +if ${ac_cv_lib_ldap_ldap_bind+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lldap $EXTRA_LDAP_LIBS $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ldap_bind (); +int +main () +{ +return ldap_bind (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_ldap_ldap_bind=yes +else + ac_cv_lib_ldap_ldap_bind=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ldap_ldap_bind" >&5 +$as_echo "$ac_cv_lib_ldap_ldap_bind" >&6; } +if test "x$ac_cv_lib_ldap_ldap_bind" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBLDAP 1 +_ACEOF + + LIBS="-lldap $LIBS" + +else + as_fn_error $? "library 'ldap' is required for LDAP" "$LINENO" 5 +fi + + for ac_header in ldap.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "ldap.h" "ac_cv_header_ldap_h" "$ac_includes_default" +if test "x$ac_cv_header_ldap_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LDAP_H 1 +_ACEOF + +else + as_fn_error $? "header file <ldap.h> is required for LDAP" "$LINENO" 5 +fi + +done + + for ac_func in ldap_initialize +do : + ac_fn_c_check_func "$LINENO" "ldap_initialize" "ac_cv_func_ldap_initialize" +if test "x$ac_cv_func_ldap_initialize" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LDAP_INITIALIZE 1 +_ACEOF + +fi +done + +fi + + # Check whether --with-pam was given. if test "${with_pam+set}" = set; then : withval=$with_pam; diff --git a/src/Makefile.am b/src/Makefile.am index dae2c26c4..65a17ab09 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ pgpool_SOURCES = main/main.c \ main/pgpool_main.c \ main/health_check.c \ main/pool_internal_comms.c \ + main/pgpool_logger.c \ config/pool_config.l \ config/pool_config_variables.c \ pcp_con/pcp_child.c \ diff --git a/src/Makefile.in b/src/Makefile.in index 12ec70db4..e424d2a37 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15.1 from Makefile.am. +# Makefile.in generated by automake 1.13.4 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2017 Free Software Foundation, Inc. +# Copyright (C) 1994-2013 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -16,17 +16,7 @@ VPATH = @srcdir@ -am__is_gnu_make = { \ - if test -z '$(MAKELEVEL)'; then \ - false; \ - elif test -n '$(MAKE_HOST)'; then \ - true; \ - elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ - true; \ - else \ - false; \ - fi; \ -} +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ @@ -91,6 +81,9 @@ build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = pgpool$(EXEEXT) subdir = src +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/mkinstalldirs config/pool_config.c \ + $(top_srcdir)/ylwrap ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/docbook.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ @@ -101,7 +94,6 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/docbook.m4 \ $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) -DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs CONFIG_HEADER = $(top_builddir)/src/include/config.h CONFIG_CLEAN_FILES = @@ -113,7 +105,7 @@ am__dirstamp = $(am__leading_dot)dirstamp am_pgpool_OBJECTS = main/main.$(OBJEXT) main/pool_globals.$(OBJEXT) \ main/pgpool_main.$(OBJEXT) main/health_check.$(OBJEXT) \ main/pool_internal_comms.$(OBJEXT) \ - config/pool_config.$(OBJEXT) \ + main/pgpool_logger.$(OBJEXT) config/pool_config.$(OBJEXT) \ config/pool_config_variables.$(OBJEXT) \ pcp_con/pcp_child.$(OBJEXT) pcp_con/pcp_worker.$(OBJEXT) \ pcp_con/recovery.$(OBJEXT) auth/md5.$(OBJEXT) \ @@ -271,8 +263,6 @@ am__define_uniq_tagged_files = \ ETAGS = etags CTAGS = ctags DIST_SUBDIRS = $(SUBDIRS) -am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/mkinstalldirs \ - $(top_srcdir)/ylwrap config/pool_config.c DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) am__relativize = \ dir0=`pwd`; \ @@ -431,7 +421,6 @@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ -runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ @@ -449,6 +438,7 @@ pgpool_SOURCES = main/main.c \ main/pgpool_main.c \ main/health_check.c \ main/pool_internal_comms.c \ + main/pgpool_logger.c \ config/pool_config.l \ config/pool_config_variables.c \ pcp_con/pcp_child.c \ @@ -622,6 +612,7 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign --ignore-deps src/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign --ignore-deps src/Makefile +.PRECIOUS: Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -696,6 +687,7 @@ main/pool_globals.$(OBJEXT): main/$(am__dirstamp) main/pgpool_main.$(OBJEXT): main/$(am__dirstamp) main/health_check.$(OBJEXT): main/$(am__dirstamp) main/pool_internal_comms.$(OBJEXT): main/$(am__dirstamp) +main/pgpool_logger.$(OBJEXT): main/$(am__dirstamp) config/$(am__dirstamp): @$(MKDIR_P) config @: > config/$(am__dirstamp) @@ -1164,8 +1156,6 @@ uninstall-am: uninstall-binPROGRAMS uninstall-pkgdataDATA \ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ uninstall-pkgdataDATA uninstall-sysconfDATA -.PRECIOUS: Makefile - # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/src/config/pool_config_variables.c b/src/config/pool_config_variables.c index 2e641818f..66998cc9e 100644 --- a/src/config/pool_config_variables.c +++ b/src/config/pool_config_variables.c @@ -712,6 +712,24 @@ static struct config_bool ConfigureNamesBool[] = false, NULL, NULL, NULL }, + { + {"logging_collector", CFGCXT_INIT, LOGING_CONFIG, + "Enable capturing of stderr into log files.", + CONFIG_VAR_TYPE_BOOL, false, 0 + }, + &g_pool_config.logging_collector, + false, + NULL, NULL, NULL + }, + { + {"log_truncate_on_rotation", CFGCXT_INIT, LOGING_CONFIG, + "If on, an existing log file gets truncated on time based log rotation.", + CONFIG_VAR_TYPE_BOOL, false, 0 + }, + &g_pool_config.log_truncate_on_rotation, + false, + NULL, NULL, NULL + }, /* End-of-list marker */ EMPTY_CONFIG_BOOL @@ -1238,6 +1256,25 @@ static struct config_string ConfigureNamesString[] = DEFAULT_LOGDIR, NULL, NULL, NULL, NULL }, + { + {"log_directory", CFGCXT_INIT, LOGING_CONFIG, + "directory where log files are written.", + CONFIG_VAR_TYPE_STRING, false, 0 + }, + &g_pool_config.log_directory, + "/tmp/pgpool_logs", + NULL, NULL, NULL, NULL + }, + + { + {"log_filename", CFGCXT_INIT, LOGING_CONFIG, + "log file name pattern.", + CONFIG_VAR_TYPE_STRING, false, 0 + }, + &g_pool_config.log_filename, + "pgpool-%Y-%m-%d_%H%M%S.log", + NULL, NULL, NULL, NULL + }, /* End-of-list marker */ EMPTY_CONFIG_STRING @@ -2042,6 +2079,37 @@ static struct config_int ConfigureNamesInt[] = 0, INT_MAX, NULL, NULL, NULL }, + { + {"log_rotation_age", CFGCXT_INIT, LOGING_CONFIG, + "Automatic rotation of logfiles will happen after that (minutes) time.", + CONFIG_VAR_TYPE_INT, false, GUC_UNIT_MIN + }, + &g_pool_config.log_rotation_age, + 1440,/*1 day*/ + 10, INT_MAX, + NULL, NULL, NULL + }, + { + {"log_rotation_size", CFGCXT_INIT, LOGING_CONFIG, + "Automatic rotation of logfiles will happen after that much (kilobytes) log output.", + CONFIG_VAR_TYPE_INT, false, GUC_UNIT_KB + }, + &g_pool_config.log_rotation_size, + 0, + 1, INT_MAX, + NULL, NULL, NULL + }, + { + {"log_file_mode", CFGCXT_INIT, LOGING_CONFIG, + "creation mode for log files.", + CONFIG_VAR_TYPE_INT, false, 0 + }, + &g_pool_config.log_file_mode, + 0600, + 0, INT_MAX, + NULL, NULL, NULL + }, + /* End-of-list marker */ EMPTY_CONFIG_INT diff --git a/src/include/main/pgpool_logger.h b/src/include/main/pgpool_logger.h new file mode 100644 index 000000000..95675b15f --- /dev/null +++ b/src/include/main/pgpool_logger.h @@ -0,0 +1,96 @@ +/* + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Copyright (c) 2003-2020 PgPool Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * ppgool_logger.h.: master definition header file + * + */ +/*------------------------------------------------------------------------- + * From: PostgreSQL + * src/include/postmaster/syslogger.h + * Exports from postmaster/syslogger.c. + * + * Copyright (c) 2004-2020, PostgreSQL Global Development Group + * + *------------------------------------------------------------------------- + */ +#ifndef _SYSLOGGER_H +#define _SYSLOGGER_H + +#include <limits.h> /* for PIPE_BUF */ + + +/* + * Primitive protocol structure for writing to syslogger pipe(s). The idea + * here is to divide long messages into chunks that are not more than + * PIPE_BUF bytes long, which according to POSIX spec must be written into + * the pipe atomically. The pipe reader then uses the protocol headers to + * reassemble the parts of a message into a single string. The reader can + * also cope with non-protocol data coming down the pipe, though we cannot + * guarantee long strings won't get split apart. + * + * We use non-nul bytes in is_last to make the protocol a tiny bit + * more robust against finding a false double nul byte prologue. But + * we still might find it in the len and/or pid bytes unless we're careful. + */ + +#ifdef PIPE_BUF +/* Are there any systems with PIPE_BUF > 64K? Unlikely, but ... */ +#if PIPE_BUF > 65536 +#define PIPE_CHUNK_SIZE 65536 +#else +#define PIPE_CHUNK_SIZE ((int) PIPE_BUF) +#endif +#else /* not defined */ +/* POSIX says the value of PIPE_BUF must be at least 512, so use that */ +#define PIPE_CHUNK_SIZE 512 +#endif + +typedef struct +{ + char nuls[2]; /* always \0\0 */ + uint16 len; /* size of this chunk (counts data only) */ + int32 pid; /* writer's pid */ + char is_last; /* last chunk of message? 't' or 'f' ('T' or + * 'F' for CSV case) */ + char data[]; /* data payload starts here */ +} PipeProtoHeader; + +typedef union +{ + PipeProtoHeader proto; + char filler[PIPE_CHUNK_SIZE]; +} PipeProtoChunk; + +#define PIPE_HEADER_SIZE offsetof(PipeProtoHeader, data) +#define PIPE_MAX_PAYLOAD ((int) (PIPE_CHUNK_SIZE - PIPE_HEADER_SIZE)) + + +extern int syslogPipe[2]; +extern bool redirection_done; + + +extern int SysLogger_Start(void); + +extern void write_syslogger_file(const char *buffer, int count, int dest); + + +extern bool CheckLogrotateSignal(void); +extern void RemoveLogrotateSignalFiles(void); + + +#endif /* _SYSLOGGER_H */ diff --git a/src/include/pool.h b/src/include/pool.h index c17447cb0..7f395e033 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -505,6 +505,7 @@ typedef enum PT_PCP, PT_PCP_WORKER, PT_HEALTH_CHECK, + PT_LOGGER, PT_LAST_PTYPE /* last ptype marker. any ptype must be above this. */ } ProcessType; @@ -536,6 +537,7 @@ typedef struct * pool_global.c */ extern pid_t mypid; /* parent pid */ +extern pid_t myProcPid; /* process pid */ extern ProcessType processType; extern ProcessState processState; extern volatile SI_ManageInfo *si_manage_info; diff --git a/src/include/pool_config.h b/src/include/pool_config.h index be81737b3..7eacd5b80 100644 --- a/src/include/pool_config.h +++ b/src/include/pool_config.h @@ -261,6 +261,15 @@ typedef struct * sent to client */ int log_min_messages; /* controls which message should be * emitted to server log */ + /* log collector settings */ + bool logging_collector; + int log_rotation_age; + int log_rotation_size; + char *log_directory; + char *log_filename; + bool log_truncate_on_rotation; + int log_file_mode; + bool master_slave_mode; /* operate in master/slave mode */ MasterSlaveSubModes master_slave_sub_mode; /* either "slony" or "stream" */ int64 delay_threshold; /* If the standby server delays more than diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h new file mode 100644 index 000000000..78e65c46c --- /dev/null +++ b/src/include/utils/timestamp.h @@ -0,0 +1,214 @@ +/* + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Copyright (c) 2003-2020 PgPool Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * ppgool_logger.h.: master definition header file + * + */ +/*------------------------------------------------------------------------- + * + * From: PostgreSQL + * src/include/datatype/timestamp.h + * Timestamp and Interval typedefs and related macros. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + *------------------------------------------------------------------------- + */ +#ifndef DATATYPE_TIMESTAMP_H +#define DATATYPE_TIMESTAMP_H + +/* + * Timestamp represents absolute time. + * + * Interval represents delta time. Keep track of months (and years), days, + * and hours/minutes/seconds separately since the elapsed time spanned is + * unknown until instantiated relative to an absolute time. + * + * Note that Postgres uses "time interval" to mean a bounded interval, + * consisting of a beginning and ending time, not a time span - thomas 97/03/20 + * + * Timestamps, as well as the h/m/s fields of intervals, are stored as + * int64 values with units of microseconds. (Once upon a time they were + * double values with units of seconds.) + * + * TimeOffset and fsec_t are convenience typedefs for temporary variables. + * Do not use fsec_t in values stored on-disk. + * Also, fsec_t is only meant for *fractional* seconds; beware of overflow + * if the value you need to store could be many seconds. + */ + +typedef int64 Timestamp; +typedef int64 TimestampTz; +typedef int64 TimeOffset; +typedef int32 fsec_t; /* fractional seconds (in microseconds) */ + +typedef struct +{ + TimeOffset time; /* all time units other than days, months and + * years */ + int32 day; /* days, after time for alignment */ + int32 month; /* months and years, after time for alignment */ +} Interval; + + +/* Limits on the "precision" option (typmod) for these data types */ +#define MAX_TIMESTAMP_PRECISION 6 +#define MAX_INTERVAL_PRECISION 6 + +/* + * Round off to MAX_TIMESTAMP_PRECISION decimal places. + * Note: this is also used for rounding off intervals. + */ +#define TS_PREC_INV 1000000.0 +#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV) + + +/* + * Assorted constants for datetime-related calculations + */ + +#define DAYS_PER_YEAR 365.25 /* assumes leap year every four years */ +#define MONTHS_PER_YEAR 12 +/* + * DAYS_PER_MONTH is very imprecise. The more accurate value is + * 365.2425/12 = 30.436875, or '30 days 10:29:06'. Right now we only + * return an integral number of days, but someday perhaps we should + * also return a 'time' value to be used as well. ISO 8601 suggests + * 30 days. + */ +#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */ +#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */ + +/* + * This doesn't adjust for uneven daylight savings time intervals or leap + * seconds, and it crudely estimates leap years. A more accurate value + * for days per years is 365.2422. + */ +#define SECS_PER_YEAR (36525 * 864) /* avoid floating-point computation */ +#define SECS_PER_DAY 86400 +#define SECS_PER_HOUR 3600 +#define SECS_PER_MINUTE 60 +#define MINS_PER_HOUR 60 + +#define USECS_PER_DAY INT64CONST(86400000000) +#define USECS_PER_HOUR INT64CONST(3600000000) +#define USECS_PER_MINUTE INT64CONST(60000000) +#define USECS_PER_SEC INT64CONST(1000000) + +/* + * We allow numeric timezone offsets up to 15:59:59 either way from Greenwich. + * Currently, the record holders for wackiest offsets in actual use are zones + * Asia/Manila, at -15:56:00 until 1844, and America/Metlakatla, at +15:13:42 + * until 1867. If we were to reject such values we would fail to dump and + * restore old timestamptz values with these zone settings. + */ +#define MAX_TZDISP_HOUR 15 /* maximum allowed hour part */ +#define TZDISP_LIMIT ((MAX_TZDISP_HOUR + 1) * SECS_PER_HOUR) + +/* + * DT_NOBEGIN represents timestamp -infinity; DT_NOEND represents +infinity + */ +#define DT_NOBEGIN PG_INT64_MIN +#define DT_NOEND PG_INT64_MAX + +#define TIMESTAMP_NOBEGIN(j) \ + do {(j) = DT_NOBEGIN;} while (0) + +#define TIMESTAMP_IS_NOBEGIN(j) ((j) == DT_NOBEGIN) + +#define TIMESTAMP_NOEND(j) \ + do {(j) = DT_NOEND;} while (0) + +#define TIMESTAMP_IS_NOEND(j) ((j) == DT_NOEND) + +#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j)) + + +/* + * Julian date support. + * + * date2j() and j2date() nominally handle the Julian date range 0..INT_MAX, + * or 4714-11-24 BC to 5874898-06-03 AD. In practice, date2j() will work and + * give correct negative Julian dates for dates before 4714-11-24 BC as well. + * We rely on it to do so back to 4714-11-01 BC. Allowing at least one day's + * slop is necessary so that timestamp rotation doesn't produce dates that + * would be rejected on input. For example, '4714-11-24 00:00 GMT BC' is a + * legal timestamptz value, but in zones east of Greenwich it would print as + * sometime in the afternoon of 4714-11-23 BC; if we couldn't process such a + * date we'd have a dump/reload failure. So the idea is for IS_VALID_JULIAN + * to accept a slightly wider range of dates than we really support, and + * then we apply the exact checks in IS_VALID_DATE or IS_VALID_TIMESTAMP, + * after timezone rotation if any. To save a few cycles, we can make + * IS_VALID_JULIAN check only to the month boundary, since its exact cutoffs + * are not very critical in this scheme. + * + * It is correct that JULIAN_MINYEAR is -4713, not -4714; it is defined to + * allow easy comparison to tm_year values, in which we follow the convention + * that tm_year <= 0 represents abs(tm_year)+1 BC. + */ + +#define JULIAN_MINYEAR (-4713) +#define JULIAN_MINMONTH (11) +#define JULIAN_MINDAY (24) +#define JULIAN_MAXYEAR (5874898) +#define JULIAN_MAXMONTH (6) +#define JULIAN_MAXDAY (3) + +#define IS_VALID_JULIAN(y,m,d) \ + (((y) > JULIAN_MINYEAR || \ + ((y) == JULIAN_MINYEAR && ((m) >= JULIAN_MINMONTH))) && \ + ((y) < JULIAN_MAXYEAR || \ + ((y) == JULIAN_MAXYEAR && ((m) < JULIAN_MAXMONTH)))) + +/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */ +#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */ +#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */ + +/* + * Range limits for dates and timestamps. + * + * We have traditionally allowed Julian day zero as a valid datetime value, + * so that is the lower bound for both dates and timestamps. + * + * The upper limit for dates is 5874897-12-31, which is a bit less than what + * the Julian-date code can allow. For timestamps, the upper limit is + * 294276-12-31. The int64 overflow limit would be a few days later; again, + * leaving some slop avoids worries about corner-case overflow, and provides + * a simpler user-visible definition. + */ + +/* First allowed date, and first disallowed date, in Julian-date form */ +#define DATETIME_MIN_JULIAN (0) +#define DATE_END_JULIAN (2147483494) /* == date2j(JULIAN_MAXYEAR, 1, 1) */ +#define TIMESTAMP_END_JULIAN (109203528) /* == date2j(294277, 1, 1) */ + +/* Timestamp limits */ +#define MIN_TIMESTAMP INT64CONST(-211813488000000000) +/* == (DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE) * USECS_PER_DAY */ +#define END_TIMESTAMP INT64CONST(9223371331200000000) +/* == (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE) * USECS_PER_DAY */ + +/* Range-check a date (given in Postgres, not Julian, numbering) */ +#define IS_VALID_DATE(d) \ + ((DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE) <= (d) && \ + (d) < (DATE_END_JULIAN - POSTGRES_EPOCH_JDATE)) + +/* Range-check a timestamp */ +#define IS_VALID_TIMESTAMP(t) (MIN_TIMESTAMP <= (t) && (t) < END_TIMESTAMP) + +#endif /* DATATYPE_TIMESTAMP_H */ diff --git a/src/libs/pcp/Makefile.in b/src/libs/pcp/Makefile.in index d956bb1b0..58a6f018d 100644 --- a/src/libs/pcp/Makefile.in +++ b/src/libs/pcp/Makefile.in @@ -340,7 +340,7 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = -D_GNU_SOURCE -DPOOL_PRIVATE -I @PGSQL_INCLUDE_DIR@ lib_LTLIBRARIES = libpcp.la -libpcp_la_LDFLAGS = -version-info 1:0:0 +libpcp_la_LDFLAGS = -version-info 2:0:0 dist_libpcp_la_SOURCES = pcp.c \ ../../utils/pool_path.c \ ../../tools/fe_port.c \ diff --git a/src/main/main.c b/src/main/main.c index afffd3653..0f79e7e56 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -222,6 +222,7 @@ main(int argc, char **argv) hba_file = make_absolute_path(hba_file_path, base_dir); mypid = getpid(); + myProcPid = mypid; pool_init_config(); @@ -478,6 +479,7 @@ daemonize(void) #endif mypid = getpid(); + myProcPid = mypid; write_pid_file(); if (chdir("/")) ereport(WARNING, diff --git a/src/main/pgpool_logger.c b/src/main/pgpool_logger.c new file mode 100644 index 000000000..0e6b56cdb --- /dev/null +++ b/src/main/pgpool_logger.c @@ -0,0 +1,1176 @@ +/* -*-pgpool_logger-c-*- */ +/* + * $Header$ + * + * pgpool: a language independent connection pool server for PostgreSQL + * written by Tatsuo Ishii + * + * Copyright (c) 2003-2020 PgPool Global Development Group + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of the + * author not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. The author makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + */ +/*------------------------------------------------------------------------- + * + * From: PostgreSQL + * src/backend/postmaster/syslogger.c + * Copyright (c) 2004-2020, PostgreSQL Global Development Group + *------------------------------------------------------------------------- + */ + +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <time.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include "pool.h" +#include "pool_config.h" +#include "utils/palloc.h" +#include "utils/memutils.h" +#include "utils/elog.h" +#include "parser/pg_list.h" +#include "parser/stringinfo.h" +#include "utils/ps_status.h" +#include "utils/timestamp.h" +#include "utils/pool_signal.h" +#include "main/pgpool_logger.h" + +#define DEVNULL "/dev/null" +typedef int64 pg_time_t; +/* + * We read() into a temp buffer twice as big as a chunk, so that any fragment + * left after processing can be moved down to the front and we'll still have + * room to read a full chunk. + */ +#define READ_BUF_SIZE (2 * PIPE_CHUNK_SIZE) + +/* Log rotation signal file path, relative to $PGDATA */ +#define LOGROTATE_SIGNAL_FILE "logrotate" + + +/* + * GUC parameters. Logging_collector cannot be changed after postmaster + * start, but the rest can change at SIGHUP. + */ + + +bool redirection_done = false; + +/* + * Private state + */ +static pg_time_t next_rotation_time; +static bool pipe_eof_seen = false; +static bool rotation_disabled = false; +static FILE *syslogFile = NULL; +static FILE *csvlogFile = NULL; +static pg_time_t first_syslogger_file_time = 0; +static char *last_file_name = NULL; +static char *last_csv_file_name = NULL; + +/* + * Buffers for saving partial messages from different backends. + * + * Keep NBUFFER_LISTS lists of these, with the entry for a given source pid + * being in the list numbered (pid % NBUFFER_LISTS), so as to cut down on + * the number of entries we have to examine for any one incoming message. + * There must never be more than one entry for the same source pid. + * + * An inactive buffer is not removed from its list, just held for re-use. + * An inactive buffer has pid == 0 and undefined contents of data. + */ +typedef struct +{ + int32 pid; /* PID of source process */ + StringInfoData data; /* accumulated data, as a StringInfo */ +} save_buffer; + +#define NBUFFER_LISTS 256 +static List *buffer_lists[NBUFFER_LISTS]; + +int syslogPipe[2] = {-1, -1}; + + +/* + * Flags set by interrupt handlers for later service in the main loop. + */ +static volatile sig_atomic_t got_SIGHUP = false; +static volatile sig_atomic_t rotation_requested = false; + + +static void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn(); +static void process_pipe_input(char *logbuffer, int *bytes_in_logbuffer); +static void flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer); +static FILE *logfile_open(const char *filename, const char *mode, + bool allow_errors); + +static void logfile_rotate(bool time_based_rotation, int size_rotation_for); +static char *logfile_getname(pg_time_t timestamp, const char *suffix); +static void set_next_rotation_time(void); +static void sigHupHandler(int sig); +static void sigUsr1Handler(int sig); + + +/* + * Main entry point for syslogger process + * argc/argv parameters are valid only in EXEC_BACKEND case. + */ +static void +SysLoggerMain(int argc, char *argv[]) +{ + char logbuffer[READ_BUF_SIZE]; + int bytes_in_logbuffer = 0; + char *currentLogDir; + char *currentLogFilename; + int currentLogRotationAge; + pg_time_t now; + + now = time(NULL); + + + init_ps_display("", "", "", ""); + set_ps_display("PgpoolLogger", false); + + /* + * If we restarted, our stderr is already redirected into our own input + * pipe. This is of course pretty useless, not to mention that it + * interferes with detecting pipe EOF. Point stderr to /dev/null. This + * assumes that all interesting messages generated in the syslogger will + * come through elog.c and will be sent to write_syslogger_file. + */ + if (redirection_done) + { + int fd = open(DEVNULL, O_WRONLY, 0); + + /* + * The closes might look redundant, but they are not: we want to be + * darn sure the pipe gets closed even if the open failed. We can + * survive running with stderr pointing nowhere, but we can't afford + * to have extra pipe input descriptors hanging around. + * + * As we're just trying to reset these to go to DEVNULL, there's not + * much point in checking for failure from the close/dup2 calls here, + * if they fail then presumably the file descriptors are closed and + * any writes will go into the bitbucket anyway. + */ + close(fileno(stdout)); + close(fileno(stderr)); + if (fd != -1) + { + (void) dup2(fd, fileno(stdout)); + (void) dup2(fd, fileno(stderr)); + close(fd); + } + } + + + /* + * Also close our copy of the write end of the pipe. This is needed to + * ensure we can detect pipe EOF correctly. (But note that in the restart + * case, the postmaster already did this.) + */ + if (syslogPipe[1] >= 0) + close(syslogPipe[1]); + syslogPipe[1] = -1; + + /* + * Properly accept or ignore signals the postmaster might send us + * + * Note: we ignore all termination signals, and instead exit only when all + * upstream processes are gone, to ensure we don't miss any dying gasps of + * broken backends... + */ + + pool_signal(SIGHUP, sigHupHandler); + + pool_signal(SIGINT, SIG_IGN); + pool_signal(SIGTERM, SIG_IGN); + pool_signal(SIGQUIT, SIG_IGN); + pool_signal(SIGALRM, SIG_IGN); + pool_signal(SIGPIPE, SIG_IGN); + pool_signal(SIGUSR1, sigUsr1Handler); /* request log rotation */ + pool_signal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pool_signal(SIGCHLD, SIG_DFL); + + POOL_SETMASK(&UnBlockSig); + + + /* + * Remember active logfiles' name(s). We recompute 'em from the reference + * time because passing down just the pg_time_t is a lot cheaper than + * passing a whole file path in the EXEC_BACKEND case. + */ + last_file_name = logfile_getname(first_syslogger_file_time, NULL); + if (csvlogFile != NULL) + last_csv_file_name = logfile_getname(first_syslogger_file_time, ".csv"); + + /* remember active logfile parameters */ + currentLogDir = pstrdup(pool_config->log_directory); + currentLogFilename = pstrdup(pool_config->log_filename); + currentLogRotationAge = pool_config->log_rotation_age; + /* set next planned rotation time */ + set_next_rotation_time(); + + /* + * Set up a reusable WaitEventSet object we'll use to wait for our latch, + * and (except on Windows) our socket. + * + * Unlike all other postmaster child processes, we'll ignore postmaster + * death because we want to collect final log output from all backends and + * then exit last. We'll do that by running until we see EOF on the + * syslog pipe, which implies that all other backends have exited + * (including the postmaster). + */ + + /* main worker loop */ + for (;;) + { + bool time_based_rotation = false; + int size_rotation_for = 0; + struct timeval timeout; + fd_set rfds; + int rc; + + /* + * Process any requests or signals received recently. + */ + if (got_SIGHUP) + { + got_SIGHUP = false; + MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext); + + pool_get_config(get_config_file_name(), CFGCXT_RELOAD); + MemoryContextSwitchTo(oldContext); + + /* + * Check if the log directory or filename pattern changed in + * pgpool.conf. If so, force rotation to make sure we're + * writing the logfiles in the right place. + */ + if (strcmp(pool_config->log_directory, currentLogDir) != 0) + { + pfree(currentLogDir); + currentLogDir = pstrdup(pool_config->log_directory); + rotation_requested = true; + + /* + * Also, create new directory if not present; ignore errors + */ + if (mkdir(pool_config->log_directory, S_IREAD | S_IWRITE | S_IEXEC) == -1) + { + ereport(LOG, + (errmsg("pgpool logger, failed to create directory:\"%s\". error:\"%s\"", pool_config->log_directory, strerror(errno)))); + } + } + if (strcmp(pool_config->log_filename, currentLogFilename) != 0) + { + pfree(currentLogFilename); + currentLogFilename = pstrdup(pool_config->log_filename); + rotation_requested = true; + } + + /* + * Force a rotation if CSVLOG output was just turned on or off and + * we need to open or close csvlogFile accordingly. + */ + if (((pool_config->log_destination & LOG_DESTINATION_CSVLOG) != 0) != + (csvlogFile != NULL)) + rotation_requested = true; + + /* + * If rotation time parameter changed, reset next rotation time, + * but don't immediately force a rotation. + */ + if (currentLogRotationAge != pool_config->log_rotation_age) + { + currentLogRotationAge = pool_config->log_rotation_age; + set_next_rotation_time(); + } + + /* + * If we had a rotation-disabling failure, re-enable rotation + * attempts after SIGHUP, and force one immediately. + */ + if (rotation_disabled) + { + rotation_disabled = false; + rotation_requested = true; + } + + } + + if (pool_config->log_rotation_age > 0 && !rotation_disabled) + { + /* Do a logfile rotation if it's time */ + now = (pg_time_t) time(NULL); + if (now >= next_rotation_time) + rotation_requested = time_based_rotation = true; + } + + if (!rotation_requested && pool_config->log_rotation_size > 0 && !rotation_disabled) + { + /* Do a rotation if file is too big */ + if (ftell(syslogFile) >= pool_config->log_rotation_size * 1024L) + { + rotation_requested = true; + size_rotation_for |= LOG_DESTINATION_STDERR; + } + if (csvlogFile != NULL && + ftell(csvlogFile) >= pool_config->log_rotation_size * 1024L) + { + rotation_requested = true; + size_rotation_for |= LOG_DESTINATION_CSVLOG; + } + } + + if (rotation_requested) + { + /* + * Force rotation when both values are zero. It means the request + * was sent by pg_rotate_logfile() or "pg_ctl logrotate". + */ + if (!time_based_rotation && size_rotation_for == 0) + size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG; + logfile_rotate(time_based_rotation, size_rotation_for); + } + + /* + * Calculate time till next time-based rotation, so that we don't + * sleep longer than that. We assume the value of "now" obtained + * above is still close enough. Note we can't make this calculation + * until after calling logfile_rotate(), since it will advance + * next_rotation_time. + * + * Also note that we need to beware of overflow in calculation of the + * timeout: with large settings of pool_config->log_rotation_age, next_rotation_time + * could be more than INT_MAX msec in the future. In that case we'll + * wait no more than INT_MAX msec, and try again. + */ + timeout.tv_sec = 0; + if (pool_config->log_rotation_age > 0 && !rotation_disabled) + { + pg_time_t delay; + + delay = next_rotation_time - now; + if (delay > 0) + { + if (delay > INT_MAX / 1000) + delay = INT_MAX / 1000; + timeout.tv_sec = delay; + } + } + + /* + * Sleep until there's something to do + */ + + FD_ZERO(&rfds); + FD_SET(syslogPipe[0], &rfds); + rc = select(syslogPipe[0] + 1, &rfds, NULL, NULL, timeout.tv_sec?&timeout:NULL); + if (rc == 1) + { + int bytesRead; + + bytesRead = read(syslogPipe[0], + logbuffer + bytes_in_logbuffer, + sizeof(logbuffer) - bytes_in_logbuffer); + if (bytesRead < 0) + { + if (errno != EINTR) + ereport(LOG, + (errmsg("could not read from logger pipe: %m"))); + } + else if (bytesRead > 0) + { + bytes_in_logbuffer += bytesRead; + process_pipe_input(logbuffer, &bytes_in_logbuffer); + continue; + } + else + { + /* + * Zero bytes read when select() is saying read-ready means + * EOF on the pipe: that is, there are no longer any processes + * with the pipe write end open. Therefore, the postmaster + * and all backends are shut down, and we are done. + */ + pipe_eof_seen = true; + + /* if there's any data left then force it out now */ + flush_pipe_input(logbuffer, &bytes_in_logbuffer); + } + } + + if (pipe_eof_seen) + { + /* + * seeing this message on the real stderr is annoying - so we make + * it DEBUG1 to suppress in normal use. + */ + ereport(DEBUG1, + (errmsg("logger shutting down"))); + + /* + * Normal exit from the syslogger is here. Note that we + * deliberately do not close syslogFile before exiting; this is to + * allow for the possibility of elog messages being generated + * inside proc_exit. Regular exit() will take care of flushing + * and closing stdio channels. + */ + proc_exit(0); + } + } +} + +/* + * Postmaster subroutine to start a syslogger subprocess. + */ +int +SysLogger_Start(void) +{ + pid_t sysloggerPid; + char *filename; + + if (!pool_config->logging_collector) + return 0; + + /* + * If first time through, create the pipe which will receive stderr + * output. + * + * If the syslogger crashes and needs to be restarted, we continue to use + * the same pipe (indeed must do so, since extant backends will be writing + * into that pipe). + * + * This means the postmaster must continue to hold the read end of the + * pipe open, so we can pass it down to the reincarnated syslogger. This + * is a bit klugy but we have little choice. + * + * Also note that we don't bother counting the pipe FDs by calling + * Reserve/ReleaseExternalFD. There's no real need to account for them + * accurately in the postmaster or syslogger process, and both ends of the + * pipe will wind up closed in all other postmaster children. + */ + if (syslogPipe[0] < 0) + { + if (pipe(syslogPipe) < 0) + ereport(FATAL, + (errmsg("could not create pipe for syslog: %m"))); + } + + /* + * Create log directory if not present; ignore errors + */ + mkdir(pool_config->log_directory, S_IREAD | S_IWRITE | S_IEXEC); + + + /* + * The initial logfile is created right in the postmaster, to verify that + * the pool_config->log_directory is writable. We save the reference time so that the + * syslogger child process can recompute this file name. + * + * It might look a bit strange to re-do this during a syslogger restart, + * but we must do so since the postmaster closed syslogFile after the + * previous fork (and remembering that old file wouldn't be right anyway). + * Note we always append here, we won't overwrite any existing file. This + * is consistent with the normal rules, because by definition this is not + * a time-based rotation. + */ + first_syslogger_file_time = time(NULL); + + filename = logfile_getname(first_syslogger_file_time, NULL); + + syslogFile = logfile_open(filename, "a", false); + + pfree(filename); + + /* + * Likewise for the initial CSV log file, if that's enabled. (Note that + * we open syslogFile even when only CSV output is nominally enabled, + * since some code paths will write to syslogFile anyway.) + */ + if (pool_config->log_destination & LOG_DESTINATION_CSVLOG) + { + filename = logfile_getname(first_syslogger_file_time, ".csv"); + + csvlogFile = logfile_open(filename, "a", false); + + pfree(filename); + } + + switch ((sysloggerPid = fork())) + { + case -1: + ereport(LOG, + (errmsg("could not fork system logger: %m"))); + return 0; + + case 0: + on_exit_reset(); + myProcPid = getpid(); + processType = PT_LOGGER; + /* do the work */ + SysLoggerMain(0, NULL); + break; + + default: + /* success, in postmaster */ + + /* now we redirect stderr, if not done already */ + if (!redirection_done) + { + + /* + * Leave a breadcrumb trail when redirecting, in case the user + * forgets that redirection is active and looks only at the + * original stderr target file. + */ + ereport(LOG, + (errmsg("redirecting log output to logging collector process"), + errhint("Future log output will appear in directory \"%s\".", + pool_config->log_directory))); + +#ifndef WIN32 + fflush(stdout); + if (dup2(syslogPipe[1], fileno(stdout)) < 0) + ereport(FATAL, + (errmsg("could not redirect stdout: %m"))); + fflush(stderr); + if (dup2(syslogPipe[1], fileno(stderr)) < 0) + ereport(FATAL, + (errmsg("could not redirect stderr: %m"))); + /* Now we are done with the write end of the pipe. */ + close(syslogPipe[1]); + syslogPipe[1] = -1; +#else + + /* + * open the pipe in binary mode and make sure stderr is binary + * after it's been dup'ed into, to avoid disturbing the pipe + * chunking protocol. + */ + fflush(stderr); + fd = _open_osfhandle((intptr_t) syslogPipe[1], + _O_APPEND | _O_BINARY); + if (dup2(fd, _fileno(stderr)) < 0) + ereport(FATAL, + (errmsg("could not redirect stderr: %m"))); + close(fd); + _setmode(_fileno(stderr), _O_BINARY); + + /* + * Now we are done with the write end of the pipe. + * CloseHandle() must not be called because the preceding + * close() closes the underlying handle. + */ + syslogPipe[1] = 0; +#endif + redirection_done = true; + } + + /* postmaster will never write the file(s); close 'em */ + fclose(syslogFile); + syslogFile = NULL; + if (csvlogFile != NULL) + { + fclose(csvlogFile); + csvlogFile = NULL; + } + return (int) sysloggerPid; + } + + /* we should never reach here */ + return 0; +} + + +/* -------------------------------- + * pipe protocol handling + * -------------------------------- + */ + +/* + * Process data received through the syslogger pipe. + * + * This routine interprets the log pipe protocol which sends log messages as + * (hopefully atomic) chunks - such chunks are detected and reassembled here. + * + * The protocol has a header that starts with two nul bytes, then has a 16 bit + * length, the pid of the sending process, and a flag to indicate if it is + * the last chunk in a message. Incomplete chunks are saved until we read some + * more, and non-final chunks are accumulated until we get the final chunk. + * + * All of this is to avoid 2 problems: + * . partial messages being written to logfiles (messes rotation), and + * . messages from different backends being interleaved (messages garbled). + * + * Any non-protocol messages are written out directly. These should only come + * from non-Pgpool sources, however (e.g. third party libraries writing to + * stderr). + * + * logbuffer is the data input buffer, and *bytes_in_logbuffer is the number + * of bytes present. On exit, any not-yet-eaten data is left-justified in + * logbuffer, and *bytes_in_logbuffer is updated. + */ +static void +process_pipe_input(char *logbuffer, int *bytes_in_logbuffer) +{ + char *cursor = logbuffer; + int count = *bytes_in_logbuffer; + int dest = LOG_DESTINATION_STDERR; + + /* While we have enough for a header, process data... */ + while (count >= (int) (offsetof(PipeProtoHeader, data) + 1)) + { + PipeProtoHeader p; + int chunklen; + + /* Do we have a valid header? */ + memcpy(&p, cursor, offsetof(PipeProtoHeader, data)); + if (p.nuls[0] == '\0' && p.nuls[1] == '\0' && + p.len > 0 && p.len <= PIPE_MAX_PAYLOAD && + p.pid != 0 && + (p.is_last == 't' || p.is_last == 'f' || + p.is_last == 'T' || p.is_last == 'F')) + { + List *buffer_list; + ListCell *cell; + save_buffer *existing_slot = NULL, + *free_slot = NULL; + StringInfo str; + + chunklen = PIPE_HEADER_SIZE + p.len; + + /* Fall out of loop if we don't have the whole chunk yet */ + if (count < chunklen) + break; + + dest = (p.is_last == 'T' || p.is_last == 'F') ? + LOG_DESTINATION_CSVLOG : LOG_DESTINATION_STDERR; + + /* Locate any existing buffer for this source pid */ + buffer_list = buffer_lists[p.pid % NBUFFER_LISTS]; + foreach(cell, buffer_list) + { + save_buffer *buf = (save_buffer *) lfirst(cell); + + if (buf->pid == p.pid) + { + existing_slot = buf; + break; + } + if (buf->pid == 0 && free_slot == NULL) + free_slot = buf; + } + + if (p.is_last == 'f' || p.is_last == 'F') + { + /* + * Save a complete non-final chunk in a per-pid buffer + */ + if (existing_slot != NULL) + { + /* Add chunk to data from preceding chunks */ + str = &(existing_slot->data); + appendBinaryStringInfo(str, + cursor + PIPE_HEADER_SIZE, + p.len); + } + else + { + /* First chunk of message, save in a new buffer */ + if (free_slot == NULL) + { + /* + * Need a free slot, but there isn't one in the list, + * so create a new one and extend the list with it. + */ + free_slot = palloc(sizeof(save_buffer)); + buffer_list = lappend(buffer_list, free_slot); + buffer_lists[p.pid % NBUFFER_LISTS] = buffer_list; + } + free_slot->pid = p.pid; + str = &(free_slot->data); + initStringInfo(str); + appendBinaryStringInfo(str, + cursor + PIPE_HEADER_SIZE, + p.len); + } + } + else + { + /* + * Final chunk --- add it to anything saved for that pid, and + * either way write the whole thing out. + */ + if (existing_slot != NULL) + { + str = &(existing_slot->data); + appendBinaryStringInfo(str, + cursor + PIPE_HEADER_SIZE, + p.len); + write_syslogger_file(str->data, str->len, dest); + /* Mark the buffer unused, and reclaim string storage */ + existing_slot->pid = 0; + pfree(str->data); + } + else + { + /* The whole message was one chunk, evidently. */ + write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len, + dest); + } + } + + /* Finished processing this chunk */ + cursor += chunklen; + count -= chunklen; + } + else + { + /* Process non-protocol data */ + + /* + * Look for the start of a protocol header. If found, dump data + * up to there and repeat the loop. Otherwise, dump it all and + * fall out of the loop. (Note: we want to dump it all if at all + * possible, so as to avoid dividing non-protocol messages across + * logfiles. We expect that in many scenarios, a non-protocol + * message will arrive all in one read(), and we want to respect + * the read() boundary if possible.) + */ + for (chunklen = 1; chunklen < count; chunklen++) + { + if (cursor[chunklen] == '\0') + break; + } + /* fall back on the stderr log as the destination */ + write_syslogger_file(cursor, chunklen, LOG_DESTINATION_STDERR); + cursor += chunklen; + count -= chunklen; + } + } + + /* We don't have a full chunk, so left-align what remains in the buffer */ + if (count > 0 && cursor != logbuffer) + memmove(logbuffer, cursor, count); + *bytes_in_logbuffer = count; +} + +/* + * Force out any buffered data + * + * This is currently used only at syslogger shutdown, but could perhaps be + * useful at other times, so it is careful to leave things in a clean state. + */ +static void +flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer) +{ + int i; + + /* Dump any incomplete protocol messages */ + for (i = 0; i < NBUFFER_LISTS; i++) + { + List *list = buffer_lists[i]; + ListCell *cell; + + foreach(cell, list) + { + save_buffer *buf = (save_buffer *) lfirst(cell); + + if (buf->pid != 0) + { + StringInfo str = &(buf->data); + + write_syslogger_file(str->data, str->len, + LOG_DESTINATION_STDERR); + /* Mark the buffer unused, and reclaim string storage */ + buf->pid = 0; + pfree(str->data); + } + } + } + + /* + * Force out any remaining pipe data as-is; we don't bother trying to + * remove any protocol headers that may exist in it. + */ + if (*bytes_in_logbuffer > 0) + write_syslogger_file(logbuffer, *bytes_in_logbuffer, + LOG_DESTINATION_STDERR); + *bytes_in_logbuffer = 0; +} + + +/* -------------------------------- + * logfile routines + * -------------------------------- + */ + +/* + * Write text to the currently open logfile + * + * This is exported so that elog.c can call it when processType is PT_LOGGER; +. + * This allows the syslogger process to record elog messages of its own, + * even though its stderr does not point at the syslog pipe. + */ +void +write_syslogger_file(const char *buffer, int count, int destination) +{ + int rc; + FILE *logfile; + + /* + * If we're told to write to csvlogFile, but it's not open, dump the data + * to syslogFile (which is always open) instead. This can happen if CSV + * output is enabled after postmaster start and we've been unable to open + * csvlogFile. There are also race conditions during a parameter change + * whereby backends might send us CSV output before we open csvlogFile or + * after we close it. Writing CSV-formatted output to the regular log + * file isn't great, but it beats dropping log output on the floor. + * + * Think not to improve this by trying to open csvlogFile on-the-fly. Any + * failure in that would lead to recursion. + */ + logfile = (destination == LOG_DESTINATION_CSVLOG && + csvlogFile != NULL) ? csvlogFile : syslogFile; + + rc = fwrite(buffer, 1, count, logfile); + + /* + * Try to report any failure. We mustn't use ereport because it would + * just recurse right back here, but write_stderr is OK: it will write + * either to the postmaster's original stderr, or to /dev/null, but never + * to our input pipe which would result in a different sort of looping. + */ + if (rc != count) + write_stderr("could not write to log file: %s\n", strerror(errno)); +} + + +/* + * Open a new logfile with proper permissions and buffering options. + * + * If allow_errors is true, we just log any open failure and return NULL + * (with errno still correct for the fopen failure). + * Otherwise, errors are treated as fatal. + */ +static FILE * +logfile_open(const char *filename, const char *mode, bool allow_errors) +{ + FILE *fh; + mode_t oumask; + + /* + * Note we do not let pool_config->log_file_mode disable IWUSR, since we certainly want + * to be able to write the files ourselves. + */ + oumask = umask((mode_t) ((~(pool_config->log_file_mode | S_IWUSR)) & (S_IRWXU | S_IRWXG | S_IRWXO))); + fh = fopen(filename, mode); + umask(oumask); + + if (fh) + { + setvbuf(fh, NULL, _IOLBF, 0); + +#ifdef WIN32 + /* use CRLF line endings on Windows */ + _setmode(_fileno(fh), _O_TEXT); +#endif + } + else + { + int save_errno = errno; + + ereport(allow_errors ? LOG : FATAL, + (errmsg("could not open log file \"%s\": %m", + filename))); + errno = save_errno; + } + + return fh; +} + +/* + * perform logfile rotation + */ +static void +logfile_rotate(bool time_based_rotation, int size_rotation_for) +{ + char *filename; + char *csvfilename = NULL; + pg_time_t fntime; + FILE *fh; + + rotation_requested = false; + + /* + * When doing a time-based rotation, invent the new logfile name based on + * the planned rotation time, not current time, to avoid "slippage" in the + * file name when we don't do the rotation immediately. + */ + if (time_based_rotation) + fntime = next_rotation_time; + else + fntime = time(NULL); + filename = logfile_getname(fntime, NULL); + if (pool_config->log_destination & LOG_DESTINATION_CSVLOG) + csvfilename = logfile_getname(fntime, ".csv"); + + /* + * Decide whether to overwrite or append. We can overwrite if (a) + * pool_config->log_truncate_on_rotation is set, (b) the rotation was triggered by + * elapsed time and not something else, and (c) the computed file name is + * different from what we were previously logging into. + * + * Note: last_file_name should never be NULL here, but if it is, append. + */ + if (time_based_rotation || (size_rotation_for & LOG_DESTINATION_STDERR)) + { + if (pool_config->log_truncate_on_rotation && time_based_rotation && + last_file_name != NULL && + strcmp(filename, last_file_name) != 0) + fh = logfile_open(filename, "w", true); + else + fh = logfile_open(filename, "a", true); + + if (!fh) + { + /* + * ENFILE/EMFILE are not too surprising on a busy system; just + * keep using the old file till we manage to get a new one. + * Otherwise, assume something's wrong with pool_config->log_directory and stop + * trying to create files. + */ + if (errno != ENFILE && errno != EMFILE) + { + ereport(LOG, + (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); + rotation_disabled = true; + } + + if (filename) + pfree(filename); + if (csvfilename) + pfree(csvfilename); + return; + } + + fclose(syslogFile); + syslogFile = fh; + + /* instead of pfree'ing filename, remember it for next time */ + if (last_file_name != NULL) + pfree(last_file_name); + last_file_name = filename; + filename = NULL; + } + + /* + * Same as above, but for csv file. Note that if LOG_DESTINATION_CSVLOG + * was just turned on, we might have to open csvlogFile here though it was + * not open before. In such a case we'll append not overwrite (since + * last_csv_file_name will be NULL); that is consistent with the normal + * rules since it's not a time-based rotation. + */ + if ((pool_config->log_destination & LOG_DESTINATION_CSVLOG) && + (csvlogFile == NULL || + time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG))) + { + if (pool_config->log_truncate_on_rotation && time_based_rotation && + last_csv_file_name != NULL && + strcmp(csvfilename, last_csv_file_name) != 0) + fh = logfile_open(csvfilename, "w", true); + else + fh = logfile_open(csvfilename, "a", true); + + if (!fh) + { + /* + * ENFILE/EMFILE are not too surprising on a busy system; just + * keep using the old file till we manage to get a new one. + * Otherwise, assume something's wrong with pool_config->log_directory and stop + * trying to create files. + */ + if (errno != ENFILE && errno != EMFILE) + { + ereport(LOG, + (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); + rotation_disabled = true; + } + + if (filename) + pfree(filename); + if (csvfilename) + pfree(csvfilename); + return; + } + + if (csvlogFile != NULL) + fclose(csvlogFile); + csvlogFile = fh; + + /* instead of pfree'ing filename, remember it for next time */ + if (last_csv_file_name != NULL) + pfree(last_csv_file_name); + last_csv_file_name = csvfilename; + csvfilename = NULL; + } + else if (!(pool_config->log_destination & LOG_DESTINATION_CSVLOG) && + csvlogFile != NULL) + { + /* CSVLOG was just turned off, so close the old file */ + fclose(csvlogFile); + csvlogFile = NULL; + if (last_csv_file_name != NULL) + pfree(last_csv_file_name); + last_csv_file_name = NULL; + } + + if (filename) + pfree(filename); + if (csvfilename) + pfree(csvfilename); + + set_next_rotation_time(); +} + + +/* + * construct logfile name using timestamp information + * + * If suffix isn't NULL, append it to the name, replacing any ".log" + * that may be in the pattern. + * + * Result is palloc'd. + */ +static char * +logfile_getname(pg_time_t timestamp, const char *suffix) +{ + char *filename; + int len; + + filename = palloc(MAXPGPATH); + + snprintf(filename, MAXPGPATH, "%s/", pool_config->log_directory); + + len = strlen(filename); + + /* treat pool_config->log_filename as a strftime pattern */ +// strftime(strbuf, 128, "%Y-%m-%d %H:%M:%S", localtime(&now)); + strftime(filename + len, MAXPGPATH - len, pool_config->log_filename, + localtime(&timestamp)); + + if (suffix != NULL) + { + len = strlen(filename); + if (len > 4 && (strcmp(filename + (len - 4), ".log") == 0)) + len -= 4; + strlcpy(filename + len, suffix, MAXPGPATH - len); + } + + return filename; +} + +/* + * Determine the next planned rotation time, and store in next_rotation_time. + */ +static void +set_next_rotation_time(void) +{ + pg_time_t now; + struct tm *tm; + int rotinterval; + + /* nothing to do if time-based rotation is disabled */ + if (pool_config->log_rotation_age <= 0) + return; + + /* + * The requirements here are to choose the next time > now that is a + * "multiple" of the log rotation interval. "Multiple" can be interpreted + * fairly loosely. In this version we align to log_timezone rather than + * GMT. + */ + rotinterval = pool_config->log_rotation_age * SECS_PER_MINUTE; /* convert to seconds */ + now = (pg_time_t) time(NULL); + tm = localtime(&now); + now += tm->tm_gmtoff; + now -= now % rotinterval; + now += rotinterval; + now -= tm->tm_gmtoff; + next_rotation_time = now; +} + + +/* -------------------------------- + * signal handler routines + * -------------------------------- + */ + +/* + * Check to see if a log rotation request has arrived. Should be + * called by postmaster after receiving SIGUSR1. + */ +bool +CheckLogrotateSignal(void) +{ + struct stat stat_buf; + + if (stat(LOGROTATE_SIGNAL_FILE, &stat_buf) == 0) + return true; + + return false; +} + +/* + * Remove the file signaling a log rotation request. + */ +void +RemoveLogrotateSignalFiles(void) +{ + unlink(LOGROTATE_SIGNAL_FILE); +} + +/* SIGHUP: set flag to reload config file */ +static void +sigHupHandler(int sig) +{ + int save_errno = errno; + + got_SIGHUP = true; +// SetLatch(MyLatch); + + errno = save_errno; +} + +/* SIGUSR1: set flag to rotate logfile */ +static void +sigUsr1Handler(int sig) +{ + int save_errno = errno; + + rotation_requested = true; +// SetLatch(MyLatch); + + errno = save_errno; +} diff --git a/src/main/pgpool_main.c b/src/main/pgpool_main.c index a2158ec32..86ef9b16b 100644 --- a/src/main/pgpool_main.c +++ b/src/main/pgpool_main.c @@ -39,6 +39,7 @@ #include "pool_config.h" #include "main/health_check.h" #include "main/pool_internal_comms.h" +#include "main/pgpool_logger.h" #include "utils/elog.h" #include "utils/palloc.h" #include "utils/memutils.h" @@ -138,7 +139,7 @@ static int trigger_failover_command(int node, const char *command_line, int old_master, int new_master, int old_primary); static int find_primary_node(void); static int find_primary_node_repeatedly(void); -static void terminate_all_childrens(); +static void terminate_all_childrens(int sig); static void system_will_go_down(int code, Datum arg); static char *process_name_from_pid(pid_t pid); static void sync_backend_from_watchdog(void); @@ -170,7 +171,7 @@ BACKEND_STATUS private_backend_status[MAX_NUM_BACKENDS]; */ ConnectionInfo *con_info; -static int *fds; /* listening file descriptors (UNIX socket, +static int *fds = NULL; /* listening file descriptors (UNIX socket, * inet domain sockets) */ static int pcp_unix_fd; /* unix domain socket fd for PCP (not used) */ @@ -200,6 +201,7 @@ static pid_t follow_pid = 0; /* pid for child process handling follow * command */ static pid_t pcp_pid = 0; /* pid for child process handling PCP */ static pid_t watchdog_pid = 0; /* pid for watchdog child process */ +static pid_t pgpool_logger_pid = 0; /* pid for pgpool_logger process */ static pid_t wd_lifecheck_pid = 0; /* pid for child process handling watchdog * lifecheck */ @@ -264,13 +266,28 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) /* set up signal handlers */ pool_signal(SIGPIPE, SIG_IGN); - /* create unix domain socket */ - fds = malloc(sizeof(int) * 2); - if (fds == NULL) - ereport(FATAL, - (errmsg("failed to allocate memory in startup process"))); + + /* start the log collector if enabled */ + pgpool_logger_pid = SysLogger_Start(); + /* + * If using syslogger, close the read side of the pipe. We don't bother + * tracking this in fd.c, either. + */ + if (syslogPipe[0] >= 0) + close(syslogPipe[0]); + syslogPipe[0] = -1; initialize_shared_mem_objects(clear_memcache_oidmaps); + + /* setup signal handlers */ + pool_signal(SIGCHLD, reap_handler); + pool_signal(SIGUSR1, sigusr1_handler); + pool_signal(SIGUSR2, wakeup_handler); + pool_signal(SIGTERM, exit_handler); + pool_signal(SIGINT, exit_handler); + pool_signal(SIGQUIT, exit_handler); + pool_signal(SIGHUP, reload_config_handler); + if (pool_config->use_watchdog) { sigset_t mask; @@ -278,20 +295,15 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) wakeup_request = 0; /* - * Watchdog process fires SIGUSR2 once in stable state, so install the - * SIGUSR2 handler first up. In addition, when wathcodg fails to start - * with FATAL, the process exits and SIGCHLD is fired, so SIGCHLD - * handelr is also needed. Finally, we also need to set the SIGUSR1 - * handler for the failover requests from other watchdog nodes. In + * Watchdog process fires SIGUSR2 once in stable state + * In addition, when wathcodg fails to start with FATAL, the process + * exits and SIGCHLD is fired, so we can also expect SIGCHLD from + * watchdog process. Finally, we also need to look for the SIGUSR1 + * signla for the failover requests from other watchdog nodes. In * case a request arrives at the same time when the watchdog has just * been initialized. - */ - pool_signal(SIGUSR2, wakeup_handler); - pool_signal(SIGCHLD, reap_handler); - pool_signal(SIGUSR1, sigusr1_handler); - - /* - * okay as we need to wait until watchdog is in stable state so only + * + * So we need to wait until watchdog is in stable state so only * wait for SIGUSR1, SIGCHLD, and signals those are necessary to make * sure we respond to user requests of shutdown if it arrives while we * are in waiting state. @@ -309,6 +321,8 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) sigdelset(&mask, SIGTERM); sigdelset(&mask, SIGINT); sigdelset(&mask, SIGQUIT); + sigdelset(&mask, SIGHUP); + watchdog_pid = initialize_watchdog(); ereport(LOG, (errmsg("waiting for watchdog to initialize"))); @@ -340,6 +354,12 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) } } + /* create unix domain socket */ + fds = malloc(sizeof(int) * 2); + if (fds == NULL) + ereport(FATAL, + (errmsg("failed to allocate memory in startup process"))); + fds[0] = create_unix_domain_socket(un_addr); fds[1] = -1; on_proc_exit(FileUnlink, (Datum) un_addr.sun_path); @@ -386,16 +406,6 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) process_info[i].start_time = time(NULL); } - /* set up signal handlers */ - - pool_signal(SIGTERM, exit_handler); - pool_signal(SIGINT, exit_handler); - pool_signal(SIGQUIT, exit_handler); - pool_signal(SIGCHLD, reap_handler); - pool_signal(SIGUSR1, sigusr1_handler); - pool_signal(SIGUSR2, wakeup_handler); - pool_signal(SIGHUP, reload_config_handler); - /* create pipe for delivering event */ if (pipe(pipe_fds) < 0) { @@ -612,7 +622,7 @@ pcp_fork_a_child(int unix_fd, int inet_fd, char *pcp_conf_file) /* Set the process type variable */ processType = PT_PCP; - + myProcPid = getpid(); /* call PCP child main */ POOL_SETMASK(&UnBlockSig); health_check_timer_expired = 0; @@ -706,6 +716,7 @@ worker_fork_a_child(ProcessType type, void (*func) (), void *params) /* Set the process type variable */ processType = type; + myProcPid = getpid(); set_application_name(type); /* call child main */ @@ -1002,38 +1013,74 @@ create_unix_domain_socket(struct sockaddr_un un_addr_tmp) } /* - * function called as shared memory exit call back to kill all childrens + * sends the kill signal to all Pgpool children except to + * the pgpool_logger child + * wait for the termination of all killed children before returning. */ static void -terminate_all_childrens() +terminate_all_childrens(int sig) { pid_t wpid; int i; - + int killed_count = 0; + int terminated_count = 0; /* * This is supposed to be called from main process */ if (processType != PT_MAIN) return; - POOL_SETMASK(&BlockSig); - kill_all_children(SIGINT); + if (sig != SIGTERM && sig != SIGINT && sig != SIGQUIT) + { + ereport(LOG, + (errmsg("invalid terminate signal: \"%d\"", sig), + errdetail("ignoring"))); + + return; + } + + for (i = 0; i < pool_config->num_init_children; i++) + { + pid_t pid = process_info[i].pid; + + if (pid != 0) + { + kill(pid, sig); + process_info[i].pid = 0; + killed_count++; + } + } + if (pcp_pid != 0) - kill(pcp_pid, SIGINT); + { + kill(pcp_pid, sig); + killed_count++; + } pcp_pid = 0; + if (worker_pid != 0) - kill(worker_pid, SIGINT); + { + kill(worker_pid, sig); + killed_count++; + } worker_pid = 0; + if (pool_config->use_watchdog) { if (pool_config->use_watchdog) { if (watchdog_pid) - kill(watchdog_pid, SIGINT); + { + kill(watchdog_pid, sig); + killed_count++; + } watchdog_pid = 0; if (wd_lifecheck_pid) - kill(wd_lifecheck_pid, SIGINT); + { + kill(wd_lifecheck_pid, sig); + killed_count++; + } wd_lifecheck_pid = 0; } } @@ -1042,33 +1089,32 @@ terminate_all_childrens() { if (health_check_pids[i] != 0) { - kill(health_check_pids[i], SIGINT); + kill(health_check_pids[i], sig); health_check_pids[i] = 0; + killed_count++; } } - - /* wait for all children to exit */ + /* wait for all killed children to exit */ do { int ret_pid; wpid = waitpid(-1, &ret_pid, 0); - } while (wpid > 0 || (wpid == -1 && errno == EINTR)); + if (wpid > 0) + terminated_count++; + } while (terminated_count < killed_count && + (wpid > 0 || (wpid == -1 && errno == EINTR))); if (wpid == -1 && errno != ECHILD) ereport(LOG, (errmsg("wait() failed. reason:%s", strerror(errno)))); - POOL_SETMASK(&UnBlockSig); } static RETSIGTYPE exit_handler(int sig) { - int i; - pid_t wpid; int *walk; - int save_errno = errno; POOL_SETMASK(&AuthBlockSig); @@ -1092,57 +1138,23 @@ static RETSIGTYPE exit_handler(int sig) exiting = 1; processState = EXITING; - /* Close listen socket */ - for (walk = fds; *walk != -1; walk++) - close(*walk); - - for (i = 0; i < pool_config->num_init_children; i++) - { - pid_t pid = process_info[i].pid; - - if (pid != 0) - { - kill(pid, sig); - process_info[i].pid = 0; - } - } - - for (i = 0; i < MAX_NUM_BACKENDS; i++) + ereport(LOG, + (errmsg("shutting down"))); + /* Close listen socket if they are already initialized */ + if (fds) { - if (health_check_pids[i] != 0) - { - kill(health_check_pids[i], sig); - health_check_pids[i] = 0; - } + for (walk = fds; *walk != -1; walk++) + close(*walk); } - if (pcp_pid != 0) - kill(pcp_pid, sig); - pcp_pid = 0; - - if (worker_pid != 0) - kill(worker_pid, sig); - worker_pid = 0; - - if (pool_config->use_watchdog) - { - if (watchdog_pid != 0) - kill(watchdog_pid, sig); - watchdog_pid = 0; + ereport(LOG, + (errmsg("terminating all child processes"))); + terminate_all_childrens(sig); - if (wd_lifecheck_pid != 0) - kill(wd_lifecheck_pid, sig); - wd_lifecheck_pid = 0; - } POOL_SETMASK(&UnBlockSig); - do - { - int ret_pid; - - wpid = waitpid(-1, &ret_pid, 0); - } while (wpid > 0 || (wpid == -1 && errno == EINTR)); - + ereport(LOG, + (errmsg("Pgpool-II system is shutdown"))); process_info = NULL; exit(0); } @@ -2207,6 +2219,8 @@ process_name_from_pid(pid_t pid) else if (pid == wd_lifecheck_pid) return "watchdog lifecheck"; } + if (pid == pgpool_logger_pid) + return "pgpool log collector"; return "child"; } @@ -2329,6 +2343,15 @@ reaper(void) else worker_pid = 0; } + else if (pid == pgpool_logger_pid) + { + if (restart_child) + { + pgpool_logger_pid = SysLogger_Start(); + } + else + pgpool_logger_pid = 0; + } /* exiting process was watchdog process */ else if (pool_config->use_watchdog) @@ -3229,7 +3252,7 @@ fork_follow_child(int old_master, int new_primary, int old_primary) { on_exit_reset(); processType = PT_FOLLOWCHILD; - + myProcPid = getpid(); ereport(LOG, (errmsg("start triggering follow command."))); for (i = 0; i < pool_config->backend_desc->num_backends; i++) @@ -3714,8 +3737,13 @@ system_will_go_down(int code, Datum arg) * Terminate all childrens. But we may already have killed all the * childrens if we come to this function because of shutdown signal. */ + if (processState != EXITING) - terminate_all_childrens(); + { + ereport(LOG, + (errmsg("shutting down"))); + terminate_all_childrens(SIGINT); + } processState = EXITING; POOL_SETMASK(&UnBlockSig); diff --git a/src/main/pool_globals.c b/src/main/pool_globals.c index 517701bd3..45a36750a 100644 --- a/src/main/pool_globals.c +++ b/src/main/pool_globals.c @@ -23,5 +23,6 @@ #include "pool.h" pid_t mypid; /* pgpool parent process id */ +pid_t myProcPid; /* process pid */ ProcessType processType; ProcessState processState; diff --git a/src/pcp_con/pcp_child.c b/src/pcp_con/pcp_child.c index 8ca00fa51..d60acc00b 100644 --- a/src/pcp_con/pcp_child.c +++ b/src/pcp_con/pcp_child.c @@ -280,6 +280,7 @@ start_pcp_command_processor_process(int port) /* Close the listen sockets sockets */ close(pcp_unix_fd); close(pcp_inet_fd); + myProcPid = getpid(); /* call PCP child main */ if (pcp_worker_children) list_free(pcp_worker_children); diff --git a/src/sample/pgpool.conf.sample-logical b/src/sample/pgpool.conf.sample-logical index 6ea8c2a22..dff0276d3 100644 --- a/src/sample/pgpool.conf.sample-logical +++ b/src/sample/pgpool.conf.sample-logical @@ -164,7 +164,7 @@ max_pool = 4 # - Life time - -child_life_time = 300 +child_life_time = 5min # Pool exits after being idle for this many seconds child_max_connections = 0 # Pool exits after receiving that many connections @@ -247,6 +247,38 @@ syslog_ident = 'pgpool' # fatal # panic +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_log' # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. + #------------------------------------------------------------------------------ # FILE LOCATIONS #------------------------------------------------------------------------------ diff --git a/src/sample/pgpool.conf.sample-raw b/src/sample/pgpool.conf.sample-raw index 16b14d184..6074fa4ab 100644 --- a/src/sample/pgpool.conf.sample-raw +++ b/src/sample/pgpool.conf.sample-raw @@ -165,7 +165,7 @@ max_pool = 4 # - Life time - -child_life_time = 300 +child_life_time = 5min # Pool exits after being idle for this many seconds child_max_connections = 0 # Pool exits after receiving that many connections @@ -248,6 +248,38 @@ syslog_ident = 'pgpool' # fatal # panic +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_log' # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. + #------------------------------------------------------------------------------ # FILE LOCATIONS #------------------------------------------------------------------------------ @@ -538,7 +570,7 @@ client_idle_limit_in_recovery = 0 auto_failback = off # Dettached backend node reattach automatically # if replication_state is 'streaming'. -auto_failback_interval = 60 +auto_failback_interval = 1min # Min interval of executing auto_failback in # seconds. @@ -802,7 +834,7 @@ memqcache_memcached_port = 11211 # Memcached port number. Mondatory if memqcache_method = 'memcached'. # Defaults to 11211. # (change requires restart) -memqcache_total_size = 67108864 +memqcache_total_size = 64MB # Total memory size in bytes for storing memory cache. # Mandatory if memqcache_method = 'shmem'. # Defaults to 64MB. @@ -822,11 +854,11 @@ memqcache_auto_cache_invalidation = on # DDL/DML/DCL(and memqcache_expire). If off, it is only triggered # by memqcache_expire. on by default. # (change requires restart) -memqcache_maxcache = 409600 +memqcache_maxcache = 400kB # Maximum SELECT result size in bytes. # Must be smaller than memqcache_cache_block_size. Defaults to 400KB. # (change requires restart) -memqcache_cache_block_size = 1048576 +memqcache_cache_block_size = 1MB # Cache block size in bytes. Mandatory if memqcache_method = 'shmem'. # Defaults to 1MB. # (change requires restart) diff --git a/src/sample/pgpool.conf.sample-replication b/src/sample/pgpool.conf.sample-replication index 5cd9d2fc9..2734ed41d 100644 --- a/src/sample/pgpool.conf.sample-replication +++ b/src/sample/pgpool.conf.sample-replication @@ -160,7 +160,7 @@ max_pool = 4 # - Life time - -child_life_time = 300 +child_life_time = 5min # Pool exits after being idle for this many seconds child_max_connections = 0 # Pool exits after receiving that many connections @@ -243,6 +243,38 @@ syslog_ident = 'pgpool' # fatal # panic +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_log' # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. + #------------------------------------------------------------------------------ # FILE LOCATIONS #------------------------------------------------------------------------------ diff --git a/src/sample/pgpool.conf.sample-slony b/src/sample/pgpool.conf.sample-slony index 1548c8b7b..701f87384 100644 --- a/src/sample/pgpool.conf.sample-slony +++ b/src/sample/pgpool.conf.sample-slony @@ -161,7 +161,7 @@ max_pool = 4 # - Life time - -child_life_time = 300 +child_life_time = 5min # Pool exits after being idle for this many seconds child_max_connections = 0 # Pool exits after receiving that many connections @@ -244,6 +244,38 @@ syslog_ident = 'pgpool' # fatal # panic +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_log' # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. + #------------------------------------------------------------------------------ # FILE LOCATIONS #------------------------------------------------------------------------------ @@ -503,7 +535,7 @@ detach_false_primary = off # mode and with PostgreSQL 9.6 or # after. -search_primary_node_timeout = 300 +search_primary_node_timeout = 5min # Timeout in seconds to search for the # primary node when a failover occurs. # 0 means no timeout, keep searching @@ -512,7 +544,7 @@ search_primary_node_timeout = 300 auto_failback = off # Dettached backend node reattach automatically # if replication_state is 'streaming'. -auto_failback_interval = 60 +auto_failback_interval = 1min # Min interval of executing auto_failback in # seconds. @@ -803,7 +835,7 @@ memqcache_memcached_port = 11211 # Memcached port number. Mondatory if memqcache_method = 'memcached'. # Defaults to 11211. # (change requires restart) -memqcache_total_size = 67108864 +memqcache_total_size = 64MB # Total memory size in bytes for storing memory cache. # Mandatory if memqcache_method = 'shmem'. # Defaults to 64MB. @@ -823,11 +855,11 @@ memqcache_auto_cache_invalidation = on # DDL/DML/DCL(and memqcache_expire). If off, it is only triggered # by memqcache_expire. on by default. # (change requires restart) -memqcache_maxcache = 409600 +memqcache_maxcache = 400kB # Maximum SELECT result size in bytes. # Must be smaller than memqcache_cache_block_size. Defaults to 400KB. # (change requires restart) -memqcache_cache_block_size = 1048576 +memqcache_cache_block_size = 1MB # Cache block size in bytes. Mandatory if memqcache_method = 'shmem'. # Defaults to 1MB. # (change requires restart) diff --git a/src/sample/pgpool.conf.sample-snapshot b/src/sample/pgpool.conf.sample-snapshot index b7c7d2930..b84a8f05b 100644 --- a/src/sample/pgpool.conf.sample-snapshot +++ b/src/sample/pgpool.conf.sample-snapshot @@ -160,7 +160,7 @@ max_pool = 4 # - Life time - -child_life_time = 300 +child_life_time = 5min # Pool exits after being idle for this many seconds child_max_connections = 0 # Pool exits after receiving that many connections @@ -243,6 +243,38 @@ syslog_ident = 'pgpool' # fatal # panic +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_log' # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. + #------------------------------------------------------------------------------ # FILE LOCATIONS #------------------------------------------------------------------------------ @@ -512,7 +544,7 @@ detach_false_primary = off # mode and with PostgreSQL 9.6 or # after. -search_primary_node_timeout = 300 +search_primary_node_timeout = 5min # Timeout in seconds to search for the # primary node when a failover occurs. # 0 means no timeout, keep searching @@ -521,7 +553,7 @@ search_primary_node_timeout = 300 auto_failback = off # Dettached backend node reattach automatically # if replication_state is 'streaming'. -auto_failback_interval = 60 +auto_failback_interval = 1min # Min interval of executing auto_failback in # seconds. @@ -815,7 +847,7 @@ memqcache_memcached_port = 11211 # Memcached port number. Mondatory if memqcache_method = 'memcached'. # Defaults to 11211. # (change requires restart) -memqcache_total_size = 67108864 +memqcache_total_size = 64MB # Total memory size in bytes for storing memory cache. # Mandatory if memqcache_method = 'shmem'. # Defaults to 64MB. @@ -835,11 +867,11 @@ memqcache_auto_cache_invalidation = on # DDL/DML/DCL(and memqcache_expire). If off, it is only triggered # by memqcache_expire. on by default. # (change requires restart) -memqcache_maxcache = 409600 +memqcache_maxcache = 400kB # Maximum SELECT result size in bytes. # Must be smaller than memqcache_cache_block_size. Defaults to 400KB. # (change requires restart) -memqcache_cache_block_size = 1048576 +memqcache_cache_block_size = 1MB # Cache block size in bytes. Mandatory if memqcache_method = 'shmem'. # Defaults to 1MB. # (change requires restart) diff --git a/src/sample/pgpool.conf.sample-stream b/src/sample/pgpool.conf.sample-stream index e09aeaebc..4d7b3ab85 100644 --- a/src/sample/pgpool.conf.sample-stream +++ b/src/sample/pgpool.conf.sample-stream @@ -165,7 +165,7 @@ max_pool = 4 # - Life time - -child_life_time = 300 +child_life_time = 5min # Pool exits after being idle for this many seconds child_max_connections = 0 # Pool exits after receiving that many connections @@ -248,6 +248,37 @@ syslog_ident = 'pgpool' # fatal # panic +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_log' # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. #------------------------------------------------------------------------------ # FILE LOCATIONS #------------------------------------------------------------------------------ diff --git a/src/test/regression/tests/010.rewrite_timestamp/timestamp/Makefile b/src/test/regression/tests/010.rewrite_timestamp/timestamp/Makefile index f79ba0ea2..658ad6b3f 100644 --- a/src/test/regression/tests/010.rewrite_timestamp/timestamp/Makefile +++ b/src/test/regression/tests/010.rewrite_timestamp/timestamp/Makefile @@ -7,6 +7,7 @@ CC=gcc OBJS=main.o \ $(topsrc_dir)/utils/strlcpy.o \ $(topsrc_dir)/utils/psprintf.o \ + $(topsrc_dir)/main/pool_globals.o \ $(topsrc_dir)/rewrite/pool_timestamp.o \ $(topsrc_dir)/parser/libsql-parser.a @@ -15,6 +16,7 @@ all: all-pre $(PROGRAM) all-pre: $(MAKE) -C $(topsrc_dir)/utils strlcpy.o $(MAKE) -C $(topsrc_dir)/utils psprintf.o + $(MAKE) -C $(topsrc_dir)/main pool_globals.o $(MAKE) -C $(topsrc_dir)/rewrite pool_timestamp.o $(MAKE) -C $(topsrc_dir)/parser diff --git a/src/test/regression/tests/010.rewrite_timestamp/timestamp/main.c b/src/test/regression/tests/010.rewrite_timestamp/timestamp/main.c index d8dba4ba5..71069be49 100644 --- a/src/test/regression/tests/010.rewrite_timestamp/timestamp/main.c +++ b/src/test/regression/tests/010.rewrite_timestamp/timestamp/main.c @@ -17,7 +17,7 @@ POOL_REQUEST_INFO *Req_info = &_req_info; POOL_CONFIG _pool_config; POOL_CONFIG *pool_config = &_pool_config; ProcessType processType; - +bool redirection_done = false; typedef struct { char *attrname; /* attribute name */ diff --git a/src/tools/pcp/Makefile.in b/src/tools/pcp/Makefile.in index 3f95cac89..9ef63ed3e 100644 --- a/src/tools/pcp/Makefile.in +++ b/src/tools/pcp/Makefile.in @@ -83,7 +83,8 @@ bin_PROGRAMS = pcp_stop_pgpool$(EXEEXT) pcp_node_count$(EXEEXT) \ pcp_proc_count$(EXEEXT) pcp_proc_info$(EXEEXT) \ pcp_detach_node$(EXEEXT) pcp_attach_node$(EXEEXT) \ pcp_recovery_node$(EXEEXT) pcp_promote_node$(EXEEXT) \ - pcp_pool_status$(EXEEXT) pcp_watchdog_info$(EXEEXT) + pcp_pool_status$(EXEEXT) pcp_watchdog_info$(EXEEXT) \ + pcp_reload_config$(EXEEXT) subdir = src/tools/pcp DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ $(top_srcdir)/mkinstalldirs @@ -141,6 +142,9 @@ pcp_promote_node_DEPENDENCIES = $(libs_dir)/pcp/libpcp.la am_pcp_recovery_node_OBJECTS = $(am__objects_1) pcp_recovery_node_OBJECTS = $(am_pcp_recovery_node_OBJECTS) pcp_recovery_node_DEPENDENCIES = $(libs_dir)/pcp/libpcp.la +am_pcp_reload_config_OBJECTS = $(am__objects_1) +pcp_reload_config_OBJECTS = $(am_pcp_reload_config_OBJECTS) +pcp_reload_config_DEPENDENCIES = $(libs_dir)/pcp/libpcp.la am_pcp_stop_pgpool_OBJECTS = $(am__objects_1) pcp_stop_pgpool_OBJECTS = $(am_pcp_stop_pgpool_OBJECTS) pcp_stop_pgpool_DEPENDENCIES = $(libs_dir)/pcp/libpcp.la @@ -185,13 +189,15 @@ SOURCES = $(pcp_attach_node_SOURCES) $(pcp_detach_node_SOURCES) \ $(pcp_node_info_SOURCES) $(pcp_pool_status_SOURCES) \ $(pcp_proc_count_SOURCES) $(pcp_proc_info_SOURCES) \ $(pcp_promote_node_SOURCES) $(pcp_recovery_node_SOURCES) \ - $(pcp_stop_pgpool_SOURCES) $(pcp_watchdog_info_SOURCES) + $(pcp_reload_config_SOURCES) $(pcp_stop_pgpool_SOURCES) \ + $(pcp_watchdog_info_SOURCES) DIST_SOURCES = $(pcp_attach_node_SOURCES) $(pcp_detach_node_SOURCES) \ $(pcp_health_check_stats_SOURCES) $(pcp_node_count_SOURCES) \ $(pcp_node_info_SOURCES) $(pcp_pool_status_SOURCES) \ $(pcp_proc_count_SOURCES) $(pcp_proc_info_SOURCES) \ $(pcp_promote_node_SOURCES) $(pcp_recovery_node_SOURCES) \ - $(pcp_stop_pgpool_SOURCES) $(pcp_watchdog_info_SOURCES) + $(pcp_reload_config_SOURCES) $(pcp_stop_pgpool_SOURCES) \ + $(pcp_watchdog_info_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -381,6 +387,8 @@ pcp_promote_node_SOURCES = $(client_sources) pcp_promote_node_LDADD = $(libs_dir)/pcp/libpcp.la pcp_watchdog_info_SOURCES = $(client_sources) pcp_watchdog_info_LDADD = $(libs_dir)/pcp/libpcp.la +pcp_reload_config_SOURCES = $(client_sources) +pcp_reload_config_LDADD = $(libs_dir)/pcp/libpcp.la all: all-am .SUFFIXES: @@ -516,6 +524,10 @@ pcp_recovery_node$(EXEEXT): $(pcp_recovery_node_OBJECTS) $(pcp_recovery_node_DEP @rm -f pcp_recovery_node$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pcp_recovery_node_OBJECTS) $(pcp_recovery_node_LDADD) $(LIBS) +pcp_reload_config$(EXEEXT): $(pcp_reload_config_OBJECTS) $(pcp_reload_config_DEPENDENCIES) $(EXTRA_pcp_reload_config_DEPENDENCIES) + @rm -f pcp_reload_config$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(pcp_reload_config_OBJECTS) $(pcp_reload_config_LDADD) $(LIBS) + pcp_stop_pgpool$(EXEEXT): $(pcp_stop_pgpool_OBJECTS) $(pcp_stop_pgpool_DEPENDENCIES) $(EXTRA_pcp_stop_pgpool_DEPENDENCIES) @rm -f pcp_stop_pgpool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pcp_stop_pgpool_OBJECTS) $(pcp_stop_pgpool_LDADD) $(LIBS) diff --git a/src/utils/error/elog.c b/src/utils/error/elog.c index b7b761081..0c8f2c167 100644 --- a/src/utils/error/elog.c +++ b/src/utils/error/elog.c @@ -69,6 +69,7 @@ #include <stdarg.h> #include <errno.h> #include <sys/fcntl.h> +#include "main/pgpool_logger.h" #include "utils/elog.h" #include "utils/memutils.h" #include "pool_config.h" @@ -115,7 +116,6 @@ ErrorContextCallback *error_context_stack = NULL; sigjmp_buf *PG_exception_stack = NULL; -extern bool redirection_done; /* * Hook for intercepting messages before they are sent to the server log. @@ -148,6 +148,7 @@ static void write_syslog(int level, const char *line); static void send_message_to_server_log(ErrorData *edata); static void send_message_to_frontend(ErrorData *edata); +static void write_pipe_chunks(char *data, int len, int dest); static void write_console(const char *line, int len); static void log_line_prefix(StringInfo buf, const char *line_prefix, ErrorData *edata); static const char *process_log_prefix_padding(const char *p, int *ppadding); @@ -1748,6 +1749,57 @@ write_console(const char *line, int len) (void) rc; } +/* + * Send data to the syslogger using the chunked protocol + * + * Note: when there are multiple backends writing into the syslogger pipe, + * it's critical that each write go into the pipe indivisibly, and not + * get interleaved with data from other processes. Fortunately, the POSIX + * spec requires that writes to pipes be atomic so long as they are not + * more than PIPE_BUF bytes long. So we divide long messages into chunks + * that are no more than that length, and send one chunk per write() call. + * The collector process knows how to reassemble the chunks. + * + * Because of the atomic write requirement, there are only two possible + * results from write() here: -1 for failure, or the requested number of + * bytes. There is not really anything we can do about a failure; retry would + * probably be an infinite loop, and we can't even report the error usefully. + * (There is noplace else we could send it!) So we might as well just ignore + * the result from write(). However, on some platforms you get a compiler + * warning from ignoring write()'s result, so do a little dance with casting + * rc to void to shut up the compiler. + */ +static void +write_pipe_chunks(char *data, int len, int dest) +{ + PipeProtoChunk p; + int fd = fileno(stderr); + int rc; + + Assert(len > 0); + + p.proto.nuls[0] = p.proto.nuls[1] = '\0'; + p.proto.pid = myProcPid; + + /* write all but the last chunk */ + while (len > PIPE_MAX_PAYLOAD) + { + p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'F' : 'f'); + p.proto.len = PIPE_MAX_PAYLOAD; + memcpy(p.proto.data, data, PIPE_MAX_PAYLOAD); + rc = write(fd, &p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD); + (void) rc; + data += PIPE_MAX_PAYLOAD; + len -= PIPE_MAX_PAYLOAD; + } + + /* write the last chunk */ + p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'T' : 't'); + p.proto.len = len; + memcpy(p.proto.data, data, len); + rc = write(fd, &p, PIPE_HEADER_SIZE + len); + (void) rc; +} /* * Write error report to frontend log @@ -1944,7 +1996,6 @@ log_line_prefix(StringInfo buf, const char *line_prefix, ErrorData *edata) static int log_my_pid = 0; int padding; const char *p; - int MyProcPid = getpid(); POOL_CONNECTION *frontend = NULL; POOL_SESSION_CONTEXT *session = pool_get_session_context(true); @@ -1958,10 +2009,10 @@ log_line_prefix(StringInfo buf, const char *line_prefix, ErrorData *edata) * MyProcPid changes. MyStartTime also changes when MyProcPid does, so * reset the formatted start timestamp too. */ - if (log_my_pid != MyProcPid) + if (log_my_pid != myProcPid) { log_line_number = 0; - log_my_pid = MyProcPid; + log_my_pid = myProcPid; } log_line_number++; @@ -2059,9 +2110,9 @@ log_line_prefix(StringInfo buf, const char *line_prefix, ErrorData *edata) break; case 'p': if (padding != 0) - appendStringInfo(buf, "%*d", padding, MyProcPid); + appendStringInfo(buf, "%*d", padding, myProcPid); else - appendStringInfo(buf, "%d", MyProcPid); + appendStringInfo(buf, "%d", myProcPid); break; case 'l': if (padding != 0) @@ -2215,7 +2266,16 @@ send_message_to_server_log(ErrorData *edata) if (pool_config->log_destination & LOG_DESTINATION_STDERR) { - write_console(buf.data, buf.len); + /* + * Use the chunking protocol if we know the syslogger should be + * catching stderr output, and we are not ourselves the syslogger. + * Otherwise, just do a vanilla write to stderr. + */ + + if (redirection_done && processType != PT_LOGGER) + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR); + else + write_console(buf.data, buf.len); } pfree(buf.data); } diff --git a/src/watchdog/watchdog.c b/src/watchdog/watchdog.c index 1e15bb1c2..ab6e7d8c6 100644 --- a/src/watchdog/watchdog.c +++ b/src/watchdog/watchdog.c @@ -1101,6 +1101,7 @@ fork_watchdog_child(void) /* Set the process type variable */ processType = PT_WATCHDOG; + myProcPid = getpid(); set_application_name(processType); /* call watchdog child main */ diff --git a/src/watchdog/wd_escalation.c b/src/watchdog/wd_escalation.c index 9b9105aad..7e8760432 100644 --- a/src/watchdog/wd_escalation.c +++ b/src/watchdog/wd_escalation.c @@ -74,6 +74,7 @@ fork_escalation_process(void) } on_exit_reset(); processType = PT_WATCHDOG_UTILITY; + myProcPid = getpid(); set_application_name(processType); POOL_SETMASK(&UnBlockSig); @@ -162,6 +163,7 @@ fork_plunging_process(void) return pid; } on_exit_reset(); + myProcPid = getpid(); processType = PT_WATCHDOG_UTILITY; set_application_name(processType); diff --git a/src/watchdog/wd_heartbeat.c b/src/watchdog/wd_heartbeat.c index 11e281501..a6ed950cd 100644 --- a/src/watchdog/wd_heartbeat.c +++ b/src/watchdog/wd_heartbeat.c @@ -365,6 +365,7 @@ wd_hb_receiver(int fork_wait_time, WdHbIf * hb_if) on_exit_reset(); processType = PT_HB_RECEIVER; + myProcPid = getpid(); set_application_name(processType); if (fork_wait_time > 0) @@ -498,6 +499,7 @@ wd_hb_sender(int fork_wait_time, WdHbIf * hb_if) } on_exit_reset(); + myProcPid = getpid(); processType = PT_HB_SENDER; set_application_name(processType); diff --git a/src/watchdog/wd_if.c b/src/watchdog/wd_if.c index 173766583..0c68a08c8 100644 --- a/src/watchdog/wd_if.c +++ b/src/watchdog/wd_if.c @@ -313,6 +313,7 @@ exec_if_cmd(char *path, char *command) on_exit_reset(); processType = PT_WATCHDOG_UTILITY; set_application_name(processType); + myProcPid = getpid(); close(STDOUT_FILENO); dup2(pfd[1], STDOUT_FILENO); close(pfd[0]); diff --git a/src/watchdog/wd_lifecheck.c b/src/watchdog/wd_lifecheck.c index 12653934a..211ca9c2f 100644 --- a/src/watchdog/wd_lifecheck.c +++ b/src/watchdog/wd_lifecheck.c @@ -359,6 +359,7 @@ fork_lifecheck_child(void) /* Set the process type variable */ processType = PT_LIFECHECK; + myProcPid = getpid(); set_application_name(processType); /* call lifecheck child main */ diff --git a/src/watchdog/wd_ping.c b/src/watchdog/wd_ping.c index 2113e4e97..760166b9c 100644 --- a/src/watchdog/wd_ping.c +++ b/src/watchdog/wd_ping.c @@ -135,6 +135,7 @@ wd_issue_ping_command(char *hostname, int *outfd) /* CHILD */ on_exit_reset(); processType = PT_WATCHDOG_UTILITY; + myProcPid = getpid(); set_application_name(processType); close(STDOUT_FILENO); dup2(pfd[1], STDOUT_FILENO); [parent: b7ac3f76ad10]