List:Commits« Previous MessageNext Message »
From:Vladislav Vaintroub Date:August 3 2009 10:48pm
Subject:bzr commit into connector-net-6.1 branch (vvaintroub:723) Bug#40684
View as plain text  
#At file:///H:/connector_net/6.1/ based on revid:reggie.burnett@stripped

  723 Vladislav Vaintroub	2009-08-04
      Bug #40684 Connector/NET does not obey Connect Timeout.
      The actual problem in the bug report is that pooled connections are dropped  by firewalls due to long inactivity and that results in longer connect times.
      
      The patch introduces 2 new parameters: "keepalive" (boolean, sets keepalive option on socket) and "keepalive timeout" (integer, number of seconds before first keepalive packet is sent).  "keepalive timeout" does not automatically switch "keepalive" on, both options need to be specified to set the timeout.
      
      One can use this parameters to prevent inactivity drops and also to detect broken connections faster.
      Note that  "Keepalive timeout"  is not currently available in Compact Framework nor in Mono ( will be silently ignored there and connection will use the default timeout what is normally 2 hours)

    modified:
      MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs
      MySql.Data/Provider/Source/NativeDriver.cs
      MySql.Data/Provider/Source/common/StreamCreator.cs
      MySql.Data/Tests/Source/ConnectionStringBuilder.cs
      MySql.Data/Tests/Source/ConnectionTests.cs
=== modified file 'MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs'
--- a/MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs	2009-07-31 22:45:32 +0000
+++ b/MySql.Data/Provider/Source/MySqlConnectionStringBuilder.cs	2009-08-03 22:48:52 +0000
@@ -59,6 +59,8 @@ namespace MySql.Data.MySqlClient
         bool functionsReturnString;
         bool useAffectedRows;
         bool oldGuids;
+        bool keepalive;
+        uint keepaliveTimeout;
 
         static MySqlConnectionStringBuilder()
         {
@@ -104,6 +106,8 @@ namespace MySql.Data.MySqlClient
             defaultValues.Add(Keyword.UseAffectedRows, false);
             defaultValues.Add(Keyword.SslMode, MySqlSslMode.None);
             defaultValues.Add(Keyword.OldGuids, false);
+            defaultValues.Add(Keyword.Keepalive, false);
+            defaultValues.Add(Keyword.KeepaliveTimeout, 7200);
         }
 
         /// <summary>
@@ -761,6 +765,37 @@ namespace MySql.Data.MySqlClient
             }
         }
 
+#if !CF
+        [Category("Advanced")]
+        [DisplayName("Keepalive")]
+        [Description("Set keepalive option on socket, useful to check for network outage and to prevent disconnect due to long inactivity")]
+        [DefaultValue(false)]
+#endif
+        public bool Keepalive
+        {
+            get { return keepalive;}
+            set 
+            {
+                SetValue("Keepalive", value);
+                keepalive = value;
+            }
+        }
+
+#if !CF
+        [Category("Advanced")]
+        [DisplayName("Keepalive timeout")]
+        [Description("Timeout, in seconds, with no activity until the first keep-alive packet is sent.")]
+        [DefaultValue(false)]
+#endif
+        public uint KeepaliveTimeout
+        {
+            get { return keepaliveTimeout; }
+            set
+            {
+                SetValue("Keepalive timeout", value);
+                keepaliveTimeout = value;
+            }
+        }
         #endregion
 
         #region Pooling Properties
@@ -1225,6 +1260,10 @@ namespace MySql.Data.MySqlClient
                     return Keyword.SslMode;
                 case "OLD GUIDS":
                     return Keyword.OldGuids;
+                case "KEEPALIVE":
+                    return Keyword.Keepalive;
+                case "KEEPALIVE TIMEOUT":
+                    return Keyword.KeepaliveTimeout;
             }
             throw new ArgumentException(Resources.KeywordNotSupported, key);
         }
@@ -1317,6 +1356,10 @@ namespace MySql.Data.MySqlClient
                     return sslMode;
                 case Keyword.OldGuids:
                     return oldGuids;
+                case Keyword.Keepalive:
+                    return keepalive;
+                case Keyword.KeepaliveTimeout:
+                    return keepaliveTimeout;
                 default:
                     return null; /* this will never happen */
             }
@@ -1435,6 +1478,10 @@ namespace MySql.Data.MySqlClient
                     sslMode = ConvertToSslMode(value); break;
                 case Keyword.OldGuids:
                     oldGuids = ConvertToBool(value); break;
+                case Keyword.Keepalive:
+                    keepalive = ConvertToBool(value); break;
+                case Keyword.KeepaliveTimeout:
+                    keepaliveTimeout = ConvertToUInt(value); break;
             }
         }
 
@@ -1619,6 +1666,8 @@ namespace MySql.Data.MySqlClient
         FunctionsReturnString,
         UseAffectedRows,
         SslMode,
-        OldGuids
+        OldGuids,
+        Keepalive,
+        KeepaliveTimeout
     }
 }

=== modified file 'MySql.Data/Provider/Source/NativeDriver.cs'
--- a/MySql.Data/Provider/Source/NativeDriver.cs	2009-07-28 20:40:35 +0000
+++ b/MySql.Data/Provider/Source/NativeDriver.cs	2009-08-03 22:48:52 +0000
@@ -201,7 +201,8 @@ namespace MySql.Data.MySqlClient
                     string pipeName = Settings.PipeName;
                     if (Settings.ConnectionProtocol != MySqlConnectionProtocol.NamedPipe)
                         pipeName = null;
-                    StreamCreator sc = new StreamCreator(Settings.Server, Settings.Port, pipeName);
+                    StreamCreator sc = new StreamCreator(Settings.Server, Settings.Port, pipeName,
+                        Settings.Keepalive, Settings.KeepaliveTimeout);
                     baseStream = sc.GetStream(Settings.ConnectionTimeout);
 #if !CF
                 }

=== modified file 'MySql.Data/Provider/Source/common/StreamCreator.cs'
--- a/MySql.Data/Provider/Source/common/StreamCreator.cs	2009-07-28 20:40:35 +0000
+++ b/MySql.Data/Provider/Source/common/StreamCreator.cs	2009-08-03 22:48:52 +0000
@@ -25,6 +25,7 @@ using System.Net.Sockets;
 using System.Reflection;
 using System.Diagnostics;
 using MySql.Data.MySqlClient.Properties;
+using System.Runtime.InteropServices;
 
 namespace MySql.Data.Common
 {
@@ -37,14 +38,18 @@ namespace MySql.Data.Common
         uint port;
         string pipeName;
         uint timeOut;
+        uint keepaliveTimeout;
+        bool keepalive;
 
-        public StreamCreator(string hosts, uint port, string pipeName)
+        public StreamCreator(string hosts, uint port, string pipeName, bool keepalive, uint keepaliveTimeout)
         {
             hostList = hosts;
             if (hostList == null || hostList.Length == 0)
                 hostList = "localhost";
             this.port = port;
             this.pipeName = pipeName;
+            this.keepalive = keepalive;
+            this.keepaliveTimeout = keepaliveTimeout;
         }
 
         public Stream GetStream(uint timeout)
@@ -181,6 +186,10 @@ namespace MySql.Data.Common
 			Socket socket = unix ?
 				new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP) :
 				new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            if (keepalive)
+            {
+                SetKeepAlive(socket, keepaliveTimeout);
+            }
 			IAsyncResult ias = socket.BeginConnect(endPoint, null, null);
 			if (!ias.AsyncWaitHandle.WaitOne((int)timeOut * 1000, false))
 			{
@@ -202,5 +211,56 @@ namespace MySql.Data.Common
             return stream;
 		}
 
+
+
+        /// <summary>
+        /// Set keepalive + timeout on socket.
+        /// </summary>
+        /// <param name="s">socket</param>
+        /// <param name="time">keepalive timeout, in seconds</param>
+        private static void SetKeepAlive(Socket s, uint time)
+        {
+
+#if !CF
+            uint on = 1;
+            uint interval = 1000; // default interval = 1 sec
+
+            uint timeMilliseconds;
+            if (time > UInt32.MaxValue / 1000)
+                timeMilliseconds = UInt32.MaxValue;
+            else
+                timeMilliseconds = time * 1000;
+
+            // Use Socket.IOControl to implement equivalent of
+            // WSAIoctl with  SOL_KEEPALIVE_VALS 
+
+            // the native structure passed to WSAIoctl is
+            //struct tcp_keepalive {
+            //    ULONG onoff;
+            //    ULONG keepalivetime;
+            //    ULONG keepaliveinterval;
+            //};
+            // marshal the equivalent of the native structure into a byte array
+
+            byte[] inOptionValues = new byte[12];
+            BitConverter.GetBytes(on).CopyTo(inOptionValues, 0);
+            BitConverter.GetBytes(time).CopyTo(inOptionValues, 4);
+            BitConverter.GetBytes(interval).CopyTo(inOptionValues, 8);
+            try
+            {
+                // call WSAIoctl via IOControl
+                s.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
+                return;
+            }
+            catch (NotImplementedException)
+            {
+                // Mono throws not implemented currently
+            }
+#endif
+            // Fallback if Socket.IOControl is not available ( Compact Framework )
+            // or not implemented ( Mono ). Keepalive option will still be set, but
+            // with timeout is kept default.
+            s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true);
+        }
     }
 }

=== modified file 'MySql.Data/Tests/Source/ConnectionStringBuilder.cs'
--- a/MySql.Data/Tests/Source/ConnectionStringBuilder.cs	2009-04-21 18:02:13 +0000
+++ b/MySql.Data/Tests/Source/ConnectionStringBuilder.cs	2009-08-03 22:48:52 +0000
@@ -35,7 +35,7 @@ namespace MySql.Data.MySqlClient.Tests
             sb = new MySqlConnectionStringBuilder();
             sb.ConnectionString = "server=localhost;uid=reggie;pwd=pass;port=1111;" +
                 "connection timeout=23; pooling=true; min pool size=33; " +
-                "max pool size=66";
+                "max pool size=66;keepalive=true;keepalive timeout=1";
             Assert.AreEqual("localhost", sb.Server);
             Assert.AreEqual("reggie", sb.UserID);
             Assert.AreEqual("pass", sb.Password);
@@ -44,6 +44,10 @@ namespace MySql.Data.MySqlClient.Tests
             Assert.IsTrue(sb.Pooling);
             Assert.AreEqual(33, sb.MinimumPoolSize);
             Assert.AreEqual(66, sb.MaximumPoolSize);
+            Assert.AreEqual(sb.Keepalive, true);
+            Assert.AreEqual(sb.KeepaliveTimeout, 1);
+
+
 
             try
             {
@@ -81,6 +85,8 @@ namespace MySql.Data.MySqlClient.Tests
             Assert.IsFalse(sb.AllowZeroDateTime);
             Assert.IsFalse(sb.UsePerformanceMonitor);
             Assert.AreEqual(25, sb.ProcedureCacheSize);
+            Assert.IsFalse(sb.Keepalive);
+            Assert.AreEqual(sb.KeepaliveTimeout, 7200);
         }
 
         /// <summary>

=== modified file 'MySql.Data/Tests/Source/ConnectionTests.cs'
--- a/MySql.Data/Tests/Source/ConnectionTests.cs	2009-04-21 18:02:13 +0000
+++ b/MySql.Data/Tests/Source/ConnectionTests.cs	2009-08-03 22:48:52 +0000
@@ -449,5 +449,19 @@ namespace MySql.Data.MySqlClient.Tests
             MySqlConnection c = new MySqlConnection();
             c.ConnectionString = null;
         }
+
+        /// <summary>
+        /// Test if keepalive parameters work.
+        /// </summary>
+        [Test]
+        public void Keepalive()
+        {
+            string connstr = GetConnectionStringEx("test","test",true);
+            connstr += ";keepalive=true;keepalive timeout=10;pooling=true";
+            using (MySqlConnection c = new MySqlConnection(connstr))
+            {
+                c.Open();
+            }
+        }
     }
 }


Attachment: [text/bzr-bundle] bzr/vvaintroub@mysql.com-20090803224852-2utrc3bzpay72jhq.bundle
Thread
bzr commit into connector-net-6.1 branch (vvaintroub:723) Bug#40684Vladislav Vaintroub4 Aug