Skip to content

Commit

Permalink
Support failed completion building for activity (temporalio#353)
Browse files Browse the repository at this point in the history
  • Loading branch information
cretz authored Oct 8, 2024
1 parent b33105e commit fb3c991
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/Temporalio/Converters/DefaultFailureConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected DefaultFailureConverter(DefaultFailureConverterOptions options) =>
public DefaultFailureConverterOptions Options { get; private init; }

/// <inheritdoc />
public Failure ToFailure(Exception exception, IPayloadConverter payloadConverter)
public virtual Failure ToFailure(Exception exception, IPayloadConverter payloadConverter)
{
// If the exception is not already a failure exception, make it an application exception
var failureEx =
Expand Down Expand Up @@ -76,7 +76,7 @@ exception as FailureException
}

/// <inheritdoc />
public Exception ToException(Failure failure, IPayloadConverter payloadConverter)
public virtual Exception ToException(Failure failure, IPayloadConverter payloadConverter)
{
// If encoded attributes are present and we can decode the attributes, set them as
// expected
Expand Down
7 changes: 6 additions & 1 deletion src/Temporalio/Worker/ActivityWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,16 @@ private async Task ExecuteActivityAsync(
{
completion = new()
{
TaskToken = tsk.TaskToken,
Result = new()
{
Failed = new()
{
Failure_ = new() { Message = $"Failed building completion: {e}" },
Failure_ = new()
{
Message = $"Failed building completion: {e}",
ApplicationFailureInfo = new() { Type = e.GetType().Name },
},
},
},
};
Expand Down
74 changes: 74 additions & 0 deletions tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Temporalio.Tests.Worker;
using Temporalio.Activities;
using Temporalio.Api.Common.V1;
using Temporalio.Api.Enums.V1;
using Temporalio.Api.Failure.V1;
using Temporalio.Api.History.V1;
using Temporalio.Client;
using Temporalio.Client.Schedules;
Expand Down Expand Up @@ -5792,6 +5793,79 @@ await ExecuteWorkerAsync<NullWithCodecWorkflow>(
client);
}

[Workflow]
public class ActivityFailToFailWorkflow
{
public static TaskCompletionSource WaitingForCancel { get; } = new();

[Activity]
public static async Task WaitForCancelAsync()
{
WaitingForCancel.SetResult();
while (!ActivityExecutionContext.Current.CancellationToken.IsCancellationRequested)
{
ActivityExecutionContext.Current.Heartbeat();
await Task.Delay(100);
}
throw new InvalidOperationException("Intentional exception");
}

[WorkflowRun]
public Task RunAsync() =>
Workflow.ExecuteActivityAsync(
() => WaitForCancelAsync(),
new()
{
StartToCloseTimeout = TimeSpan.FromSeconds(10),
CancellationType = ActivityCancellationType.WaitCancellationCompleted,
RetryPolicy = new() { MaximumAttempts = 1 },
HeartbeatTimeout = TimeSpan.FromSeconds(1),
});
}

public class CannotSerializeIntentionalFailureConverter : DefaultFailureConverter
{
public override Failure ToFailure(Exception exception, IPayloadConverter payloadConverter)
{
if (exception.Message == "Intentional exception")
{
throw new InvalidOperationException("Intentional conversion failure");
}
return base.ToFailure(exception, payloadConverter);
}
}

[Fact]
public async Task ExecuteWorkflowAsync_ActivityFailToFail_ProperlyHandled()
{
// Need client with failure converter
var newOptions = (TemporalClientOptions)Client.Options.Clone();
newOptions.DataConverter = DataConverter.Default with
{
FailureConverter = new CannotSerializeIntentionalFailureConverter(),
};
var client = new TemporalClient(Client.Connection, newOptions);
await ExecuteWorkerAsync<ActivityFailToFailWorkflow>(
async worker =>
{
var handle = await client.StartWorkflowAsync(
(ActivityFailToFailWorkflow wf) => wf.RunAsync(),
new(id: $"workflow-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!));
// Wait until activity started
await ActivityFailToFailWorkflow.WaitingForCancel.Task;
// Issue cancel and wait result
await handle.CancelAsync();
var err = await Assert.ThrowsAsync<WorkflowFailedException>(() =>
handle.GetResultAsync());
var errAct = Assert.IsType<ActivityFailureException>(err.InnerException);
var errFail = Assert.IsType<ApplicationFailureException>(errAct.InnerException);
Assert.Contains("Failed building completion", errFail.Message);
Assert.Contains("Intentional conversion failure", errFail.Message);
},
new TemporalWorkerOptions().AddAllActivities<ActivityFailToFailWorkflow>(null),
client);
}

[Workflow]
public class DetachedCancellationWorkflow
{
Expand Down

0 comments on commit fb3c991

Please sign in to comment.