1

I have a program that loads a large quantity of data (~800K-1M rows per iteration) in a Task running on the threadpool (see offending code sample below); no more than 4 tasks running concurrently. This is the only place in the program that a connection is made to this database. When running the program on my laptop (and other coworkers identical laptops), the program functions perfectly. However, we have access to another workstation via remote desktop that is substantially more powerful than our laptops. The program fails about 1/3 to 1/2 of the way through its list. All of the tasks return an exception.

The first exception was: "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached." I've tried googling, binging, searching on StackOverflow, and banging my head against the table trying to figure out how this can be the case. With no more than 4 tasks running at once, there shouldn't be more than 4 connections at any one time.

In response to this, I tried two things: (1) I added a try/catch around the conn.Open() line that would clear the pool if InvalidOperationException appears--that appeared to work [didn't let it run all the way through, but got substantially past where it did before], but at the cost of performance. (2) I changed ConnectionTimeout to be 30 seconds instead of 15, which did not work (but let it proceed a little more). I also tried at one point to do ConnectRetryInterval=4 (mistakenly choosing this instead of ConnectRetryCount)--this let to a different error "The maximum number of requests is 4,800", which is strange because we still shouldn't be anywhere near 4,800 requests or connections.

In short, I'm at a loss because I can't figure out what is causing this connection leak only on a higher speed computer. I am also unable to get Visual Studio on that computer to debug directly--any thoughts anyone might have on where to look to try and resolve this would be much appreciated.

(Follow-up to c# TaskFactory ContinueWhenAll unexpectedly running before all tasks complete)

private void LoadData()
    {
        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
        builder.DataSource = "redacted";
        builder.UserID = "redacted";
        builder.Password = "redacted";
        builder.InitialCatalog = "redacted";
        builder.ConnectTimeout = 30;

        using (SqlConnection conn = new SqlConnection(builder.ConnectionString))
        {
            //try
            //{
            //    conn.Open();
            //} catch (InvalidOperationException)
            //{
            //    SqlConnection.ClearPool(conn);
            //    conn.Open();
            //}
            conn.Open();


            string monthnum = _monthsdict.First((x) => x.Month == _month).MonthNum;
            string yearnum = _monthsdict.First((x) => x.Month == _month).YearNum;

            string nextmonthnum = _monthsdict[Array.IndexOf(_monthsdict, _monthsdict.First((x) => x.Month == _month))+1].MonthNum;
            string nextyearnum = _monthsdict[Array.IndexOf(_monthsdict, _monthsdict.First((x) => x.Month == _month)) + 1].YearNum;

            SqlCommand cmd = new SqlCommand();

            cmd.Connection = conn;

            cmd.CommandText = @"redacted";
            cmd.Parameters.AddWithValue("@redacted", redacted);
            cmd.Parameters.AddWithValue("@redacted", redacted);
            cmd.Parameters.AddWithValue("@redacted", redacted);
            cmd.CommandTimeout = 180;

            SqlDataReader reader = cmd.ExecuteReader();
            while(reader.Read())
            {
                Data data = new Data();

                int col1 = reader.GetOrdinal("col1");
                int col2 = reader.GetOrdinal("col2");
                int col3 = reader.GetOrdinal("col3");
                int col4 = reader.GetOrdinal("col4");

                data.redacted = redacted;
                data.redacted = redacted;
                data.redacted = redacted;
                data.redacted = redacted;
                data.redacted = redacted;

                data.Calculate();
                _data.Add(data); //not a mistake, referring to another class variable
            }
            reader.Close();
            cmd.Dispose();
            conn.Close();
            conn.Dispose();
        }
    }
ww2406
  • 109
  • 9
  • Also, this is .NET 4.6.2 with a WinForms UI – ww2406 May 15 '20 at 13:17
  • Can you please provide a [mcve]? – Enigmativity May 17 '20 at 09:33
  • Honestly I’m not sure I can given some of the environmental factors at play (e.g., that the primary table used in the query is over 20 *b*illion rows with several indices and that the program works fine on more consumer-common hardware). I’ll also update this question to include the button click procedure from my other question so they’re in one place, because those two form the MRE code-wise. I’ll then update briefly to give a suggested DB schema and field names for anyone wishing to test. – ww2406 May 17 '20 at 14:16
  • This is not an answer, but: `SqlCommand` and `SqlReader` also implement the `IDisposable` pattern - so you should do`using(var con...) {using(var cmd...){using var reader...){}}}` this way, you don't need to close or dispose any of them explicitly, the `using` statement is doing that for you. Also, you shouldn't use `AddWithValue`, and you should probably should use unique names for your parameters. – Zohar Peled May 18 '20 at 05:46

1 Answers1

0

This turned out to be a classic case of not reading the documentation closely enough. I was trying to cap max Threads at 4, using ThreadPool.SetMaxThreads, but max Threads cannot be less than the number of processors. On the workstation it failed on, it has 8 processors. So, there was never a cap, it was running as many tasks as the Task Scheduler felt appropriate, and it was eventually hitting the Connection Pool limit.

https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool.setmaxthreads

ww2406
  • 109
  • 9