using Microsoft.Extensions.Logging; namespace Managing.Core.Exceptions; /// /// Helper class to detect and handle Orleans-specific exceptions /// public static class OrleansExceptionHelper { /// /// Checks if an exception is an Orleans-specific exception /// public static bool IsOrleansException(Exception ex) { if (ex == null) return false; var exceptionTypeName = ex.GetType().Name; var exceptionMessage = ex.Message ?? string.Empty; // Check for Orleans exception type names if (exceptionTypeName.Contains("Orleans", StringComparison.OrdinalIgnoreCase)) { return true; } // Check for Orleans-specific exception messages if (exceptionMessage.Contains("Orleans", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("grain", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("silo", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("activation", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("forwarding failed", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("Unable to create local activation", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("Request timeout", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("Deadlock", StringComparison.OrdinalIgnoreCase)) { return true; } // Check inner exception recursively if (ex.InnerException != null) { return IsOrleansException(ex.InnerException); } return false; } /// /// Converts an Orleans exception to a user-friendly message /// public static string GetUserFriendlyMessage(Exception ex, string operation = "operation") { if (!IsOrleansException(ex)) { return ex.Message; } var exceptionTypeName = ex.GetType().Name; var exceptionMessage = ex.Message ?? string.Empty; // Handle timeout exceptions if (ex is TimeoutException || ex is TaskCanceledException || exceptionMessage.Contains("timeout", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("timed out", StringComparison.OrdinalIgnoreCase)) { return $"The {operation} timed out. This may occur when closing positions takes longer than expected. Please try again in a moment."; } // Handle deadlock exceptions if (exceptionMessage.Contains("deadlock", StringComparison.OrdinalIgnoreCase)) { return $"The {operation} could not complete due to a system conflict. Please try again in a moment."; } // Handle activation/grain errors if (exceptionMessage.Contains("activation", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("grain", StringComparison.OrdinalIgnoreCase) || exceptionMessage.Contains("forwarding failed", StringComparison.OrdinalIgnoreCase)) { return $"The {operation} encountered a temporary system issue. Please try again in a moment."; } // Generic Orleans error return $"The {operation} encountered a temporary system issue. Please try again in a moment. If the problem persists, contact support."; } /// /// Wraps an async operation with timeout and Orleans exception handling /// public static async Task ExecuteWithTimeoutAndOrleansHandling( Func> operation, TimeSpan timeout, string operationName, ILogger logger = null) { try { using var cts = new CancellationTokenSource(timeout); var task = operation(); var timeoutTask = Task.Delay(timeout, cts.Token); var completedTask = await Task.WhenAny(task, timeoutTask); if (completedTask == timeoutTask) { throw new TimeoutException($"The {operationName} timed out after {timeout.TotalSeconds} seconds."); } return await task; } catch (Exception ex) when (IsOrleansException(ex) || ex is TimeoutException) { logger?.LogError(ex, "Orleans exception or timeout during {OperationName}: {ExceptionType} - {Message}", operationName, ex.GetType().Name, ex.Message); var userMessage = GetUserFriendlyMessage(ex, operationName); throw new ServiceUnavailableException(userMessage); } } }