From 6a0d87b67dd593343e60c808ffa179bb7f13c0f1 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Thu, 2 Oct 2025 14:03:42 -0600 Subject: [PATCH] Expand platform-level retry checking - Include exception and all inner exceptions in the check - Add checks to activity and entity calls --- .../OutOfProcMiddleware.cs | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs b/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs index 0aeaa9440..944c32395 100644 --- a/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs +++ b/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs @@ -176,12 +176,8 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync( // - a timeout // - an out of memory exception // - a worker process exit - if (functionResult.Exception is Host.FunctionTimeoutException - || functionResult.Exception?.InnerException is SessionAbortedException // see RemoteOrchestrationContext.TrySetResultInternal for details on OOM-handling - || (functionResult.Exception?.InnerException?.GetType().ToString().Contains("WorkerProcessExitException") ?? false)) - { - // TODO: the `WorkerProcessExitException` type is not exposed in our dependencies, it's part of WebJobs.Host.Script. - // Should we add that dependency or should it be exposed in WebJobs.Host? + if (this.IsPlatformLevelError(functionResult)) + { throw functionResult.Exception; } } @@ -205,9 +201,9 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync( OrchestratorExecutionResult orchestratorResult; if (functionResult.Succeeded) { - if (workerRequiresHistory) - { - throw new SessionAbortedException("The worker has since ended the extended session and needs an orchestration history to execute the orchestration request."); + if (workerRequiresHistory) + { + throw new SessionAbortedException("The worker has since ended the extended session and needs an orchestration history to execute the orchestration request."); } orchestratorResult = context.GetResult(); @@ -295,6 +291,40 @@ await this.LifeCycleNotificationHelper.OrchestratorFailedAsync( dispatchContext.SetProperty(orchestratorResult); } + private bool IsPlatformLevelError(FunctionResult functionResult) + { + if (this.ExceptionIsInstanceOrIsCausedBy(functionResult.Exception, checkType: typeof(Host.FunctionTimeoutException)) + || this.ExceptionIsInstanceOrIsCausedBy(functionResult.Exception, checkType: typeof(SessionAbortedException)) // see RemoteOrchestrationContext.TrySetResultInternal for details on OOM-handling + || this.ExceptionIsInstanceOrIsCausedBy(functionResult.Exception, checkTypeString: "WorkerProcessExitException")) + { + // TODO: the `WorkerProcessExitException` type is not exposed in our dependencies, it's part of WebJobs.Host.Script. + // Should we add that dependency or should it be exposed in WebJobs.Host? + return true; + } + + return false; + } + + private bool ExceptionIsInstanceOrIsCausedBy(Exception? sourceException, Type? checkType = null, string? checkTypeString = null) + { + if (sourceException is null) + { + return false; + } + + if (checkType != null && sourceException.GetType().IsAssignableFrom(checkType)) + { + return true; + } + + if (checkTypeString != null && sourceException.GetType().ToString().Contains(checkTypeString)) + { + return true; + } + + return this.ExceptionIsInstanceOrIsCausedBy(sourceException.InnerException, checkType, checkTypeString); + } + /// /// Durable Task Framework entity middleware that invokes an out-of-process orchestrator function. /// @@ -401,6 +431,11 @@ void SetErrorResult(FailureDetails failureDetails) // Re-throw so we can abort this invocation. this.HostLifetimeService.OnStopping.ThrowIfCancellationRequested(); } + + if (this.IsPlatformLevelError(functionResult)) + { + throw functionResult.Exception; + } } catch (Exception hostRuntimeException) { @@ -545,6 +580,11 @@ public async Task CallActivityAsync(DispatchMiddlewareContext dispatchContext, F // Re-throw so we can abort this invocation. this.HostLifetimeService.OnStopping.ThrowIfCancellationRequested(); } + + if (this.IsPlatformLevelError(result)) + { + throw result.Exception; + } } catch (Exception hostRuntimeException) {