improve.dk
Just another mindless drone looking for the perfect stack
posts - 227, comments - 489

Controlling SqlConnection timeouts

Written on March 10, 2008 by Mark S. Rasmussen in Development: .NET, SQL Server: Tricks

When performing queries against a SQL Server database, there are a couple of methods readily available. However, an option is missing.

The primary timeout value is that of SqlConnection.ConnectionTimeout. This specifies how long time the SQL Server service has to respond to a connection attempt. You cannot set this value directly, you'll have to set it as part of the connection string:

Data Source=server;Initial Catalog=databaseUser Id=username;Password=password;Connect Timeout=30

Note that the value is expressed in seconds, not milliseconds. The default value is 30 seconds. Secondly, we can use the SqlCommand.CommandTimeout value. This sets the timeout value of a specific query running on SQL Server. The problem with these two is that we're missing a point in the pipeline, which goes:

TCP Connection to SQL Server -> SqlConnection.Open -> SqlCommand.Execute

The last two are covered, but if for some reason the SQL Server is dead, taken off the network, totally overloaded, we may get a timeout on the TCP level - and this could take a while. We currently have no way of controlling this timeout besides a server wide network level setting. Often, it's not desirable to have your application potentially spending several minutes before receiving a TCP timeout - or sometimes simply wait indefinitely. We need some way to control this.

What I present below is an example of a SqlConnection extension method called QuickOpen (in lack of a better name, it isn't quicker, it simply fails quicker). It'll take a timeout parameter in milliseconds, after which it'll throw a simple Exception. You can modify this to a more proper exception, this is just to show the point. Overall, using this method will introduce a slight delay (a couple of ms), so use it only when necessary, or when a couple of ms per SqlConnection.Open doesn't matter.

public static class SqlExtensions
{
	public static void QuickOpen(this SqlConnection conn, int timeout)
	{
		// We'll use a Stopwatch here for simplicity. A comparison to a stored DateTime.Now value could also be used
		Stopwatch sw = new Stopwatch();
		bool connectSuccess = false;

		// Try to open the connection, if anything goes wrong, make sure we set connectSuccess = false
		Thread t = new Thread(delegate()
		{
			try
			{
				sw.Start();
				conn.Open();
				connectSuccess = true;
			}
			catch { }
		});

		// Make sure it's marked as a background thread so it'll get cleaned up automatically
		t.IsBackground = true;
		t.Start();

		// Keep trying to join the thread until we either succeed or the timeout value has been exceeded
		while (timeout > sw.ElapsedMilliseconds)
			if (t.Join(1))
				break;

		// If we didn't connect successfully, throw an exception
		if (!connectSuccess)
			throw new Exception("Timed out while trying to connect.");
	}
}

Feedback

Gravatar

Michael Vermilion wrote on 5/9/2008 6:35 PM

This helped me out a lot. In my situation on Windows Mobile I did it a little differently. There was no StopWatch in the compact framework so I tried the DateTime idea you suggested, but it didn't work reliably. It would eventually start returning false after using it several times. The following seems to work reliably:

t.IsBackground = true;
t.Start();
t.Join(timeout);
Gravatar

Mark S. Rasmussen wrote on 5/9/2008 10:21 PM

Michael,

That's a great way to simplify my functionality. I love it when I'm reminded of even more KISS'ish ways to accomplish the same functionality :)
Gravatar

Michael Vermilion wrote on 5/10/2008 7:50 AM

I could ramble on for hours about everything I've done today, but my application finally works correctly thanks to you. BTW I think I might have also added t.Abort() after the Join() to resolve some problem I was having, although there were so many problems mixing together that it's hard for me to say whether that was needed. Also, I just upgraded my project to Compact Framework 3.5 and MS has now implemented the StopWatch. For another, MS has now added a sound player into the Compact Framework. Anyway, I spent the whole day trying to solve problems with my application losing wifi connection. I had sounds playing that were making calls to an external method and I think funny things were happening when I stressed that along with the connection timeout. Anyway making everything syncronous, including the SQL connection blocking the main thread and the SoundPlayer blocking the main thread (ie connect/disconnect sounds) seems to have done it.

U-Da-Man
Gravatar

Freddie wrote on 1/5/2009 7:34 AM

Thank you so much.
It helped me a lot.
Gravatar

Michael Vermilion wrote on 6/21/2010 7:13 PM

Daniel - Glad you got it working. I've been using this in production for a year now and it works great. I haven't touched this system in that time and haven't got any complaints about this not working, so props to Mark. Finally got some enhancement requests recently for merge replication, which looks interesting.
Gravatar

Ray wrote on 5/31/2011 7:23 PM

My version of your process in VB.
Thanks
Imports Microsoft.VisualBasic
Imports System.Threading

Public NotInheritable Class SqlExtensions
Private Sub New()
End Sub

Shared ThreadProcConn As Data.SqlClient.SqlConnection
Shared connectSuccess As Boolean = False
Public Shared Sub ThreadProc()
Try ' Try to open the connection, if anything goes wrong, make sure we set connectSuccess = false
ThreadProcConn.Open()
connectSuccess = True
Catch
connectSuccess = False
End Try
End Sub
Public Shared Sub QuickOpen(ByVal conn As Data.SqlClient.SqlConnection, ByVal timeout As Integer)
ThreadProcConn = conn
Dim t As New Thread(AddressOf ThreadProc)
Dim StartTime As Date = Date.Now
' Make sure it's marked as a background thread so it'll get cleaned up automatically
t.IsBackground = True
t.Start()
' Keep trying to join the thread until we either succeed or the timeout value has been exceeded
While StartTime.AddSeconds(timeout) > Date.Now
If t.Join(1) Then
Exit While
End If
End While
' If we didn't connect successfully, throw an exception
If Not connectSuccess Then
Throw New Exception("Server Unavailable")
End If
End Sub
End Class
Gravatar

John wrote on 8/17/2011 1:20 AM

Brilliant. Thanks for posting. :) It also helps explain why the timeout never seemed to work on the connection.
Gravatar

Kiki wrote on 4/29/2012 8:00 PM

Cool! I did something similar for queries against Active Directory.

Now I just wish I could use the QuickConnect method also in auto-generated TableAdapters. Any ideas how to accomplish this?

Kiki

Post Comment

Name  
Email
Url
Comment
Please add 2 and 5 and type the answer here: