【问题标题】:The Mystery of the Vanishing EF Call消失的 EF 调用的奥秘
【发布时间】:2015-05-28 20:06:10
【问题描述】:

今天我接到了来自我们 ASP.NET 生产系统的用户的紧急电话。一些用户(不是全部)无法输入某些数据。用户发布数据,然后系统冻结;电话再也没有回来。

我们尝试在 QA 系统上重现该问题(该系统具有生产数据的全新恢复),但未能成功。然后我从我的开发环境中运行并直接连接到生产数据库,伪装成受影响的用户之一。再次,没问题。结论:生产环境中一定存在某种问题,可能是在托管网站的 IIS 进程中的某个地方。

所以我在生产服务器上启动了 Visual Studio,并附加到 IIS 进程(孩子们,不要在家里这样做!),在有问题的代码中设置断点,登录作为用户,并试图保存数据。打断点并逐行执行,直到遇到这样的一行代码:

try
{
  ...
  using (var db = new MyDataContext())
  {
    ...
    var fooToUpdate = db.Foos.Single(f => f.ID == fooId); // <-- THIS LINE
    ...
  }
}
catch (Exception ex)
{
  // some error logging
}

在那一行点击“step”后,线程就消失了。消失得无影无踪。我在数据库上放了一个嗅探器,没有发出任何查询;不用说没有涉及数据库锁定。没有抛出异常。代码进入实体框架,从未离开。

数据的方式是每个用户每天都有一个不同且唯一的fooId,因此其他用户不会有相同的fooId。大多数用户能够加载他们的 Foo,但少数用户始终无法加载他们的个人 Foo。我尝试运行查询以在 SSMS 窗口中加载 Foo;一点问题都没有。唯一一次失败是在生产服务器上的这个特定 IIS 进程中。

现在,我可以回收应用程序池或重新启动 IIS,这可能会解决问题。但是一周前发生了类似的事情,当时我们也无法追踪。所以我们然后重置了 IIS,希望问题会消失。它确实做到了,持续了一周。现在它又回来了。

有没有人知道线程如何像这样简单地蒸发? Norman Bates 是不是躲在 EF 门后?

【问题讨论】:

  • 您是否尝试在生产环境中查看事件查看器,是否有任何有意义的消息(如果有)?

标签: c# asp.net-mvc-4 iis-7 entity-framework-5


【解决方案1】:

鉴于线程没有神奇地蒸发这一事实,我们可以推测一些更有可能的选择:

  1. 调试器很难跟踪在发布模式下编译的生产代码。仅仅因为调试发布代码 90% 的时间都有效,不要误认为它是可靠的。优化的代码可以很快使调试器脱离实际执行的轨道。发生这种情况时,线程看起来就像消失了一样。
  2. 假设线程确实合法地进入调用并且没有返回(这似乎得到了应用程序“冻结”的原始抱怨的支持),那么最可能的情况是某种类型的死锁。 EntityFramework 死锁并不常见,但也并非闻所未闻。我知道的最常见问题通常涉及TransactionScopeCommitableTransaction。您是否在省略的代码部分中使用任何事务?

【讨论】:

  • 在该方法的其他地方有事务边界,但我们不在这个执行点的事务边界内。
  • 从技术上讲,死锁场景也可能由访问EntityFramework 的任何其他应用程序线程启动。您可能想在线搜索各种检测死锁的方法,看看这是否可能是罪魁祸首。
  • 但是没有调用被记录为进入数据库,这是发生死锁的地方。问题发生在执行到达数据库之前。
  • SQL Server 中可能没有发生死锁。例如,有时来自关键部分的死锁挂在表面上看起来完全不相关的更高级别的方法上。这可能是需要在 WinDbg / SOS 中检查内存转储的问题之一。如果你走这条路,从!analyze -hang -v 命令开始。
  • 我想我已经解决了。看我的回答。为您的逻辑和有用的 cmets +1 - 谢谢!
【解决方案2】:

事实证明,EF 部分毕竟是一条红鲱鱼。我去下载了 Telerik 的 JustDecompile 和 JustCode,希望能够进入 EF 代码,但是当我进入那一行时,我发现自己不在 Single() 扩展方法中,而是在我自己的一个方法调用中 - 那我以为我已经在上一行执行了。显然代码与生产版本并不完全同步。

第 1 课:如果您附加到一个进程,您的执行点可能不在您认为的位置,如果您的代码与之前的代码不同 编译到该进程中。

不管怎样,既然我可以在不反编译任何东西的情况下进入代码,我注意到的第一件事是:

lock (_lockObj)
{
  ...
}

当我试图踏入它时,它就僵在那里了。确凿证据。

所以在某个地方,某个其他线程正在锁定这个对象。查看调用锁的其他地方,导致依赖项的意大利面,以及另一个代码锁定段,有几个数据库调用,甚至是事务边界。它可能是代码锁定/数据库事务死锁,尽管对数据库事务中的代码进行简短扫描未能在事务生命周期内找到任何竞争者来阻止其他任何事情。另外,有证据表明数据库没有显示任何阻塞或打开的事务。更确切地说,这可能只是因为数百个长时间运行的进程排队等待,所有在密码锁内的密码锁,最后看起来就像周五 17:05 的西侧高速公路,有一辆折叠式拖车横跨 3 条车道,接近 GW 大桥。

第 2 课:代码锁是危险的,不仅 - 尤其是 - 与 DB 事务结合使用时。尝试在不使用代码锁的情况下找到使代码线程安全的方法。如果您真的必须使用密码锁,请确保您尽快进出。可以这么说,当你的帖子占据唯一的摊位时,不要给你的帖子阅读杂志。

【讨论】:

    猜你喜欢
    • 2012-11-08
    • 1970-01-01
    • 2013-12-29
    • 1970-01-01
    • 1970-01-01
    • 2015-01-16
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多