消失的EF召唤之谜

本文关键字:召唤 EF 消失 | 更新日期: 2023-09-27 17:51:10

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

我们试着在QA系统上重现这个问题(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"之后,线程就消失了。消失得无影无踪。我在数据库上放置了一个嗅探器,没有触发任何查询;不用说,没有涉及到DB锁。没有抛出异常。代码进入实体框架后就再也没有离开过。

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

现在,我可以回收应用程序池或重新启动IIS,这可能会掩盖问题。但一周前也发生过类似的事情,当时我们也无法追踪。所以我们重新设置IIS,希望问题会消失。确实如此,持续了一个星期。现在它又回来了。

有没有人有任何想法,它是如何可能的线程简单地蒸发像这样?诺曼·贝茨躲在英孚的门后吗?

消失的EF召唤之谜

考虑到线程没有神奇地蒸发,我们可以推测一些更可能的选项:

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

事实证明,EF部分终究是转移注意力。我去下载了Telerik的JustDecompile和JustCode,希望能进入EF代码,但是当我进入那一行时,我发现自己不在Single()扩展方法中,而是在我自己的一个方法调用中——我认为我已经在前一行执行了。显然,这段代码与生产中的版本并不完全同步。

第1课:如果你附加到一个进程,如果你的代码与原来的代码不相同,你的执行点可能不在你认为的地方编译到那个进程

无论如何,现在我可以进入代码而不反编译任何东西,我注意到的第一件事是:
lock (_lockObj)
{
  ...
}

当我想踩进去的时候,它就僵住了。确凿的证据。

在某个地方,其他线程正在锁定这个对象。查看调用锁的其他地方,导致依赖关系的意大利面,以及另一个代码锁定的段,几个DB调用,甚至一个事务边界。它可能是代码锁/db事务死锁,尽管对db事务中的代码进行了简短扫描,但未能在事务的生命周期内找出任何阻塞其他事物的竞争者。此外,有证据表明数据库没有显示任何阻塞或开放的事务。更确切地说,这可能只是几百个长时间运行的进程排队的事实,所有的代码锁都在代码锁内,最后看起来就像周五17:05的西区高速公路,一辆被夹带的拖车卡车躺在3条车道上,接近GW桥。

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