On Mon, Jul 09, 2012 at 05:35:15PM +0900, Shigeru HANADA wrote:
> Hi Ryan,
> 
> On Mon, Jun 25, 2012 at 9:00 PM, Ryan Kelly <rpkell...@gmail.com> wrote:
> >> Connection attempt by \connect command could be also canceled by
> >> pressing Ctrl+C on psql prompt.
> >>
> >> In addition, I tried setting PGCONNECT_TIMEOUT to 0 (infinite), but
> >> psql gave up after few seconds, for both start-up and re-connect.
> >> Is this intentional behavior?
> > A timeout of 0 (infinite) means to keep trying until we succeed or fail,
> > not keep trying forever. As you mentioned above, your connection
> > attempts error out after a few seconds. This is what is happening. In my
> > environment no such error occurs and as a result psql continues to
> > attempt to connect for as long as I let it.
> 
> For handy testing, I wrote simple TCP server which accepts connection
> and blocks until client gives up connection attempt (or forever).  When
> I tested your patch with setting PGCONNECT_TIMEOUT=0, I found that
> psql's CPU usage goes up to almost 100% (10~ usr + 80~ sys) while
> connection attempt by \c command is undergoing.  After reading code for
> a while, I found that FD_ZERO was not called.  This makes select() to
> return immediately in the loop every time and caused busy loop.
> # Maybe you've forgotten adding FD_ZERO when you were merging Heikki's
> v2 patch.
Yes, I had lost them somewhere. My apologies.

> >> - Checking status by calling PQstatus just after
> >> PQconnectStartParams is necessary.
> > Yes, I agree.
> 
> Checked.
> 
> >> - Copying only "select()" part of pqSocketPoll seems not enough,
> >> should we use poll(2) if it is supported?
> > I did not think the additional complexity was worth it in this case.
> > Unless you see some reason to use poll(2) that I do not.
> 
> I checked where select() is used in PG, and noticed that poll is used at
> only few places.  Agreed to use only select() here.
> 
> >> - Don't we need to clear error message stored in PGconn after
> >> PQconnectPoll returns OK status, like connectDBComplete?
> > I do not believe there is a client interface for clearing the error
> > message. Additionally, the documentation states that PQerrorMessage
> > "Returns the error message most recently generated by an operation on
> > the connection." This seems to indicate that the error message should be
> > cleared as this behavior is part of the contract of PQerrorMessage.
> 
> My comment was pointless.  Sorry for noise.
> 
> Here is my additional comments for v5 patch:
> 
> - Using static array for fixed-length connection parameters was
> suggested in comments for another CF item, using
> fallback_application_name for some command line tools, and IMO this
> approach would also suit for this patch.
> 
> http://archives.postgresql.org/message-id/CA+TgmoYZiayts=fjsytzqlg7rewlwkdkey5f+fhp5v5_nu_...@mail.gmail.com
I have done this as well.

> - Some comments go past 80 columns, and opening brace in line 1572
> should go on next line.  Please refer coding conventions written in
> PostgreSQL wiki.
> http://www.postgresql.org/docs/devel/static/source-format.html
I have corrected these issues.

> Once the issues above are fixed, IMO this patch can be marked as "Ready
> for committer".
I have also added additional documentation reflecting Heikki's
suggestion that PQconnectTimeout be recommended for use by applications
using the async API.

Attached is v6 of the patch.

> Regards,
> -- 
> Shigeru Hanada
> 
>   * 不明 - 自動検出
>   * 英語
>   * 日本語
> 
>   * 英語
>   * 日本語
> 
>  <javascript:void(0);>

-Ryan Kelly
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 5c5dd68..654f5f5 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -436,8 +436,12 @@ switch(PQstatus(conn))
        The <literal>connect_timeout</literal> connection parameter is ignored
        when using <function>PQconnectPoll</function>; it is the application's
        responsibility to decide whether an excessive amount of time has elapsed.
-       Otherwise, <function>PQconnectStart</function> followed by a
-       <function>PQconnectPoll</function> loop is equivalent to
+       It is recommended to use <function>PQconnectTimeout</function> to get the
+       value of <literal>connect_timeout</literal> and use that as the timeout
+       in the application. That way the user gets the same timeout behavior
+       regardless of whether the application uses <function>PQconnectPoll</function>
+       or the blocking connection API. Otherwise, <function>PQconnectStart</function>
+       followed by a <function>PQconnectPoll</function> loop is equivalent to
        <function>PQconnectdb</function>.
       </para>
 
@@ -1496,6 +1500,24 @@ char *PQoptions(const PGconn *conn);
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry id="libpq-pqconnecttimeout">
+     <term>
+      <function>PQconnectTimeout</function>
+      <indexterm>
+       <primary>PQconnectTimeout</primary>
+      </indexterm>
+     </term>
+
+     <listitem>
+      <para>
+       Returns the connect_timeout property as given to libpq.
+<synopsis>
+char *PQconnectTimeout(const PGconn *conn);
+</synopsis>
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
   </para>
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index c5ec981..d987be8 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1506,7 +1506,7 @@ static bool
 do_connect(char *dbname, char *user, char *host, char *port)
 {
 	PGconn	   *o_conn = pset.db,
-			   *n_conn;
+			   *n_conn = NULL;
 	char	   *password = NULL;
 
 	if (!dbname)
@@ -1539,9 +1539,9 @@ do_connect(char *dbname, char *user, char *host, char *port)
 
 	while (true)
 	{
-#define PARAMS_ARRAY_SIZE	8
-		const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
-		const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
+#define PARAMS_ARRAY_SIZE	9
+		const char *keywords[PARAMS_ARRAY_SIZE];
+		const char *values[PARAMS_ARRAY_SIZE];
 
 		keywords[0] = "host";
 		values[0] = host;
@@ -1557,30 +1557,145 @@ do_connect(char *dbname, char *user, char *host, char *port)
 		values[5] = pset.progname;
 		keywords[6] = "client_encoding";
 		values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
-		keywords[7] = NULL;
-		values[7] = NULL;
+		keywords[7] = "connect_timeout";
+		values[7] = PQconnectTimeout(o_conn);
+		keywords[8] = NULL;
+		values[8] = NULL;
 
-		n_conn = PQconnectdbParams(keywords, values, true);
-
-		free(keywords);
-		free(values);
-
-		/* We can immediately discard the password -- no longer needed */
-		if (password)
-			free(password);
-
-		if (PQstatus(n_conn) == CONNECTION_OK)
-			break;
+		/* attempt connection asynchronously */
+		n_conn = PQconnectStartParams(keywords, values, true);
 
 		/*
-		 * Connection attempt failed; either retry the connection attempt with
-		 * a new password, or give up.
+		 * if we get CONNECTION_BAD, PQconnectStartParams has failed, so don't
+		 * bother even attempting to connect.
 		 */
-		if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
+		if (PQstatus(n_conn) != CONNECTION_BAD)
 		{
-			PQfinish(n_conn);
-			password = prompt_for_password(user);
-			continue;
+			if (sigsetjmp(sigint_interrupt_jmp, 1) != 0)
+			{
+				/* interrupted during connection attempt */
+				PQfinish(n_conn);
+				n_conn = NULL;
+			}
+			else
+			{
+				time_t		end_time = -1;
+
+				/*
+				 * maybe use a connection timeout. this code essentially stolen
+				 * from src/interfaces/libpq/fe-connect.c connectDBComplete
+				 */
+				if (PQconnectTimeout(n_conn) != NULL)
+				{
+					int			timeout = atoi(PQconnectTimeout(n_conn));
+					if (timeout > 0)
+					{
+						/*
+						 * Rounding could cause connection to fail; need at
+						 * least 2 secs
+						 */
+						if (timeout < 2)
+							timeout = 2;
+
+						/*
+						 * calculate the finish time based on start +
+						 * timeout
+						 */
+						end_time = time(NULL) + timeout;
+					}
+				}
+
+				while(end_time < 0 || time(NULL) < end_time)
+				{
+					int			poll_res;
+					int			rc;
+					fd_set		read_mask,
+								write_mask;
+					struct timeval timeout;
+					struct timeval *ptr_timeout;
+
+					poll_res = PQconnectPoll(n_conn);
+					if (poll_res == PGRES_POLLING_OK ||
+						poll_res == PGRES_POLLING_FAILED)
+					{
+						break;
+					}
+
+					FD_ZERO(&read_mask);
+					FD_ZERO(&write_mask);
+
+					if (poll_res == PGRES_POLLING_READING)
+						FD_SET(PQsocket(n_conn), &read_mask);
+					if (poll_res == PGRES_POLLING_WRITING)
+						FD_SET(PQsocket(n_conn), &write_mask);
+
+					/*
+					 * Compute appropriate timeout interval. essentially stolen
+					 * from src/interfaces/libpq/fe-misc.c pqSocketPoll. Maybe
+					 * that function could be made public? we could then
+					 * replace the whole inside of this while loop, assuming it
+					 * is safe to longjmp out from there.
+					 */
+					if (end_time == ((time_t) -1))
+						ptr_timeout = NULL;
+					else
+					{
+						time_t      now = time(NULL);
+
+						if (end_time > now)
+							timeout.tv_sec = end_time - now;
+						else
+							timeout.tv_sec = 0;
+						timeout.tv_usec = 0;
+						ptr_timeout = &timeout;
+					}
+
+					sigint_interrupt_enabled = true;
+					if (cancel_pressed)
+					{
+						PQfinish(n_conn);
+						n_conn = NULL;
+						sigint_interrupt_enabled = false;
+						break;
+					}
+					rc = select(PQsocket(n_conn) + 1,
+								&read_mask, &write_mask, NULL,
+								ptr_timeout);
+					sigint_interrupt_enabled = false;
+
+					if (rc < 0 && errno != EINTR)
+						break;
+				}
+
+				if (PQstatus(n_conn) != CONNECTION_OK &&
+					end_time > 0 && time(NULL) >= end_time)
+				{
+					PQfinish(n_conn);
+					n_conn = NULL;
+					fputs(_("timeout expired\n"), stderr);
+				}
+			}
+
+			/* We can immediately discard the password -- no longer needed */
+			if (password)
+			{
+				free(password);
+				password = NULL;
+			}
+
+			if (PQstatus(n_conn) == CONNECTION_OK)
+				break;
+
+			/*
+			 * Connection attempt failed; either retry the connection attempt
+			 * with a new password, or give up.
+			 */
+			if (PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
+			{
+				PQfinish(n_conn);
+				password = prompt_for_password(user);
+				continue;
+			}
 		}
 
 		/*
@@ -1590,7 +1705,8 @@ do_connect(char *dbname, char *user, char *host, char *port)
 		 */
 		if (pset.cur_cmd_interactive)
 		{
-			psql_error("%s", PQerrorMessage(n_conn));
+			if (n_conn)
+				psql_error("%s", PQerrorMessage(n_conn));
 
 			/* pset.db is left unmodified */
 			if (o_conn)
@@ -1598,7 +1714,9 @@ do_connect(char *dbname, char *user, char *host, char *port)
 		}
 		else
 		{
-			psql_error("\\connect: %s", PQerrorMessage(n_conn));
+			if (n_conn)
+				psql_error("\\connect: %s", PQerrorMessage(n_conn));
+
 			if (o_conn)
 			{
 				PQfinish(o_conn);
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 9a6306b..c3ffea5 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -111,8 +111,6 @@ main(int argc, char *argv[])
 	setvbuf(stderr, NULL, _IONBF, 0);
 #endif
 
-	setup_cancel_handler();
-
 	pset.progname = get_progname(argv[0]);
 
 	pset.db = NULL;
@@ -253,8 +251,9 @@ main(int argc, char *argv[])
 	}
 
 	/*
-	 * Now find something to do
+	 * Now find something to do (and handle cancellation, if applicable)
 	 */
+	setup_cancel_handler();
 
 	/*
 	 * process file given by -f
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 1251455..fe98c7a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -163,3 +163,4 @@ PQlibVersion              160
 PQsetRowProcessor         161
 PQgetRowProcessor         162
 PQskipResult              163
+PQconnectTimeout          164
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a32258a..065b782 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -5161,6 +5161,14 @@ PQoptions(const PGconn *conn)
 	return conn->pgoptions;
 }
 
+char *
+PQconnectTimeout(const PGconn *conn)
+{
+	if (!conn)
+		return NULL;
+	return conn->connect_timeout;
+}
+
 ConnStatusType
 PQstatus(const PGconn *conn)
 {
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 67db611..3ea168d 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -310,6 +310,7 @@ extern char *PQhost(const PGconn *conn);
 extern char *PQport(const PGconn *conn);
 extern char *PQtty(const PGconn *conn);
 extern char *PQoptions(const PGconn *conn);
+extern char *PQconnectTimeout(const PGconn *conn);
 extern ConnStatusType PQstatus(const PGconn *conn);
 extern PGTransactionStatusType PQtransactionStatus(const PGconn *conn);
 extern const char *PQparameterStatus(const PGconn *conn,
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to