From 8408f65252c23040e766b6a3ecde7bff390e2847 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Thu, 2 Dec 2004 15:32:54 +0000
Subject: [PATCH] Rework libpq threaded SIGPIPE handling to avoid interference
 with calling applications.  This is done by blocking sigpipe in the libpq
 thread and using sigpending/sigwait to possibily discard any sigpipe we
 generated.

---
 configure                         |  12 +++
 configure.in                      |   9 +-
 doc/src/sgml/libpq.sgml           |  20 +----
 doc/src/sgml/ref/copy.sgml        |   3 +-
 src/interfaces/libpq/fe-connect.c |  11 +--
 src/interfaces/libpq/fe-print.c   |  14 +++-
 src/interfaces/libpq/fe-secure.c  | 132 +++++++++++++++++-------------
 src/interfaces/libpq/libpq-fe.h   |   8 +-
 src/interfaces/libpq/libpq-int.h  |  13 +--
 9 files changed, 115 insertions(+), 107 deletions(-)

diff --git a/configure b/configure
index 009714367fc..378cd612910 100755
--- a/configure
+++ b/configure
@@ -17431,6 +17431,18 @@ _ACEOF
 fi
 HAVE_POSIX_SIGNALS=$pgac_cv_func_posix_signals
 
+if test "$pgac_cv_func_posix_signals" != yes -a "$enable_thread_safety" = yes; then
+  { { echo "$as_me:$LINENO: error:
+*** Thread-safety requires POSIX signals, which are not supported by your
+*** operating system.
+" >&5
+echo "$as_me: error:
+*** Thread-safety requires POSIX signals, which are not supported by your
+*** operating system.
+" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
 if test $ac_cv_func_fseeko = yes; then
 # Check whether --enable-largefile or --disable-largefile was given.
 if test "${enable_largefile+set}" = set; then
diff --git a/configure.in b/configure.in
index ed65d5fec1e..cbb2279ffec 100644
--- a/configure.in
+++ b/configure.in
@@ -1,5 +1,5 @@
 dnl Process this file with autoconf to produce a configure script.
-dnl $PostgreSQL: pgsql/configure.in,v 1.387 2004/11/30 06:13:04 tgl Exp $
+dnl $PostgreSQL: pgsql/configure.in,v 1.388 2004/12/02 15:32:50 momjian Exp $
 dnl
 dnl Developers, please strive to achieve this order:
 dnl
@@ -1174,6 +1174,13 @@ AC_CHECK_TYPES(sig_atomic_t, [], [], [#include <signal.h>])
 
 
 PGAC_FUNC_POSIX_SIGNALS
+if test "$pgac_cv_func_posix_signals" != yes -a "$enable_thread_safety" = yes; then
+  AC_MSG_ERROR([
+*** Thread-safety requires POSIX signals, which are not supported by your
+*** operating system.
+])
+fi
+
 if test $ac_cv_func_fseeko = yes; then
 AC_SYS_LARGEFILE
 fi
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index c47c33d56e2..cfcc7c96562 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.169 2004/11/27 21:56:04 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.170 2004/12/02 15:32:52 momjian Exp $
 -->
 
  <chapter id="libpq">
@@ -3954,24 +3954,6 @@ safety</></> It is better to use the <literal>md5</literal> method,
 which is thread-safe on all platforms.
 </para>
 
-<para>
-<application>libpq</application> must ignore <literal>SIGPIPE</> signals
-generated internally by <function>send()</> calls to backend processes.
-When <productname>PostgreSQL</> is configured without
-<literal>--enable-thread-safety</>, <application>libpq</> sets
-<literal>SIGPIPE</> to <literal>SIG_IGN</> before each
-<function>send()</> call and restores the original signal handler after
-completion. When <literal>--enable-thread-safety</> is used,
-<application>libpq</> installs its own <literal>SIGPIPE</> handler
-before the first database connection.  This handler uses thread-local
-storage to determine if a <literal>SIGPIPE</> signal has been generated
-by a libpq <function>send()</>. If an application wants to install
-its own <literal>SIGPIPE</> signal handler, it should call
-<function>PQinSend()</> to determine if it should ignore the
-<literal>SIGPIPE</> signal. This function is available in both
-thread-safe and non-thread-safe versions of <application>libpq</>.
-</para>
-
 <para>
 If you experience problems with threaded applications, run
 the program in <filename>src/tools/thread</> to see if your
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index d9a0130333f..96e0caecb87 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -1,8 +1,9 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.60 2004/11/27 21:56:05 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.61 2004/12/02 15:32:53 momjian Exp $
 PostgreSQL documentation
 -->
 
+
 <refentry id="SQL-COPY">
  <refmeta>
   <refentrytitle id="sql-copy-title">COPY</refentrytitle>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 6da2c79af4a..30e56b4b6eb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.290 2004/12/01 23:42:26 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.291 2004/12/02 15:32:54 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -866,15 +866,6 @@ connectDBStart(PGconn *conn)
 	const char *node = NULL;
 	int			ret;
 
-#ifdef ENABLE_THREAD_SAFETY
-#ifndef WIN32
-	static pthread_once_t check_sigpipe_once = PTHREAD_ONCE_INIT;
-
-	/* Check only on first connection request */
-	pthread_once(&check_sigpipe_once, pq_check_sigpipe_handler);
-#endif
-#endif
-
 	if (!conn)
 		return 0;
 
diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c
index 832af2b3ac0..9992de533d7 100644
--- a/src/interfaces/libpq/fe-print.c
+++ b/src/interfaces/libpq/fe-print.c
@@ -10,7 +10,7 @@
  * didn't really belong there.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-print.c,v 1.55 2004/11/09 15:57:57 petere Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-print.c,v 1.56 2004/12/02 15:32:54 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,7 +91,11 @@ PQprint(FILE *fout,
 		int			total_line_length = 0;
 		int			usePipe = 0;
 		char	   *pagerenv;
-
+#ifdef ENABLE_THREAD_SAFETY
+		sigset_t	osigset;
+		bool		sigpipe_masked = false;
+		bool		sigpipe_pending;
+#endif
 #if !defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
 		pqsigfunc	oldsigpipehandler = NULL;
 #endif
@@ -189,7 +193,8 @@ PQprint(FILE *fout,
 				{
 					usePipe = 1;
 #ifdef ENABLE_THREAD_SAFETY
-					pthread_setspecific(pq_thread_in_send, "t");
+					pq_block_sigpipe(&osigset, &sigpipe_pending);
+					sigpipe_masked = true;
 #else
 #ifndef WIN32
 					oldsigpipehandler = pqsignal(SIGPIPE, SIG_IGN);
@@ -311,7 +316,8 @@ PQprint(FILE *fout,
 			pclose(fout);
 #endif
 #ifdef ENABLE_THREAD_SAFETY
-			pthread_setspecific(pq_thread_in_send, "f");
+			if (sigpipe_masked)
+				pq_reset_sigpipe(&osigset, sigpipe_pending);
 #else
 #ifndef WIN32
 			pqsignal(SIGPIPE, oldsigpipehandler);
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 6b8d98001d6..79ea192f99c 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.57 2004/11/20 00:35:13 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.58 2004/12/02 15:32:54 momjian Exp $
  *
  * NOTES
  *	  [ Most of these notes are wrong/obsolete, but perhaps not all ]
@@ -152,12 +152,6 @@ bool		pq_initssllib = true;
 static SSL_CTX *SSL_context = NULL;
 #endif
 
-#ifdef ENABLE_THREAD_SAFETY
-static void sigpipe_handler_ignore_send(int signo);
-pthread_key_t pq_thread_in_send = 0;	/* initializer needed on Darwin */
-static pqsigfunc pq_pipe_handler;
-#endif
-
 /* ------------------------------------------------------------ */
 /*						 Hardcoded values						*/
 /* ------------------------------------------------------------ */
@@ -379,9 +373,12 @@ ssize_t
 pqsecure_write(PGconn *conn, const void *ptr, size_t len)
 {
 	ssize_t		n;
-
+	
 #ifdef ENABLE_THREAD_SAFETY
-	pthread_setspecific(pq_thread_in_send, "t");
+	sigset_t	osigmask;
+	bool		sigpipe_pending;
+	
+	pq_block_sigpipe(&osigmask, &sigpipe_pending);
 #else
 #ifndef WIN32
 	pqsigfunc	oldsighandler = pqsignal(SIGPIPE, SIG_IGN);
@@ -452,9 +449,14 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
 	else
 #endif
 		n = send(conn->sock, ptr, len, 0);
+		/*
+		 *	Possible optimization:  if sigpending() turns out to be an
+		 *	expensive operation, we can set sigpipe_pending = 'true'
+		 *	here if errno != EPIPE, avoiding a sigpending call.
+		 */
 
 #ifdef ENABLE_THREAD_SAFETY
-	pthread_setspecific(pq_thread_in_send, "f");
+	pq_reset_sigpipe(&osigmask, sigpipe_pending);
 #else
 #ifndef WIN32
 	pqsignal(SIGPIPE, oldsighandler);
@@ -1216,65 +1218,77 @@ PQgetssl(PGconn *conn)
 }
 #endif   /* USE_SSL */
 
-
 #ifdef ENABLE_THREAD_SAFETY
-#ifndef WIN32
 /*
- *	Check SIGPIPE handler and perhaps install our own.
+ *	Block SIGPIPE for this thread.  This prevents send()/write() from exiting
+ *	the application.
  */
-void
-pq_check_sigpipe_handler(void)
-{
-	pthread_key_create(&pq_thread_in_send, NULL);
-
-	/*
-	 * Find current pipe handler and chain on to it.
-	 */
-	pq_pipe_handler = pqsignalinquire(SIGPIPE);
-	pqsignal(SIGPIPE, sigpipe_handler_ignore_send);
-}
-
-/*
- *	Threaded SIGPIPE signal handler
- */
-void
-sigpipe_handler_ignore_send(int signo)
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
 {
-	/*
-	 * If we have gotten a SIGPIPE outside send(), chain or exit if we are
-	 * at the end of the chain. Synchronous signals are delivered to the
-	 * thread that caused the signal.
-	 */
-	if (!PQinSend())
+	sigset_t sigpipe_sigset;
+	sigset_t sigset;
+	int		 ret;
+	
+	sigemptyset(&sigpipe_sigset);
+	sigaddset(&sigpipe_sigset, SIGPIPE);
+
+	/* Block SIGPIPE and save previous mask for later reset */
+	ret = pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset);
+
+	/* We can have a pending SIGPIPE only if it was blocked before */
+	if (sigismember(osigset, SIGPIPE))
 	{
-		if (pq_pipe_handler == SIG_DFL) /* not set by application */
-			exit(128 + SIGPIPE);	/* typical return value for SIG_DFL */
+		/* Is there a pending SIGPIPE? */
+		if (sigpending(&sigset) != 0)
+			return -1;
+	
+		if (sigismember(&sigset, SIGPIPE))
+			*sigpipe_pending = true;
 		else
-			(*pq_pipe_handler) (signo); /* call original handler */
+			*sigpipe_pending = false;
 	}
+	else
+		*sigpipe_pending = false;
+	
+	return ret;
 }
-#endif
-#endif
-
+	
 /*
- *	Indicates whether the current thread is in send()
- *	For use by SIGPIPE signal handlers;  they should
- *	ignore SIGPIPE when libpq is in send().  This means
- *	that the backend has died unexpectedly.
+ *	Discard any pending SIGPIPE and reset the signal mask.
+ *	We might be discarding a blocked SIGPIPE that we didn't generate,
+ *	but we document that you can't keep blocked SIGPIPE calls across
+ *	libpq function calls.
  */
-pqbool
-PQinSend(void)
+int
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending)
 {
-#ifdef ENABLE_THREAD_SAFETY
-	return (pthread_getspecific(pq_thread_in_send) /* has it been set? */ &&
-			*(char *) pthread_getspecific(pq_thread_in_send) == 't') ? true : false;
-#else
+	int	signo;
+	sigset_t sigset;
 
-	/*
-	 * No threading: our code ignores SIGPIPE around send(). Therefore, we
-	 * can't be in send() if we are checking from a SIGPIPE signal
-	 * handler.
-	 */
-	return false;
-#endif
+	/* Clear SIGPIPE only if none was pending */
+	if (!sigpipe_pending)
+	{
+		if (sigpending(&sigset) != 0)
+			return -1;
+	
+		/*
+		 *	Discard pending and blocked SIGPIPE
+		 */
+		if (sigismember(&sigset, SIGPIPE))
+		{
+			sigset_t sigpipe_sigset;
+			
+			sigemptyset(&sigpipe_sigset);
+			sigaddset(&sigpipe_sigset, SIGPIPE);
+	
+			sigwait(&sigpipe_sigset, &signo);
+			if (signo != SIGPIPE)
+				return -1;
+		}
+	}
+	
+	/* Restore saved block mask */
+	return pthread_sigmask(SIG_SETMASK, osigset, NULL);
 }
+#endif
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 8d73fc8bf59..b472a1ae81c 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.113 2004/10/30 23:11:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.114 2004/12/02 15:32:54 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -497,12 +497,6 @@ extern int	PQenv2encoding(void);
 
 /* === in fe-secure.c === */
 
-/*
- *	Indicates whether the libpq thread is in send().
- *	Used to ignore SIGPIPE if thread is in send().
- */
-extern pqbool PQinSend(void);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 343834f0a20..13b7e3588ce 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.96 2004/10/30 23:11:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.97 2004/12/02 15:32:54 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,7 @@
 
 #ifdef ENABLE_THREAD_SAFETY
 #include <pthread.h>
+#include <signal.h>
 #endif
 
 #ifdef WIN32_CLIENT_ONLY
@@ -475,15 +476,15 @@ extern void pqsecure_close(PGconn *);
 extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
 extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
 
-#ifdef ENABLE_THREAD_SAFETY
-extern void pq_check_sigpipe_handler(void);
-extern pthread_key_t pq_thread_in_send;
-#endif
-
 #ifdef USE_SSL
 extern bool pq_initssllib;
 #endif
 
+#ifdef ENABLE_THREAD_SAFETY
+int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+int pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending);
+#endif
+
 /*
  * this is so that we can check if a connection is non-blocking internally
  * without the overhead of a function call
-- 
GitLab