サービスにサーキット ブレーカーを実装しています。すべてのアクションをタイムアウトでラップするという事実を除いて、これは非常に標準的なサーキット ブレーカーです。そのため、タイムアウトが多すぎるとサーキットも中断します。サーキット ブレーカーがハーフ オープン状態になると、MonitorTryEnter
メソッドを使用して、1 つのスレッドのみがアクションを実行するようにします (サービスが回復したかどうかを確認するため)。以下のスニペットを見つけてください (これはハーフオープン状態で実行されるコードです):
EDIT(機能コード全体を貼り付け)
public async Task<T> ExecuteWithCircuitBreaker<T>(Func<Task<T>> func)
{
if (!_circuitBreakerStateManager.IsInState(CircuitBreakerState.Closed))
{
if (HalfOpen())
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_halfOpenSyncObject, ref lockTaken);
if (lockTaken)
{
var timeoutTask = Task.Delay(_options.ActionTimeout);
var resultTask = func.Invoke();
var firstFinishedTask = await Task.WhenAny(timeoutTask, resultTask);
if (firstFinishedTask == timeoutTask)
{
throw new TimeoutException($"Operation reached timeout configured for this circuit breaker. Timeout: {_options.ActionTimeout.TotalSeconds} s");
}
else
{
Reset();
return await resultTask;
}
}
}
catch (Exception ex)
{
Trip(ex);
throw;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_halfOpenSyncObject);
}
}
}
throw new CircuitBreakerOpenException(_circuitBreakerStateManager.LastException);
}
// Circuit Breaker is closed - execute function normally
try
{
var timeoutTask = Task.Delay(_options.ActionTimeout);
var resultTask = func.Invoke();
var firstFinishedTask = await Task.WhenAny(timeoutTask, resultTask);
if (firstFinishedTask == timeoutTask)
throw new TimeoutException($"Operation reached timeout configured for this circuit breaker. Timeout: {_options.ActionTimeout.TotalSeconds} s");
else
return await resultTask;
}
catch (Exception ex)
{
Trip(ex);
throw;
}
}
上記のスニペットでわかるように、TryEnter は Try/Finally でラップされているため、何があってもロックが解放されます。私が抱えている問題は、ある時点Monitor.TryEnter
ですべての試行に対して false を返し始めることです。コードをデバッグしたところ、発生する前にすべてのMonitor.TryEnter
呼び出しでMonitor.Exit
が実行されることがわかりました。つまり、ロックを解除する必要がありますが、この時点ではすべての呼び出しが false を返すわけではないようです。これがどのように可能かはわかりません。
何らかの理由でロックが解除されない可能性はMonitor.Exit
ありますか (ただし、例外はスローされません)。それとも、私はここで何か間違ったことをしていますか?
編集。これが _halfOpenSyncObject の宣言方法です (このクラスのインスタンスは 1 つだけです)。
public class TimeoutableCircuitBreaker: ICircuitBreaker
{
private readonly object _halfOpenSyncObject = new object();
...
HalfOpen 実装の編集:
private bool HalfOpen()
{
var isHalfOpen = _circuitBreakerStateManager.GetLastStateChangedDate() + _options.BreakDuration < DateTime.UtcNow;
if (isHalfOpen)
_circuitBreakerStateManager.SetState(CircuitBreakerState.HalfOpen);
return isHalfOpen;
}