Skip to content

When an activity has a fault depending on how it's created there a different events raised #7372

@ByronMayne

Description

@ByronMayne

Description

If you subscribe to the IEventHandler<ActivityExecuting> and IEventHandler<ActivityExecuted> to track everything before and after an activity runs you can get different behaviour depending on how the fault is created.

For example setting the fault like

protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
   context.Fault(new Exception())
}

In this case you would get an event for ActivityExecuting and ActivityExecuted. However if you do this

protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
    throw new Exception();
}

You will only get ActivityExecuting because the Exception bubbles up the pipeline and skips NotificationPublishingMiddleware and gets processed by the ExceptionHandlingMiddleware.

Steps to Reproduce

Create a notification class and subscribe to both events, have one activity throw an another set the fault. Notice the difference.

internal class SuperHandler :
    INotificationHandler<ActivityExecuting>,
    INotificationHandler<ActivityExecuted>
{
    public Task HandleAsync(ActivityExecuted notification, CancellationToken cancellationToken)
    {
        // This gets called only if the activity does not use a `throw` statement to throw an exception.
        return Task.CompletedTask;
    }

    public Task HandleAsync(ActivityExecuting notification, CancellationToken cancellationToken)
    {
        // This gets called for every activity 
        return Task.CompletedTask;
    }
}

Reproduction Rate: 100% of the time

Expected Behavior

This could be fixed in a few different ways

  1. Move the exception handler up in the pipeline, I don't think this is a good idea as it won't catch exception from other middleware.
  2. Handle exceptions from activities at the invocation level and use the exception handler for just exceptions at the middleware level.
  3. Add a new event ActivityFaulted that looks like
public record ActivityFaulted(ActivityExecutionContext ActivityExecutionContext, Exception Falut) : INotification;

you would need to pass the exception because depending on how the fault is created the context would not be in the faulted state yet.

Actual Behavior

You can't know (unless you write your own middleware) when events complete but become faulted due to an unhandled exception

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions