ASP.NET デッドロック 検知機能について (2)

こんにちは d99 です。
だいぶ間が空いてしまいましたが、続きを。お待たせしてしまってすみません。

前回、イベントソースが W3-SVC で 「デッドロックが検出されました」 というイベントログが記録された時、IIS では何が起きているのか、を解説しました。

ASP.NET デッドロック 検知機能について (1)
https://blogs.msdn.com/b/d99/archive/2009/03/10/9468749.aspx

今回は、わざとデッドロック検知を動作させるコツについて記載します。

サーバー環境は、Windows Server 2003、IIS 6.0 を使用します。これは 2008 以降では ASP.NET を統合した影響で、ASP.NET でのデッドロック検知動作に変更が行われたためです。.NET Framework は 2.0 を使ってください。なお、以下にはシステム全体に影響を及ぼす設定もあるため、必ずテスト環境等で行うようにしてください。

今回はまず簡単な方法をご紹介します。machine.config を含めた設定も変更し、出来る限りサクッと現象を起こせるような方法です。端的に言うと、デッドロック検知が起きるまでスリープさせてしまおう、というわけです。まず、以下のような test.aspx を配置し、ASP.NET 2.0 でアプリケーションとして動くよう設定します。

<%@ Page Language="C#" AutoEventWireup="true" EnableSessionState="False" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void Button1_Click(object sender, EventArgs e)
    {
        System.AppDomain ap = System.AppDomain.CurrentDomain;
lock (ap)
{
System.Threading.Thread.Sleep(int.Parse(TextBox1.Text));
}
    }
</script>

<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="TextBox1" runat="server">100000</asp:TextBox>
        <asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
    </div>
    </form>
</body>
</html>

ポイントは2か所、まず EnableSessionState を False にし、セッションを使わないようにします。これは、セッションが有効な状態だと、同じセッション ID の要求は順次処理されてしまい(前の処理が終わるまで次の処理開始が待たされる)、サーバー側の同時実行数を使い切れないためです。次に AppDomain でロックをかけます。そのため、後続の要求は全てここで詰まる事になります。一番最初のリクエストだけが Sleep() まで入り、指定した秒数だけスリープします。

次に以下の web.confg を配置します。

<?xml version="1.0"?>
<configuration>
    <system.web>
        <compilation debug="true" />
    </system.web>
</configuration>

ここのポイントは、debug 属性を True にする事で、スクリプトタイムアウトを抑制している事です。これを抑制しておかないと、既定の110秒でリクエストがエラーになってしまいます。エラーも応答として判断されるので 「指定時間、応答が一切返らなければデッドロックと見なす」 というルールが動作しなくなってしまいます。

最後に machine.config を変更します。。%WinDir%\Microsoft.NET\Framework\v2.0.50727\CONFIG  または %WinDir%\Microsoft.NET\Framework64\v2.0.50727\CONFIG の machine.config をテキストエディタで開き、processModel という要素を検索します。

変更前:
    <system.web>
        <processModel autoConfig="true" />

変更後: 4CPU 環境の場合
    <system.web>
        <processModel autoConfig="false" maxWorkerThreads="5" responseDeadlockInterval="00:00:30" />
        <httpRuntime minFreeThreads="15" minLocalRequestFreeThreads="15" />

まず、autoConfig を false にし、同時実行数の制御を自動設定(1CPU あたり 12 同時実行) から手動設定に変更します。その上で、responseDeadlockInterval を 30秒 にし、30秒でデッドロック検知が動作するようにします。次に、maxWorkerThreads を 5 に減らします。そして minFreeThreads と minLocalRequestFreeThreads を増やし、同時実行数を1~10程度になるように設定します。以下の計算式を使ってください。

計算式:(maxWorkerThreads x CPU 数) - 同時実行数 = minFreeThreads と minLocalRequestFreeThreads
例) (5 x 4) - 5 = 15

ここまで出来たら一度 IIS を IISRESET コマンドなどで再起動します。そして、test.aspx へブラウザでアクセスします。ボタンにフォーカスを移し、Enter キーを押し続けます。こうすると、要求の再送が大量に行われ、あっというまにスレッドを使い切ります。10回以上の再送が行われたと思ったら、あとは数秒おきにポンポンとEnter を押しておきます。

タスクマネージャーで w3wp.exe を見ていると、30秒ほどが経過すると新しいプロセスが起動してくるのがご確認頂けます。イベントログを見ると、目的のログが記録されているはずです。もし負荷のかけ方がピンと来ないという場合は、パフォーマンスモニタで、ASP.NET Apps v2.0.50727 の Request Executing を監視してください。Enter を押している間、これが増加していき、設定した同時実行数で頭打ちになればスレッドを使い切った事になります。

次回はこの状態をデバッグするとどう見えるのか、といった点を書きたいと思います。

ではまた。
d99 でした。