using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; [assembly: InternalsVisibleTo("NT8.Core.Tests")] [assembly: InternalsVisibleTo("NT8.Integration.Tests")] namespace NT8.Core.Execution { /// /// Circuit breaker implementation for execution systems to prevent cascading failures /// public class ExecutionCircuitBreaker { private readonly ILogger _logger; private readonly NT8.Core.Logging.ILogger _sdkLogger; private readonly object _lock = new object(); private CircuitBreakerStatus _status; private DateTime _lastFailureTime; private int _failureCount; private DateTime _nextRetryTime; private readonly TimeSpan _timeout; private readonly int _failureThreshold; private readonly TimeSpan _retryTimeout; private readonly Queue _executionTimes; private readonly int _latencyWindowSize; private readonly Queue _rejectionTimes; private readonly int _rejectionWindowSize; // Log helpers — route through whichever logger is available private void LogDebug(string message) { if (_logger != null) _logger.LogDebug(message); else if (_sdkLogger != null) _sdkLogger.LogDebug(message); } private void LogInfo(string message) { if (_logger != null) _logger.LogInformation(message); else if (_sdkLogger != null) _sdkLogger.LogInformation(message); } private void LogWarn(string message) { if (_logger != null) _logger.LogWarning(message); else if (_sdkLogger != null) _sdkLogger.LogWarning(message); } private void LogErr(string message) { if (_logger != null) _logger.LogError(message); else if (_sdkLogger != null) _sdkLogger.LogError(message); } /// /// Constructor accepting NT8.Core.Logging.ILogger. /// Use this overload from NinjaScript (.cs) files — no Microsoft.Extensions.Logging reference required. /// public ExecutionCircuitBreaker( NT8.Core.Logging.ILogger sdkLogger, int failureThreshold = 3, TimeSpan? timeout = null, TimeSpan? retryTimeout = null, int latencyWindowSize = 100, int rejectionWindowSize = 10) { _sdkLogger = sdkLogger; _logger = null; _status = CircuitBreakerStatus.Closed; _failureCount = 0; _lastFailureTime = DateTime.MinValue; _timeout = timeout ?? TimeSpan.FromSeconds(30); _retryTimeout = retryTimeout ?? TimeSpan.FromSeconds(5); _failureThreshold = failureThreshold; _latencyWindowSize = latencyWindowSize; _rejectionWindowSize = rejectionWindowSize; _executionTimes = new Queue(); _rejectionTimes = new Queue(); } /// /// Constructor accepting Microsoft.Extensions.Logging.ILogger. /// Use this overload from DLL projects and unit tests. /// internal ExecutionCircuitBreaker( ILogger logger, int failureThreshold = 3, TimeSpan? timeout = null, TimeSpan? retryTimeout = null, int latencyWindowSize = 100, int rejectionWindowSize = 10) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; _sdkLogger = null; _status = CircuitBreakerStatus.Closed; _failureCount = 0; _lastFailureTime = DateTime.MinValue; _timeout = timeout ?? TimeSpan.FromSeconds(30); _retryTimeout = retryTimeout ?? TimeSpan.FromSeconds(5); _failureThreshold = failureThreshold; _latencyWindowSize = latencyWindowSize; _rejectionWindowSize = rejectionWindowSize; _executionTimes = new Queue(); _rejectionTimes = new Queue(); } /// Records execution time for latency monitoring. public void RecordExecutionTime(TimeSpan latency) { try { lock (_lock) { _executionTimes.Enqueue(latency); while (_executionTimes.Count > _latencyWindowSize) _executionTimes.Dequeue(); if (_status == CircuitBreakerStatus.Closed && HasExcessiveLatency()) TripCircuitBreaker("Excessive execution latency detected"); } } catch (Exception ex) { LogErr(string.Format("Failed to record execution time: {0}", ex.Message)); throw; } } /// Records an order rejection. public void RecordOrderRejection(string reason) { if (string.IsNullOrEmpty(reason)) reason = "Unknown"; try { lock (_lock) { _rejectionTimes.Enqueue(DateTime.UtcNow); while (_rejectionTimes.Count > _rejectionWindowSize) _rejectionTimes.Dequeue(); if (_status == CircuitBreakerStatus.Closed && HasExcessiveRejections()) TripCircuitBreaker(string.Format("Excessive order rejections: {0}", reason)); } } catch (Exception ex) { LogErr(string.Format("Failed to record order rejection: {0}", ex.Message)); throw; } } /// Returns true if an order should be allowed through. public bool ShouldAllowOrder() { try { lock (_lock) { switch (_status) { case CircuitBreakerStatus.Closed: return true; case CircuitBreakerStatus.Open: if (DateTime.UtcNow >= _nextRetryTime) { _status = CircuitBreakerStatus.HalfOpen; LogWarn("Circuit breaker transitioning to Half-Open state"); return true; } LogDebug("Circuit breaker is Open - blocking order"); return false; case CircuitBreakerStatus.HalfOpen: LogDebug("Circuit breaker is Half-Open - allowing test order"); return true; default: return false; } } } catch (Exception ex) { LogErr(string.Format("Failed to check ShouldAllowOrder: {0}", ex.Message)); throw; } } /// Returns the current circuit breaker state. public CircuitBreakerState GetState() { try { lock (_lock) { return new CircuitBreakerState( _status != CircuitBreakerStatus.Closed, _status, GetStatusReason(), _failureCount); } } catch (Exception ex) { LogErr(string.Format("Failed to get state: {0}", ex.Message)); throw; } } /// Resets the circuit breaker to Closed state. public void Reset() { try { lock (_lock) { _status = CircuitBreakerStatus.Closed; _failureCount = 0; _lastFailureTime = DateTime.MinValue; LogInfo("Circuit breaker reset to Closed state"); } } catch (Exception ex) { LogErr(string.Format("Failed to reset circuit breaker: {0}", ex.Message)); throw; } } /// Call after a successful order submission. public void OnSuccess() { try { lock (_lock) { if (_status == CircuitBreakerStatus.HalfOpen) { Reset(); LogInfo("Circuit breaker reset after successful test operation"); } } } catch (Exception ex) { LogErr(string.Format("Failed to handle OnSuccess: {0}", ex.Message)); throw; } } /// Call after a failed order submission. public void OnFailure() { try { lock (_lock) { _failureCount++; _lastFailureTime = DateTime.UtcNow; if (_status == CircuitBreakerStatus.HalfOpen || (_status == CircuitBreakerStatus.Closed && _failureCount >= _failureThreshold)) { TripCircuitBreaker("Failure threshold exceeded"); } } } catch (Exception ex) { LogErr(string.Format("Failed to handle OnFailure: {0}", ex.Message)); throw; } } private void TripCircuitBreaker(string reason) { _status = CircuitBreakerStatus.Open; _nextRetryTime = DateTime.UtcNow.Add(_timeout); LogWarn(string.Format("Circuit breaker TRIPPED: {0}. Will retry at {1}", reason, _nextRetryTime)); } private bool HasExcessiveLatency() { if (_executionTimes.Count < 3) return false; var avgLatency = TimeSpan.FromMilliseconds(_executionTimes.Average(ts => ts.TotalMilliseconds)); return avgLatency.TotalSeconds > 5.0; } private bool HasExcessiveRejections() { if (_rejectionTimes.Count < _rejectionWindowSize) return false; var recentWindow = TimeSpan.FromMinutes(1); var recentRejections = _rejectionTimes.Count(dt => DateTime.UtcNow - dt <= recentWindow); return recentRejections >= _rejectionWindowSize; } private string GetStatusReason() { switch (_status) { case CircuitBreakerStatus.Closed: return "Normal operation"; case CircuitBreakerStatus.Open: return string.Format("Tripped due to failures. Count: {0}, Last: {1}", _failureCount, _lastFailureTime); case CircuitBreakerStatus.HalfOpen: return "Testing recovery after timeout"; default: return "Unknown"; } } /// Returns average execution latency. public TimeSpan GetAverageExecutionTime() { try { lock (_lock) { if (_executionTimes.Count == 0) return TimeSpan.Zero; return TimeSpan.FromMilliseconds(_executionTimes.Average(ts => ts.TotalMilliseconds)); } } catch (Exception ex) { LogErr(string.Format("Failed to get average execution time: {0}", ex.Message)); throw; } } /// Returns rejection rate as a percentage. public double GetRejectionRate() { try { lock (_lock) { if (_rejectionTimes.Count == 0) return 0.0; var oneMinuteAgo = DateTime.UtcNow.AddMinutes(-1); var recentRejections = _rejectionTimes.Count(dt => dt >= oneMinuteAgo); return (double)recentRejections / _rejectionWindowSize * 100.0; } } catch (Exception ex) { LogErr(string.Format("Failed to get rejection rate: {0}", ex.Message)); throw; } } } }