どの Web アプリケーションがロード バランサーからトラフィックを処理するかを制御する Web アプリケーションがあります。Web アプリケーションは、個々のサーバーで実行されます。
ASP.NET アプリケーション状態のオブジェクト内の各アプリケーションの "in または out" 状態を追跡し、状態が変更されるたびに、オブジェクトはディスク上のファイルにシリアル化されます。状態は、Web アプリケーションの開始時にファイルから逆シリアル化されます。
サイト自体は 1 秒に数件のリクエストしか取得せず、ファイルへのアクセスはめったにありませんが、何らかの理由で、ファイルの読み取りまたは書き込み中に衝突が非常に発生しやすいことがわかりました。サーバーへのローリング展開を定期的に行う自動化されたシステムがあるため、このメカニズムは非常に信頼できるものである必要があります。
上記のいずれかの慎重さに疑問を呈するコメントをする前に、その背後にある理由を説明すると、この投稿がこれまでよりもずっと長くなってしまうので、山を動かすことは避けたいと思います.
つまり、ファイルへのアクセスを制御するために使用するコードは次のようになります。
internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no
/// locking collisions caused by attempting to save and load the file simultaneously
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />.
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
var l = new Logger();
if (ServerState._lock.WaitOne(1500, false))
{
l.LogInformation( "Got lock to read/write file-based server state."
, (Int32)VipEvent.GotStateLock);
var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
, FileAccess.ReadWrite, FileShare.None);
result = func.Invoke(fileStream);
fileStream.Close();
fileStream.Dispose();
fileStream = null;
ServerState._lock.ReleaseMutex();
l.LogInformation( "Released state file lock."
, (Int32)VipEvent.ReleasedStateLock);
return true;
}
else
{
l.LogWarning( "Could not get a lock to access the file-based server state."
, (Int32)VipEvent.CouldNotGetStateLock);
result = null;
return false;
}
}
これは通常は機能しますが、ミューテックスにアクセスできない場合があります (ログに「ロックを取得できませんでした」というイベントが表示されます)。これをローカルで再現することはできません。実稼働サーバー (Win Server 2k3/IIS 6) でのみ発生します。タイムアウトを削除すると、後続のリクエストを含め、アプリケーションが無期限にハングします (競合状態??)。
エラーが発生した場合、イベント ログを見ると、ミューテックス ロックが達成され、エラーがログに記録される前に以前の要求によって解放されたことがわかります。
ミューテックスは Application_Start イベントでインスタンス化されます。宣言で静的にインスタンス化すると、同じ結果が得られます。
言い訳、言い訳: スレッド化/ロックは私の得意分野ではありません。通常は気にする必要がないからです。
ランダムに信号を取得できない理由について何か提案はありますか?
アップデート:
適切なエラー処理を追加しましたが (恥ずかしいことです!)、それでも同じエラーが発生します。記録としては、未処理の例外が問題になることはありませんでした。
ファイルにアクセスするプロセスは 1 つだけです。このアプリケーションの Web プールに Web ガーデンは使用せず、他のアプリケーションはファイルを使用しません。私が考えることができる唯一の例外は、アプリ プールがリサイクルされ、新しい WP が作成されたときに古い WP がまだ開いている場合ですが、タスク マネージャーを見て、ワーカー プロセスが 1 つしかないときに問題が発生することがわかります。
@mmr: Monitor の使用と Mutex の使用の違いは何ですか? MSDN のドキュメントに基づくと、効果的に同じことを行っているように見えます。Mutex でロックを取得できない場合は、falseを返すだけで正常に失敗します。
注意すべきもう 1 つの点: 私が抱えている問題は完全にランダムなようです。1 つの要求で失敗した場合、次の要求ではうまくいく可能性があります。パターンもないようです (確かに、少なくとも他のすべてではありません)。
更新 2:
このロックは、他の呼び出しには使用されません。_lock が InvokeOnFile メソッドの外部で参照されるのは、インスタンス化されるときだけです。
呼び出される Func は、ファイルから読み取ってオブジェクトに逆シリアル化するか、オブジェクトをシリアル化してファイルに書き込みます。どちらの操作も別のスレッドでは行われません。
ServerState.PATH は静的な読み取り専用フィールドであり、同時実行の問題が発生することはないと思います。
また、これをローカル (Cassini) で再現できないという以前の指摘を繰り返したいと思います。
学んだ教訓:
- 適切なエラー処理を使用してください (当たり前!)
- 仕事に適したツールを使用してください(そして、そのツールが何をどのように行うかについての基本的な理解を持ってください). sambo が指摘しているように、Mutex を使用すると明らかに多くのオーバーヘッドが発生し、それが私のアプリケーションで問題を引き起こしていましたが、Monitor は .NET 専用に設計されています。