代码之家  ›  专栏  ›  技术社区  ›  nganju

连接池使用相同的错误连接字符串向两个线程返回相同的异常实例?

  •  13
  • nganju  · 技术社区  · 15 年前

    好吧,这看起来像是.NET中的一个主要基本错误:

    考虑下面的简单程序,它有目的地尝试连接到不存在的数据库:

    class Program
    {
        static void Main(string[] args)
        {            
    
            Thread threadOne = new Thread(GetConnectionOne);
            Thread threadTwo = new Thread(GetConnectionTwo);            
            threadOne.Start();
            threadTwo.Start();
    
        }
    
    
    
        static void GetConnectionOne()
        {
            try
            {
                using (SqlConnection conn = new SqlConnection("Data Source=.\\wfea;Initial Catalog=zc;Persist Security Info=True;Trusted_Connection=yes;"))
                {
                    conn.Open();
                }    
            } catch (Exception e)
            {
                File.AppendAllText("ConnectionOneError.txt", e.Message + "\n" + e.StackTrace + "\n");
            }
    
        }
    
    
        static void GetConnectionTwo()
        {
            try
            {
                using (SqlConnection conn = new SqlConnection("Data Source=.\\wfea;Initial Catalog=zc;Persist Security Info=True;Trusted_Connection=yes;"))
                {
                    conn.Open();
                }
            }
            catch (Exception e)
            {
                File.AppendAllText("ConnectionTwoError.txt", e.Message + "\n" + e.StackTrace + "\n");
            }
    
        }
    }
    

    运行此程序并在catch块上设置断点。dbconnection对象将尝试连接15秒(在两个线程上),然后它将抛出一个错误。检查异常的堆栈跟踪,堆栈跟踪将混合两个调用堆栈,如下所示:

    at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject)
    at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
    at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
    at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject)
    at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
    at System.Data.SqlClient.SqlConnection.Open()
    at ZoCom2Test.Program.GetConnectionOne() in C:\src\trunk\ZTest\Program.cs:line 38
    at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
    at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
    at System.Data.SqlClient.SqlConnection.Open()
    at ZoCom2Test.Program.GetConnectionTwo() in C:\src\trunk\ZTest\Program.cs:line 54
    

    你可能要尝试几次才能实现这一点,但我现在正在我的机器上实现这一点。这怎么可能?在虚拟机级别,这应该是完全不可能的。看起来dbconnection.open()函数同时在两个线程上引发相同的异常,或者类似的异常。

    4 回复  |  直到 14 年前
        1
  •  6
  •   John Saunders Tony    14 年前

    试试这个,看看会发生什么:

    class ThreadingBug
    {
        private const string CONNECTION_STRING =
            "Data Source=.\\wfea;Initial Catalog=catalog;Persist Security Info=True;Trusted_Connection=yes;";
    
        static void Main(string[] args)
        {
            try
            {
                Thread threadOne = new Thread(GetConnectionOne);
                Thread threadTwo = new Thread(GetConnectionTwo);
                threadOne.Start();
                threadTwo.Start();
    
                threadOne.Join(2000);
                threadTwo.Join(2000);
            }
            catch (Exception e)
            {
                File.AppendAllText("Main.txt", e.ToString());
            }
        }
    
        static void GetConnectionOne()
        {
            try
            {
                using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
                {
                    conn.Open();
                }
            }
            catch (Exception e)
            {
                File.AppendAllText("GetConnectionOne.txt", e.ToString());
            }
        }
    
        static void GetConnectionTwo()
        {
            try
            {
                using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
                {
                    conn.Open();
                }
            }
            catch (Exception e)
            {
                File.AppendAllText("GetConnectionTwo.txt", e.ToString());
            }
        }
    }
    

    我相信这里有一个bug,虽然它既不是主要的,也不是基本的。在缩小范围(并执行删除一个线程之类的操作)之后,它看起来像 Exception 类是由两个线程上的连接池实现抛出的(这是Gregory发现的荣誉)。这有时显示为损坏的(“混合”)堆栈跟踪,有时在两个线程上显示为相同的堆栈跟踪,即使两个线程之间的代码非常不同。

    发表评论 Thread.Start 调用显示完全不同的堆栈跟踪,表明奇数部分在连接池实现中-奇数堆栈跟踪由连接池发出,因为两个线程使用相同的连接字符串和凭据。

    我已在提交了有关此的连接问题 https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=522506 . 每个人都应该自由地投票决定你觉得它有多重要(或不重要),你是否可以复制它,或者你是否有一个变通方案。这将帮助Microsoft确定修复的优先级。


    更新: 已更新连接问题。微软承认这是一个bug,并计划在未来的版本中修复它。

    感谢Nganju、Gregory和所有参与解决这个问题的人。这确实是一个错误,它将被修复,这是因为我们。

        2
  •  3
  •   ChaosPandion    15 年前

    这不是虚拟机中的错误。这是你的冒犯热线:

    private static readonly DbConnectionFactory _connectionFactory;
    

    在这个内部,我们有连接池。存储对发生的异常的引用。 这将在执行多线程时打开竞争条件。

    我们如何证明这一点?

    从逻辑上讲,如果您使用不同的连接池,那么我们就不会有这种竞争条件。因此,我用在每个线程的连接字符串中指定的不同数据源重新运行同一个测试。例外情况现在正确地出现了。

    这实际上是连接池不是线程安全的情况。

        3
  •  3
  •   Henk Holterman    15 年前

    好吧,我设法在里面复制了这个(VS2008,FX3.5SP1,双核) (*)调试器。在稍微改变了你的捕获逻辑之后,它甚至是可靠的可复制的。就像格雷戈里提到的,这是 相同的 两个线程中都引发了异常实例。

    这应该是完全不可能的 虚拟机级别。

    你从哪里得到这个主意的?

    两个线程都试图通过连接池进行连接。我对池的工作原理一无所知,但我猜:它正在序列化两个同时发生的请求。听起来对服务器很好。当尝试失败时,它有1个异常和2个等待线程。

    我也希望clr或connectionpool复制异常并预先准备2个单独的stacktrace,但它合并了2个调用跟踪。

    所以我认为你的虫子很可能是 特征 ,状态: 按设计 .
    因为它不是一个真正的“混合的”stacktrace,而是一个故意的Y形的。这看起来不是偶然的。

    不过,如果有人能找到这种行为的参考资料,那就太好了。现在我不确定这是一个clr还是一个connectionpool的“功能”。

    (*) 编辑: 我想我曾经在调试器之外看到过它,但现在我无法重现它。所以它可能是一个调试器或一个计时问题。

        4
  •  2
  •   John Saunders Tony    15 年前

    您也会遇到同样的异常。但我不明白为什么。查看输出窗口,特别是exception1==exception2。

    class ThreadingBug
    {
        private const string CONNECTION_STRING =
            "Data Source=.\\wfea;Initial Catalog=catalog;Persist Security Info=True;Trusted_Connection=yes;";
    
        static void Main(string[] args)
        {
            try
            {
                Thread threadOne = new Thread(GetConnectionOne);
                Thread threadTwo = new Thread(GetConnectionTwo);
                threadOne.Start();
                threadTwo.Start();
    
                threadOne.Join(20000);
                threadTwo.Join(20000);
    
                Debug.WriteLine("Same?" + (exception1 == exception2));
            }
            catch (Exception e)
            {
                Debug.WriteLine("error main" + e);
            }
        }
    
        static Exception exception1;
    
        static void GetConnectionOne()
        {
            try
            {
                using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
                {
                    conn.Open();
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine("Error Con one" + e);
                exception1 = e;
            }
        }
        static Exception exception2;
    
        static void GetConnectionTwo()
        {
            try
            {
                using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
                {
                    conn.Open();
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine("Error Con two" + e);
                exception2 = e;
            }
        }
    }
    

    编辑:下面是我的原始回复。

    很可能您的“随机”文件名是相似的,如果不是相同的,因为它们有时会在非常短的时间内被调用。通常,当你有一个随机出现的问题,你有一个随机的。下一个电话,它应该是你第一个看到的地方。