首页 > 解决方案 > IIS 应用程序池在中间件记录 OperationCancelledException 约 20 秒后崩溃

问题描述

在我们的生产服务器上,全天间歇性地,我们看到这个被记录下来:

为应用程序池“MyApi”提供服务的进程与 Windows 进程激活服务发生了致命的通信错误。进程 ID 为“29568”。数据字段包含错误号。

我已经看到解决此问题的建议,例如在 IIS 中设置一些 32 位的东西,但我无法在生产级别控制 IIS。另外,我的公司运行着几十个应用程序池,只有我正在调查的一个有这个问题。所以我已经排除了某种 IIS 配置问题。

我已经通过 DebugDiag 运行了故障转储,这就是报告的内容:

在 w3wp.exe.18080.dmp 中,来自 Microsoft Corporation 的 C:\Windows\System32\inetsrv\iiscore.dll 中 iiscore!W3_CONTEXT_BASE::GetIsLastNotification+62 处的汇编指令在尝试读取时导致访问冲突异常 (0xC0000005)线程 94 上的内存位置 0xd7f76008

我试过谷歌搜索 GetIsLastNotification 并在 MS 文档中找到了这个

不要将 PreSendRequestHeaders 与实现 IHttpModule 的托管模块一起使用。设置这些属性可能会导致异步请求出现问题。应用程序请求路由 (ARR) 和 websocket 的组合可能会导致访问冲突异常,从而导致 w3wp 崩溃。例如,iiscore.dll 中的 iiscore!W3_CONTEXT_BASE::GetIsLastNotification+68 导致了访问冲突异常 (0xC0000005)。

它说不要将 PreSendRequestHeaders 与实现 IHttpModule 的模块一起使用。我已经验证了整个应用程序没有代码这样做。我还验证了我们公司所有库中的代码都没有这样做。


这是一件非常有趣和不寻常的事情。每次应用程序池崩溃前大约 20 秒,我看到这个记录:System.OperationCanceledException: The operation was canceled.

我已确定此错误来自某些 Owin 中间件。在应用程序的Startup.cs文件中,我们注册了一个执行一些日志记录的类。OperationCanceledException由于以下代码,它记录了这一点:

// This class inherits from OwinMiddleware

public override async Task Invoke(IOwinContext context)
{
  try
  {
    await this.Next.Invoke(context);
  }
  catch (Exception ex)
  {
    // log stuff
  }
}

这里发生的所有事情都是当一个 http 请求被取消时,await.this.Next.Invoke(context)抛出异常,因为这是它应该做的。这似乎没什么大不了的,但问题归结为:取消请求如何导致应用程序池在大约 20 秒后崩溃?

标签: c#asp.net.netiisowin

解决方案


经过一番努力,终于找到了答案。标题中提到的“20 秒”事情最终成为了一些延迟日志记录的红鲱鱼原因。但以下是应用程序池崩溃的原因。

在应用程序的 Startup.cs 文件中,我们注册了一些 Owin 中间件。中间件看起来像这样:

public override async Task Invoke(IOwinContext context)
{      
  try
  {
    await Next.Invoke(context);
  }
  catch (Exception ex)
  {
    // log the error and return a 500 response
    await LogAndRespond(context, ex);
  }
}

这样做的问题是,当调用此 api 的客户端取消请求时,Next.Invoke(context)会抛出一个OperationCanceledException. 在 catch 块中,我们记录了这个错误,但更重要的是,我们返回了对取消请求的响应

我不完全理解为什么这会导致整个应用程序池崩溃。我猜想中间件尝试响应关闭的连接会导致内存访问冲突。无论如何,解决方案是不发送响应。最终的代码最终看起来像这样。

public override async Task Invoke(IOwinContext context)
{
  try
  {
    await Next.Invoke(context);
  }
  catch (OperationCanceledException)
  {
    // Log the canceled request as info, but do NOT send it a response
    _logger.LogInformation("Request has been canceled: {Url}", context.Request.Uri);
  }
  catch (Exception ex)
  {
    // log the error and return a 500 response
    await LogAndRespond(context, ex);
  }
}

推荐阅读