BackgroundJob.ContinueWith - JobContinuationOptions.OnAnyFinishedState - Not Firing On Parent Failure

Subject says it all. Is this a bug?

Continuations using option JobContinuationOptions.OnAnyFinishedState do not consider Failed to be a finished state. Only Deleted and Succeeded are considered finished states (by continuations).

If you manually delete the failed parent job (via the dashboard) you should see you continuation job being processed.

You can configure the parent job to automatically be deleted once the retry count is exceeded by using the AutomaticRetryAttribute:

[AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)]

I also found this behaviour confusing, and only worked out what was going on by reading the source code. it would be great if JobContinuationOptions could be extended to include an option for triggering continuation on parent job failure without having to delete the parent job.

2 Likes

To get this to work, you have to decorate your method with the ContinuationsSupportAttribute. This takes a HashSet<string> to detect which states are final states. The best would be, if the attribute could be used as follows:

[ContinuationsSupport(SucceededState.StateName, DeletedState.StateName, FailedState.StateName)]
public void DoSomeCleanUp() { ... }

Unfortunately this is currently not supported, cause attribute parameters have to be const all the way down, which boils it down to strings, int, double, etc and arrays of them (but not lists, collections or hashsets). So to fix this issue, there must be two things done within the Hangfire code:

  1. Change the state names from static readonly to const which could have bad effects like mentioned here and here.
  2. Change the constructor parameter to either take a string[] or even better a params string[] (to support the above example), but in the latter case you could get into trouble with the most specialized constructor that takes the stateChanger as last parameter and params arguments have to be the last one in the order.

Nevertheless, while these problems are still open (and will maybe never been solved due to compatibility reasons) you can help yourself with your own attribute:

public class ContinuationsSupportIncludingFailedStateAttribute : ContinuationsSupportAttribute
{
    public ContinuationsSupportIncludingFailedStateAttribute()
        : this(new[] { SucceededState.StateName, DeletedState.StateName, FailedState.StateName })
    { }

    public ContinuationsSupportIncludingFailedStateAttribute(
        string[] knownFinalStates)
        : base(new HashSet<string>(knownFinalStates))
    { }
}

And apply this to your function:

[ContinuationsSupportIncludingFailedState]
public void DoSomeCleanUp() { ... }
2 Likes