2

サービスにサーキット ブレーカーを実装しています。すべてのアクションをタイムアウトでラップするという事実を除いて、これは非常に標準的なサーキット ブレーカーです。そのため、タイムアウトが多すぎるとサーキットも中断します。サーキット ブレーカーがハーフ オープン状態になると、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;
        }
4

0 に答える 0