3

I have a simple console app that is executing a series of stored procedures about 150,000 times. It's purpose is to import initial database data into a new instance of the database.

It runs fine at work, but at home I am trying to use Sql Server Express, and everything appears fine, but at exactly 25.76% complete the sql connection enters a broken state and generates an InvalidOperation Exception stating that the connection is broken and cannot be used to execute the next command.

I understand what that means, but I can't understand why it is becoming broken and why it happens at exactly the same time on each try of running the console app.

It enters the broken state at 25.76% complete every time, always at that spot, and always on the same line of SQL being executed.

The line that is being executed when the exception occurs is the following:

EXEC [geo].[addUpdateRegion] @countryCode = N'CG', @regionCode = N'08', @regionName = N'Plateaux', @initData = 1

It causes an InvalidOperationException: BeginExecuteNonQuery requires an open and available Connection. The connection's current state: Broken."

I am baffled, because I have timeouts disabled completely. So I'm wondering if SQLExpress maybe has a limit to how many commands you can execute on the same connection?

This is what the code looks like:

using log4net;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace XYZ.SetupData
{
    class Program
    {
        static ILog logger = null;
        static string connectionString = null;
        static SqlConnection conn = null;
        static double lineCount = 1;
        static double lineIndex = 1;
        static Program()
        {
            logger = LogManager.GetLogger(typeof(Program));
            log4net.Config.XmlConfigurator.Configure();
            var connSettings = ConfigurationManager.ConnectionStrings["theDb"];
            if (connSettings == null || string.IsNullOrEmpty(connSettings.ConnectionString))
                throw new ConfigurationErrorsException("connectionString theDbwas not found or is empty.");
            connectionString = connSettings.ConnectionString;
            conn = new SqlConnection(connectionString);
            conn.Open();
        }

        static void Main(string[] args)
        {
            var baseDir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory + "sqlFiles");
            var files = baseDir.GetFiles("*.sql", SearchOption.TopDirectoryOnly).Select(file => file.FullName).ToArray();
            lineCount = (double)GetTotalFileLines(files);

            foreach (var file in files)
            {
                ExecuteFileLines(file);
            }
            Console.ReadKey(true);
        }

        #region Utility
        static void WriteProgress(string command)
        {
            double percent = (lineIndex / lineCount) * 100;
            Console.Write("\r Percent Complete: {0}%   ", Math.Round(percent, 2));
            ++lineIndex;
        }

        static int GetTotalFileLines(params string[] fileNames)
        {
            int total = 0;
            foreach (var fileName in fileNames)
                total += (File.ReadLines(fileName).Where(line => !string.IsNullOrEmpty(line) && !line.StartsWith("--")).Count());
            return total;
        }
        static void ExecuteFileLines(string fileName)
        {
            TryRun(() =>
            {
                if (string.IsNullOrEmpty(fileName))
                    throw new ArgumentNullException("fileName");
                if (!File.Exists(fileName))
                    throw new FileNotFoundException("file: " + fileName + " was not found!");
                IEnumerable<string> fileLines = File.ReadLines(fileName).Where(line => !string.IsNullOrEmpty(line) && !line.StartsWith("--"));


                LogInfo("--Staring Execution: " + fileName);
                foreach (var line in fileLines)
                {
                    RunSqlConnection(conn =>
                    {
                        LogInfo("RUNNING | " + line);
                        WriteProgress(line);
                        try
                        {
                            SqlCommand cmd = new SqlCommand(line, conn);
                            cmd.BeginExecuteNonQuery();
                        }
                        catch (Exception ex)
                        {
                            int i = 0;
                        }
                    });
                }
            });
        }
        static void RunSqlConnection(Action<SqlConnection> callBack)
        {
            try
            {
                callBack(conn);
            }
            catch (Exception ex)
            {
                LogError(ex);
                throw ex;
            }
        }
        static void TryRun(Action callBack)
        {
            try
            {
                callBack();
            }
            catch (Exception ex)
            {
                LogError(ex);
                throw ex;
            }
        }
        static void LogError(Exception ex)
        {
            logger.Error(ex.Message, ex);
            Console.WriteLine(ex.ToString());
        }

        static void LogInfo(string message, params object[] parameters)
        {
            logger.Info(string.Format(message, parameters));
        }

        static void LogDebug(string message, params object[] parameters)
        {
            logger.Debug(string.Format(message, parameters));
        }
        #endregion
    }
}
Ryan Mann
  • 5,178
  • 32
  • 42
  • You sould check the [Comparison of Sql Server Editions](http://www.microsoft.com/en-us/server-cloud/products/sql-server-editions/). You may be hitting the 1 GB per instance memory limit. – Oguz Ozgul Nov 14 '15 at 23:00
  • @OguzOzgul I completely forgot about that, and that's probably exactly what it is. Would explain why it happens at the same time every time too. – Ryan Mann Nov 14 '15 at 23:02
  • If I enter this as an answer, would you accept it? – Oguz Ozgul Nov 14 '15 at 23:03
  • And of course if that's it. If not, I am wondering what it is so please let us know too. – Oguz Ozgul Nov 14 '15 at 23:05
  • That was it, and yeah I would. I added a reset Counter to remake the connection every 500 executes, and problem solved. – Ryan Mann Nov 14 '15 at 23:07

2 Answers2

4

You are trying to process the work on multiple threads. There is a bug in that you are using the same connection concurrently. This is not allowed. Since this is a race condition anything could happen internally.

I'm not sure why you are using the APM pattern at all since it is obsolete. I think PLINQ plus synchronous IO plus one connection per work item actually fits your scenario nicely.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I'm not sure how I am on multiple threads. It's a console app all in the Program class off the Main method. It should be Synchronous if I understand it correctly. – Ryan Mann Nov 14 '15 at 23:11
  • You are using async IO (`BeginExecuteNonQuery`), so that makes it non-synchronous. You are starting one async IO for each line on the same conn. That makes it concurrent and racy. – usr Nov 14 '15 at 23:11
  • Another bug is that EndExecuteQuery is never called. That violates the APM protocol and leaks resources. – usr Nov 14 '15 at 23:13
  • Where am I using Async.. I am using lambda expressions to call methods synchronously, inline. Unless some lambda I am using is async by default I am unaware of. – Ryan Mann Nov 14 '15 at 23:13
  • AHHH I meant to use ExecuteNonQuery(), thanks for catching that. That was the async call I see. Man I feel like a nub now lol.. Can't believe I missed that. Intellisense error... – Ryan Mann Nov 14 '15 at 23:14
  • `BeginExecuteNonQuery` starts an async IO. That is non-sync. Tell me what exactly you do not understand about that so that I can clarify. Update: OK. – usr Nov 14 '15 at 23:14
  • OK, when you make it synchronous the problem is probably going away, too. – usr Nov 14 '15 at 23:14
  • Yeah that fixed it completely. Thanks for the help. – Ryan Mann Nov 14 '15 at 23:18
  • @Ryios this was a fun question. I never before encountered someone who accidentally issued async IO. Usually it is intentional but without the required understanding. I'll add this to my arsenal of answering techniques :) – usr Nov 14 '15 at 23:20
2

You should check the Comparison of Sql Server Editions.

You may be hitting the 1 GB per instance memory limit for Sql Express Edition.

Josh Crozier
  • 233,099
  • 56
  • 391
  • 304
Oguz Ozgul
  • 6,809
  • 1
  • 14
  • 26
  • This can't be the full explanation because if the memory limit was hit there should be an error about it. I think the OP made a mistake accepting it. His reset strategy working does not mean that the instance memory limit was the culprit. His whole solution was broken. I also do not condone of pure guess answers. – usr Nov 14 '15 at 23:09
  • This is useful if you hit 1GB limit, but it was actually because I was calling the async execute method by mistake. I changed it to ExecuteNonQuery and it runs fine. – Ryan Mann Nov 14 '15 at 23:16
  • 1
    Thanks, what is important that your problem is solved. But, how at work could you execute all of that 150K operations successfully? – Oguz Ozgul Nov 14 '15 at 23:18