Browse Source

db_postgres: add new module vars "timeout" and "tcp_keepalive"

The "timeout" option enables a query timeout as well as a connection
timeout, set to the specified number of seconds.

The "tcp_keepalive" option enables the kernel's TCP keepalive packets
on the PGSQL socket and sets the keepalive timer to the specified number
of seconds. Only supported if the platform supports the TCP_KEEPIDLE
socket option.
Richard Fuchs 10 năm trước cách đây
mục cha
commit
6619c0d21d

+ 39 - 0
modules/db_postgres/README

@@ -24,12 +24,16 @@ Greg Fausak
         3. Parameters
 
               3.1. retries (integer)
+              3.2. timeout (integer)
+              3.3. tcp_keepalive (integer)
 
         4. Functions
 
    List of Examples
 
    1.1. Set retries parameter
+   1.2. Set timeout parameter
+   1.3. Set tcp_keepalive parameter
 
 Chapter 1. Admin Guide
 
@@ -44,6 +48,8 @@ Chapter 1. Admin Guide
    3. Parameters
 
         3.1. retries (integer)
+        3.2. timeout (integer)
+        3.3. tcp_keepalive (integer)
 
    4. Functions
 
@@ -73,6 +79,8 @@ Chapter 1. Admin Guide
 3. Parameters
 
    3.1. retries (integer)
+   3.2. timeout (integer)
+   3.3. tcp_keepalive (integer)
 
 3.1. retries (integer)
 
@@ -88,6 +96,37 @@ Chapter 1. Admin Guide
 modparam("db_postgres", "retries", 3)
 ...
 
+3.2. timeout (integer)
+
+   Setting this variable to any value larger than zero (which is the
+   default value) enables both a connection timeout and a query timeout.
+   If a connection attempt or a query takes longer than this many seconds,
+   the operation will be aborted and an error will be returned.
+
+   Note that this timeout is applied to each underlying operation (i.e.
+   for each connection attempt), so depending on circumstances and on the
+   value of the “retries” variable, a single query from the SIP proxy's
+   point of view can take longer than the “timeout”.
+
+   Example 1.2. Set timeout parameter
+...
+modparam("db_postgres", "timeout", 10)
+...
+
+3.3. tcp_keepalive (integer)
+
+   Enable the TCP keepalive timer and set the number of seconds the
+   connection must be idle before to start sending keepalive packets.
+   Defaults to zero, which disables TCP keepalive packets.
+
+   Only supported on platforms which understand and support the
+   “TCP_KEEPIDLE” socket option.
+
+   Example 1.3. Set tcp_keepalive parameter
+...
+modparam("db_postgres", "tcp_keepalive", 600)
+...
+
 4. Functions
 
    NONE

+ 49 - 0
modules/db_postgres/doc/db_postgres_admin.xml

@@ -78,6 +78,55 @@
 ...
 modparam("db_postgres", "retries", 3)
 ...
+</programlisting>
+		</example>
+	</section>
+
+	<section>
+		<title><varname>timeout</varname> (integer)</title>
+		<para>
+			Setting this variable to any value larger than zero (which is the
+			default value) enables both a connection timeout and a query
+			timeout. If a connection attempt or a query takes longer than this
+			many seconds, the operation will be aborted and an error will be
+			returned.
+		</para>
+		<para>
+			Note that this timeout is applied to each underlying operation
+			(i.e. for each connection attempt), so depending on circumstances
+			and on the value of the <quote>retries</quote> variable, a single
+			query from the &sip; proxy's point of view can take longer than the
+			<quote>timeout</quote>.
+		</para>
+		<example>
+		<title>Set <varname>timeout</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_postgres", "timeout", 10)
+...
+</programlisting>
+		</example>
+	</section>
+
+	<section>
+		<title><varname>tcp_keepalive</varname> (integer)</title>
+		<para>
+			Enable the TCP keepalive timer and set the number of seconds the
+			connection must be idle before to start sending keepalive packets.
+			Defaults to zero, which disables TCP keepalive packets.
+		</para>
+		<para>
+		<emphasis>
+			Only supported on platforms which understand and support the
+			<quote>TCP_KEEPIDLE</quote> socket option.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>tcp_keepalive</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_postgres", "tcp_keepalive", 600)
+...
 </programlisting>
 		</example>
 	</section>

+ 43 - 0
modules/db_postgres/km_dbase.c

@@ -167,6 +167,10 @@ static int db_postgres_submit_query(const db1_con_t* _con, const str* _s)
 	int i, retries;
 	ExecStatusType pqresult;
 	PGresult *res = NULL;
+	int sock, ret;
+	fd_set fds;
+	time_t max_time;
+	struct timeval wait_time;
 
 	if(! _con || !_s || !_s->s)
 	{
@@ -217,6 +221,44 @@ static int db_postgres_submit_query(const db1_con_t* _con, const str* _s)
 		/* exec the query */
 
 		if (PQsendQuery(CON_CONNECTION(_con), s)) {
+			if (pg_timeout <= 0)
+				goto do_read;
+
+			max_time = time(NULL) + pg_timeout;
+
+			while (1) {
+				sock = PQsocket(CON_CONNECTION(_con));
+				FD_ZERO(&fds);
+				FD_SET(sock, &fds);
+
+				wait_time.tv_usec = 0;
+				wait_time.tv_sec = max_time - time(NULL);
+				if (wait_time.tv_sec <= 0 || wait_time.tv_sec > 0xffffff)
+					goto timeout;
+
+				ret = select(sock + 1, &fds, NULL, NULL, &wait_time);
+				if (ret < 0) {
+					if (errno == EINTR)
+						continue;
+					LM_WARN("select() error\n");
+					goto reset;
+				}
+				if (!ret) {
+timeout:
+					LM_WARN("timeout waiting for postgres reply\n");
+					goto reset;
+				}
+
+				if (!PQconsumeInput(CON_CONNECTION(_con))) {
+					LM_WARN("error reading data from postgres server: %s\n",
+							PQerrorMessage(CON_CONNECTION(_con)));
+					goto reset;
+				}
+				if (!PQisBusy(CON_CONNECTION(_con)))
+					break;
+			}
+
+do_read:
 			/* Get the result of the query */
 			while ((res = PQgetResult(CON_CONNECTION(_con))) != NULL) {
 				db_postgres_free_query(_con);
@@ -239,6 +281,7 @@ static int db_postgres_submit_query(const db1_con_t* _con, const str* _s)
 				PQerrorMessage(CON_CONNECTION(_con)));
 		if(PQstatus(CON_CONNECTION(_con))!=CONNECTION_OK)
 		{
+reset:
 			LM_DBG("reseting the connection to postgress server\n");
 			PQreset(CON_CONNECTION(_con));
 		}

+ 34 - 2
modules/db_postgres/km_pg_con.c

@@ -26,11 +26,14 @@
  */
 
 #include "km_pg_con.h"
+#include "pg_mod.h"
 #include "../../mem/mem.h"
 #include "../../dprint.h"
 #include "../../ut.h"
 #include <string.h>
 #include <time.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
 
 
 /*!
@@ -45,6 +48,9 @@ struct pg_con* db_postgres_new_connection(struct db_id* id)
 {
 	struct pg_con* ptr;
 	char *ports;
+	int i = 0;
+	const char *keywords[10], *values[10];
+	char to[16];
 
 	LM_DBG("db_id = %p\n", id);
  
@@ -66,6 +72,8 @@ struct pg_con* db_postgres_new_connection(struct db_id* id)
 
 	if (id->port) {
 		ports = int2str(id->port, 0);
+		keywords[i] = "port";
+		values[i++] = ports;
 		LM_DBG("opening connection: postgres://xxxx:xxxx@%s:%d/%s\n", ZSW(id->host),
 			id->port, ZSW(id->database));
 	} else {
@@ -74,8 +82,24 @@ struct pg_con* db_postgres_new_connection(struct db_id* id)
 			ZSW(id->database));
 	}
 
- 	ptr->con = PQsetdbLogin(id->host, ports, NULL, NULL, id->database, id->username, id->password);
-	LM_DBG("PQsetdbLogin(%p)\n", ptr->con);
+	keywords[i] = "host";
+	values[i++] = id->host;
+	keywords[i] = "dbname";
+	values[i++] = id->database;
+	keywords[i] = "user";
+	values[i++] = id->username;
+	keywords[i] = "password";
+	values[i++] = id->password;
+	if (pg_timeout > 0) {
+		snprintf(to, sizeof(to)-1, "%d", pg_timeout + 3);
+		keywords[i] = "connect_timeout";
+		values[i++] = to;
+	}
+
+	keywords[i] = values[i] = NULL;
+
+	ptr->con = PQconnectdbParams(keywords, values, 1);
+	LM_DBG("PQconnectdbParams(%p)\n", ptr->con);
 
 	if( (ptr->con == 0) || (PQstatus(ptr->con) != CONNECTION_OK) )
 	{
@@ -88,6 +112,14 @@ struct pg_con* db_postgres_new_connection(struct db_id* id)
 	ptr->timestamp = time(0);
 	ptr->id = id;
 
+#if defined(SO_KEEPALIVE) && defined(TCP_KEEPIDLE)
+	if (pg_keepalive) {
+		i = 1;
+		setsockopt(PQsocket(ptr->con), SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
+		setsockopt(PQsocket(ptr->con), IPPROTO_TCP, TCP_KEEPIDLE, &pg_keepalive, sizeof(pg_keepalive));
+	}
+#endif
+
 	return ptr;
 
  err:

+ 33 - 5
modules/db_postgres/pg_con.c

@@ -39,6 +39,7 @@
 #include "pg_con.h"
 #include "pg_uri.h"
 #include "pg_sql.h"
+#include "pg_mod.h"
 
 #include "../../mem/mem.h"
 #include "../../dprint.h"
@@ -47,6 +48,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <time.h>
 
 
@@ -237,7 +239,9 @@ int pg_con_connect(db_con_t* con)
 	struct pg_con* pcon;
 	struct pg_uri* puri;
 	char* port_str;
-	int ret;
+	int ret, i = 0;
+	const char *keywords[10], *values[10];
+	char to[16];
 	
 	pcon = DB_GET_PAYLOAD(con);
 	puri = DB_GET_PAYLOAD(con->uri);
@@ -251,6 +255,8 @@ int pg_con_connect(db_con_t* con)
 
 	if (puri->port > 0) {
 		port_str = int2str(puri->port, 0);
+		keywords[i] = "port";
+		values[i++] = port_str;
 	} else {
 		port_str = NULL;
 	}
@@ -260,12 +266,26 @@ int pg_con_connect(db_con_t* con)
 		pcon->con = NULL;
 	}
 
-	pcon->con = PQsetdbLogin(puri->host, port_str,
-							 NULL, NULL, puri->database,
-							 puri->username, puri->password);
+	keywords[i] = "host";
+	values[i++] = puri->host;
+	keywords[i] = "dbname";
+	values[i++] = puri->database;
+	keywords[i] = "user";
+	values[i++] = puri->username;
+	keywords[i] = "password";
+	values[i++] = puri->password;
+	if (pg_timeout > 0) {
+		snprintf(to, sizeof(to)-1, "%d", pg_timeout + 3);
+		keywords[i] = "connect_timeout";
+		values[i++] = to;
+	}
+
+	keywords[i] = values[i] = NULL;
+
+	pcon->con = PQconnectdbParams(keywords, values, 1);
 	
 	if (pcon->con == NULL) {
-		ERR("postgres: PQsetdbLogin ran out of memory\n");
+		ERR("postgres: PQconnectdbParams ran out of memory\n");
 		goto error;
 	}
 	
@@ -285,6 +305,14 @@ int pg_con_connect(db_con_t* con)
 	    PQprotocolVersion(pcon->con), 0 );
 #endif
 
+#if defined(SO_KEEPALIVE) && defined(TCP_KEEPIDLE)
+	if (pg_keepalive) {
+		i = 1;
+		setsockopt(PQsocket(pcon->con), SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
+		setsockopt(PQsocket(pcon->con), IPPROTO_TCP, TCP_KEEPIDLE, &pg_keepalive, sizeof(pg_keepalive));
+	}
+#endif
+
 	ret = timestamp_format(pcon->con);
 	if (ret == 1 || ret == -1) {
 		/* Assume INT8 representation if detection fails */

+ 4 - 0
modules/db_postgres/pg_mod.c

@@ -61,6 +61,8 @@ int pg_retries = 2;  /* How many times should the module try re-execute failed c
 					  * 0 disables reconnecting */
 
 int pg_lockset = 4;
+int pg_timeout = 0; /* default = no timeout */
+int pg_keepalive = 0;
 
 /*
  * Postgres module interface
@@ -92,6 +94,8 @@ static cmd_export_t cmds[] = {
 static param_export_t params[] = {
 	{"retries",         PARAM_INT, &pg_retries },
 	{"lockset",         PARAM_INT, &pg_lockset },
+	{"timeout",         PARAM_INT, &pg_timeout },
+	{"tcp_keepalive",   PARAM_INT, &pg_keepalive },
 	{0, 0, 0}
 };
 

+ 2 - 0
modules/db_postgres/pg_mod.h

@@ -41,6 +41,8 @@
  */
 
 extern int pg_retries;
+extern int pg_timeout;
+extern int pg_keepalive;
 
 /** @} */