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);
}
}
}