Bug #11774 Libmysqlclient read/write timeout options
Submitted: 6 Jul 2005 12:30 Modified: 21 Aug 2006 17:33
Reporter: Nicolas Baradakis Email Updates:
Status: Duplicate Impact on me:
None 
Category:MySQL Server Severity:S3 (Non-critical)
Version:4.1.12 OS:Linux (Linux)
Assigned to: Bugs System CPU Architecture:Any

[6 Jul 2005 12:30] Nicolas Baradakis
Description:
The options MYSQL_OPT_READ_TIMEOUT and MYSQL_OPT_WRITE_TIMEOUT have been added to the mysql_options() function in version 4.1.1 of the libmysqlclient. However, for now they're available for windows only.

I think a timeout is very useful for a program using the MySQL client library. It prevents the application from hanging on an I/O operation when the MySQL server does not respond (heavy load, hardware problem, etc.) That's why I tried to make timeout options available on systems other than windows.

How to repeat:
It's possible to simulate a MySQL server which doesn't answer by running iptables after the connection with the server has been established.

# iptables -A INPUT -p tcp --dport 3306 -j DROP

Suggested fix:
Following patch was made against the latest nightly snapshot available on dev.mysql.com. It works on Linux, and should work on other POSIX systems.

diff -u -r mysql-4.1.13-nightly-20050706.orig/include/violite.h mysql-4.1.13-nightly-20050706/include/violite.h
--- mysql-4.1.13-nightly-20050706.orig/include/violite.h	2005-07-06 13:50:35.176417456 +0200
+++ mysql-4.1.13-nightly-20050706/include/violite.h	2005-07-06 13:51:13.854537488 +0200
@@ -37,6 +37,12 @@
   VIO_TYPE_SSL, VIO_TYPE_SHARED_MEMORY
 };
 
+typedef enum enum_timeout_type
+{
+  VIO_READ_TIMEOUT,
+  VIO_WRITE_TIMEOUT
+} timeout_type;
+
 Vio*	vio_new(my_socket sd, enum enum_vio_type type, my_bool localhost);
 #ifdef __WIN__
 Vio* vio_new_win32pipe(HANDLE hPipe);
@@ -81,7 +87,7 @@
 /* Remotes in_addr */
 void	vio_in_addr(Vio *vio, struct in_addr *in);
 my_bool	vio_poll_read(Vio *vio,uint timeout);
-void	vio_timeout(Vio *vio,uint which, uint timeout);
+void	vio_timeout(Vio *vio, timeout_type which, uint milliseconds);
 
 #ifdef HAVE_OPENSSL
 #include <openssl/opensslv.h>
@@ -149,7 +155,7 @@
 #define vio_close(vio)				((vio)->vioclose)(vio)
 #define vio_peer_addr(vio, buf, prt)		(vio)->peer_addr(vio, buf, prt)
 #define vio_in_addr(vio, in)			(vio)->in_addr(vio, in)
-#define vio_timeout(vio, which, seconds)	(vio)->timeout(vio, which, seconds)
+#define vio_timeout(vio, which, milliseconds)	(vio)->timeout((vio), (which), (milliseconds))
 #endif /* defined(HAVE_VIO) && !defined(DONT_MAP_VIO) */
 
 /* This enumerator is used in parser - should be always visible */
@@ -189,7 +195,7 @@
   void    (*in_addr)(Vio*, struct in_addr*);
   my_bool (*should_retry)(Vio*);
   int     (*vioclose)(Vio*);
-  void	  (*timeout)(Vio*, unsigned int which, unsigned int timeout);
+  void	  (*timeout)(Vio*, timeout_type which, uint milliseconds);
   void	  *ssl_arg;
 #ifdef HAVE_SMEM
   HANDLE  handle_file_map;
diff -u -r mysql-4.1.13-nightly-20050706.orig/sql/net_serv.cc mysql-4.1.13-nightly-20050706/sql/net_serv.cc
--- mysql-4.1.13-nightly-20050706.orig/sql/net_serv.cc	2005-07-06 13:50:39.429770848 +0200
+++ mysql-4.1.13-nightly-20050706/sql/net_serv.cc	2005-07-06 13:51:13.855537336 +0200
@@ -485,13 +485,10 @@
 #endif /* HAVE_COMPRESS */
 
   /* DBUG_DUMP("net",packet,len); */
-#ifndef NO_ALARM
   thr_alarm_init(&alarmed);
+#ifndef NO_ALARM
   if (net_blocking)
     thr_alarm(&alarmed,(uint) net->write_timeout,&alarm_buff);
-#else
-  alarmed=0;
-  vio_timeout(net->vio, 1, net->write_timeout);
 #endif /* NO_ALARM */
 
   pos=(char*) packet; end=pos+len;
@@ -506,9 +503,10 @@
         if (!thr_alarm(&alarmed,(uint) net->write_timeout,&alarm_buff))
         {                                       /* Always true for client */
 	  my_bool old_mode;
+	  uint retry_fcntl = 0;
 	  while (vio_blocking(net->vio, TRUE, &old_mode) < 0)
 	  {
-	    if (vio_should_retry(net->vio) && retry_count++ < net->retry_count)
+	    if (vio_should_retry(net->vio) && retry_fcntl++ < net->retry_count)
 	      continue;
 #ifdef EXTRA_DEBUG
 	    fprintf(stderr,
@@ -522,8 +520,8 @@
             net->report_error= 1;
 	    goto end;
 	  }
-	  retry_count=0;
-	  continue;
+	  if (retry_count++ < net->retry_count)
+	    continue;
 	}
       }
       else
@@ -539,7 +537,7 @@
 #endif /* EXTRA_DEBUG */
       }
 #if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER)
-      if (vio_errno(net->vio) == SOCKET_EINTR)
+      if (interrupted && retry_count++ < net->retry_count)
       {
 	DBUG_PRINT("warning",("Interrupted write. Retrying..."));
 	continue;
@@ -683,8 +681,6 @@
 #ifndef NO_ALARM
   if (net_blocking)
     thr_alarm(&alarmed,net->read_timeout,&alarm_buff);
-#else
-  vio_timeout(net->vio, 0, net->read_timeout);
 #endif /* NO_ALARM */
 
     pos = net->buff + net->where_b;		/* net->packet -4 */
@@ -710,10 +706,11 @@
 	    if (!thr_alarm(&alarmed,net->read_timeout,&alarm_buff)) /* Don't wait too long */
 	    {
 	      my_bool old_mode;
+	      uint retry_fcntl = 0;
 	      while (vio_blocking(net->vio, TRUE, &old_mode) < 0)
 	      {
 		if (vio_should_retry(net->vio) &&
-		    retry_count++ < net->retry_count)
+		    retry_fcntl++ < net->retry_count)
 		  continue;
 		DBUG_PRINT("error",
 			   ("fcntl returned error %d, aborting thread",
@@ -731,8 +728,8 @@
 #endif
 		goto end;
 	      }
-	      retry_count=0;
-	      continue;
+	      if (retry_count++ < net->retry_count)
+		continue;
 	    }
 	  }
 #endif /* (!defined(__WIN__) && !defined(__EMX__)) || defined(MYSQL_SERVER) */
@@ -747,7 +744,7 @@
 #endif /* EXTRA_DEBUG */
 	  }
 #if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER)
-	  if (vio_should_retry(net->vio))
+	  if (interrupted && retry_count++ < net->retry_count)
 	  {
 	    DBUG_PRINT("warning",("Interrupted read. Retrying..."));
 	    continue;
diff -u -r mysql-4.1.13-nightly-20050706.orig/sql-common/client.c mysql-4.1.13-nightly-20050706/sql-common/client.c
--- mysql-4.1.13-nightly-20050706.orig/sql-common/client.c	2005-07-06 13:50:37.151117256 +0200
+++ mysql-4.1.13-nightly-20050706/sql-common/client.c	2005-07-06 13:51:13.857537032 +0200
@@ -1800,9 +1800,17 @@
   vio_keepalive(net->vio,TRUE);
   /* Override local client variables */
   if (mysql->options.read_timeout)
-    net->read_timeout= mysql->options.read_timeout;
+  {
+    net->read_timeout = mysql->options.read_timeout;
+    vio_timeout(net->vio, VIO_READ_TIMEOUT,
+		net->read_timeout * 1000 / (net->retry_count + 1));
+  }
   if (mysql->options.write_timeout)
-    net->write_timeout= mysql->options.write_timeout;
+  {
+    net->write_timeout = mysql->options.write_timeout;
+    vio_timeout(net->vio, VIO_WRITE_TIMEOUT,
+		net->write_timeout * 1000 / (net->retry_count + 1));
+  }
   if (mysql->options.max_allowed_packet)
     net->max_packet_size= mysql->options.max_allowed_packet;
 
diff -u -r mysql-4.1.13-nightly-20050706.orig/vio/viosocket.c mysql-4.1.13-nightly-20050706/vio/viosocket.c
--- mysql-4.1.13-nightly-20050706.orig/vio/viosocket.c	2005-07-06 13:50:39.336784984 +0200
+++ mysql-4.1.13-nightly-20050706/vio/viosocket.c	2005-07-06 13:51:13.857537032 +0200
@@ -34,13 +34,7 @@
   int r;
   DBUG_ENTER("vio_read");
   DBUG_PRINT("enter", ("sd=%d, buf=%p, size=%d", vio->sd, buf, size));
-
-#ifdef __WIN__
-  r = recv(vio->sd, buf, size,0);
-#else
-  errno=0;					/* For linux */
-  r = read(vio->sd, buf, size);
-#endif /* __WIN__ */
+  r = recv(vio->sd, buf, size, 0);
 #ifndef DBUG_OFF
   if (r < 0)
   {
@@ -57,11 +51,7 @@
   int r;
   DBUG_ENTER("vio_write");
   DBUG_PRINT("enter", ("sd=%d, buf=%p, size=%d", vio->sd, buf, size));
-#ifdef __WIN__
-  r = send(vio->sd, buf, size,0);
-#else
-  r = write(vio->sd, buf, size);
-#endif /* __WIN__ */
+  r = send(vio->sd, buf, size, 0);
 #ifndef DBUG_OFF
   if (r < 0)
   {
@@ -317,15 +307,33 @@
 }
 
 
-void vio_timeout(Vio *vio __attribute__((unused)),
-		 uint which __attribute__((unused)),
-                 uint timeout __attribute__((unused)))
+void vio_timeout(Vio *vio, timeout_type which, uint milliseconds)
 {
 #ifdef __WIN__
-  ulong wait_timeout= (ulong) timeout * 1000;
-  (void) setsockopt(vio->sd, SOL_SOCKET, 
-	which ? SO_SNDTIMEO : SO_RCVTIMEO, (char*) &wait_timeout,
-        sizeof(wait_timeout));
+  ulong wait_timeout;
+#else
+  struct timeval tv;
+#endif /* __WIN__ */
+  int optname;
+  switch (which)
+  {
+    case VIO_READ_TIMEOUT:
+      optname = SO_RCVTIMEO;
+      break;
+    case VIO_WRITE_TIMEOUT:
+      optname = SO_SNDTIMEO;
+      break;
+    default:
+      return;
+  }
+#ifdef __WIN__
+  wait_timeout= (ulong) milliseconds;
+  setsockopt(vio->sd, SOL_SOCKET, optname, (char*) &wait_timeout,
+	     sizeof(wait_timeout));
+#else
+  tv.tv_sec = milliseconds / 1000;
+  tv.tv_usec = milliseconds % 1000 * 1000;
+  setsockopt(vio->sd, SOL_SOCKET, optname, &tv, sizeof(tv));
 #endif /* __WIN__ */
 }
[6 Jul 2005 12:38] Nicolas Baradakis
Fix read/write timeout options

Attachment: timeout.patch (application/octet-stream, text), 7.46 KiB.

[30 Jul 2005 19:53] Sergei Golubchik
see also WL#1907,
the thread in internals that starts from http://lists.mysql.com/internals/26739
BUG#4143, BUG#5449
[7 Feb 2006 21:25] Derek Melvin
So is there a way to timeout on Linux ?
[7 Feb 2006 21:51] Nicolas Baradakis
See SO_RCVTIMEO and SO_SNDTIMEO in socket(7) manpage.
[20 Feb 2006 0:08] Jerry Peng
This looks like the same problem as in:
   http://bugs.mysql.com/bug.php?id=5449
which is still in progress.

I cannot find the patch in any release. Is this patch checked in at all? Or, do we have to apply the patch manually for now? If so, will it work to apply it to 4.1.7? This is a serious problem for us, and I believe it is critical for others.
[9 Aug 2006 15:52] Mark Callaghan
This patch has worked great for me with one small nit. It ignores net->read_timeout and net->write_timeout when they are set to 0. This mislead others looking at the code into thinking that when read_timeout and write_timeout are set to 0, that timeouts are disabled. They then began calling mysql_options to set read and write timeouts to 0 assuming that mean no timeout and we began to get mysterious intermittent connection failures.
[10 Aug 2006 9:32] Nicolas Baradakis
> This patch has worked great for me with one small nit. It ignores
> net->read_timeout and net->write_timeout when they are set to 0.

That's the expected behaviour.

> This mislead others looking at the code into thinking that when
> read_timeout and write_timeout are set to 0, that timeouts are
> disabled.

This is actually the case. See the socket(7) manpage: by default the
operation will never timeout.

> They then began calling mysql_options to set read and write timeouts
> to 0 assuming that mean no timeout and we began to get mysterious
> intermittent connection failures.

I guess it's a local setup issue. When timeouts are set to 0 it doesn't
call setsockopt(2), so it's basically the same with or without the patch.
[21 Aug 2006 17:33] Tomash Brechko
This bug is a duplicate of bug#9678.