ServiceStack-Redis ライブラリによって提供され、ここで説明されているロック メカニズムを使用して DLM を実装しようとしていますが、API が競合状態を示しているようで、複数のクライアントに同じロックを付与することがあります。
BasicRedisClientManager mgr = new BasicRedisClientManager(redisConnStr);
using(var client = mgr.GetClient())
{
client.Remove("touchcount");
client.Increment("touchcount", 0);
}
Random rng = new Random();
Action<object> simulatedDistributedClientCode = (clientId) => {
using(var redisClient = mgr.GetClient())
{
using(var mylock = redisClient.AcquireLock("mutex", TimeSpan.FromSeconds(2)))
{
long touches = redisClient.Get<long>("touchcount");
Debug.WriteLine("client{0}: I acquired the lock! (touched: {1}x)", clientId, touches);
if(touches > 0) {
Debug.WriteLine("client{0}: Oh, but I see you've already been here. I'll release it.", clientId);
return;
}
int arbitraryDurationOfExecutingCode = rng.Next(100, 2500);
Thread.Sleep(arbitraryDurationOfExecutingCode); // do some work of arbitrary duration
redisClient.Increment("touchcount", 1);
}
Debug.WriteLine("client{0}: Okay, I released my lock, your turn now.", clientId);
}
};
Action<Task> exceptionWriter = (t) => {if(t.IsFaulted) Debug.WriteLine(t.Exception.InnerExceptions.First());};
int arbitraryDelayBetweenClients = rng.Next(5, 500);
var clientWorker1 = new Task(simulatedDistributedClientCode, 1);
var clientWorker2 = new Task(simulatedDistributedClientCode, 2);
clientWorker1.Start();
Thread.Sleep(arbitraryDelayBetweenClients);
clientWorker2.Start();
Task.WaitAll(
clientWorker1.ContinueWith(exceptionWriter),
clientWorker2.ContinueWith(exceptionWriter)
);
using(var client = mgr.GetClient())
{
var finaltouch = client.Get<long>("touchcount");
Console.WriteLine("Touched a total of {0}x.", finaltouch);
}
mgr.Dispose();
上記のコードを実行して、2 つのクライアントが同じ操作を短い時間連続して試行することをシミュレートすると、3 つの出力が考えられます。最初のケースは、Mutex が適切に機能し、クライアントが適切な順序で処理を進める最適なケースです。2 番目のケースは、2 番目のクライアントがロックの取得を待機中にタイムアウトになった場合です。こちらも納得の結果。ただし、問題はarbitraryDurationOfExecutingCode
、ロックを取得するためのタイムアウトに近づくかそれを超えると、1 番目のクライアントがロックを解放する前に 2 番目のクライアントがロックを許可される状況を非常に簡単に再現でき、次のような出力が生成されることです。
client1: ロックを取得しました! (タッチ: 0x)
client2: ロックを取得しました! (タッチ: 0x)
client1: オーケー、ロックを解除しました。今度はあなたの番です。
client2: オーケー、ロックを解除しました。今度はあなたの番です。
合計2回タッチ。
API とそのドキュメントについての私の理解では、ロックを取得するときの引数は、ロックを取得timeOut
するためのタイムアウトです。この状態を防ぐためだけに、実行中のコードの継続時間よりも常に長い値を推測する必要がある場合、それはかなりエラーが発生しやすいようです。ロックを永久に待機するために null を渡す以外に回避策がある人はいますか? 私は間違いなくそれをしたくありません。そうしないと、クラッシュしたワーカーからゴーストロックが発生することを知っています。timeOut