Skip to content

Commit fb3c991

Browse files
authored
Support failed completion building for activity (#353)
Fixes #349
1 parent b33105e commit fb3c991

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

src/Temporalio/Converters/DefaultFailureConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected DefaultFailureConverter(DefaultFailureConverterOptions options) =>
4141
public DefaultFailureConverterOptions Options { get; private init; }
4242

4343
/// <inheritdoc />
44-
public Failure ToFailure(Exception exception, IPayloadConverter payloadConverter)
44+
public virtual Failure ToFailure(Exception exception, IPayloadConverter payloadConverter)
4545
{
4646
// If the exception is not already a failure exception, make it an application exception
4747
var failureEx =
@@ -76,7 +76,7 @@ exception as FailureException
7676
}
7777

7878
/// <inheritdoc />
79-
public Exception ToException(Failure failure, IPayloadConverter payloadConverter)
79+
public virtual Exception ToException(Failure failure, IPayloadConverter payloadConverter)
8080
{
8181
// If encoded attributes are present and we can decode the attributes, set them as
8282
// expected

src/Temporalio/Worker/ActivityWorker.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,16 @@ private async Task ExecuteActivityAsync(
223223
{
224224
completion = new()
225225
{
226+
TaskToken = tsk.TaskToken,
226227
Result = new()
227228
{
228229
Failed = new()
229230
{
230-
Failure_ = new() { Message = $"Failed building completion: {e}" },
231+
Failure_ = new()
232+
{
233+
Message = $"Failed building completion: {e}",
234+
ApplicationFailureInfo = new() { Type = e.GetType().Name },
235+
},
231236
},
232237
},
233238
};

tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Temporalio.Tests.Worker;
1010
using Temporalio.Activities;
1111
using Temporalio.Api.Common.V1;
1212
using Temporalio.Api.Enums.V1;
13+
using Temporalio.Api.Failure.V1;
1314
using Temporalio.Api.History.V1;
1415
using Temporalio.Client;
1516
using Temporalio.Client.Schedules;
@@ -5792,6 +5793,79 @@ await ExecuteWorkerAsync<NullWithCodecWorkflow>(
57925793
client);
57935794
}
57945795

5796+
[Workflow]
5797+
public class ActivityFailToFailWorkflow
5798+
{
5799+
public static TaskCompletionSource WaitingForCancel { get; } = new();
5800+
5801+
[Activity]
5802+
public static async Task WaitForCancelAsync()
5803+
{
5804+
WaitingForCancel.SetResult();
5805+
while (!ActivityExecutionContext.Current.CancellationToken.IsCancellationRequested)
5806+
{
5807+
ActivityExecutionContext.Current.Heartbeat();
5808+
await Task.Delay(100);
5809+
}
5810+
throw new InvalidOperationException("Intentional exception");
5811+
}
5812+
5813+
[WorkflowRun]
5814+
public Task RunAsync() =>
5815+
Workflow.ExecuteActivityAsync(
5816+
() => WaitForCancelAsync(),
5817+
new()
5818+
{
5819+
StartToCloseTimeout = TimeSpan.FromSeconds(10),
5820+
CancellationType = ActivityCancellationType.WaitCancellationCompleted,
5821+
RetryPolicy = new() { MaximumAttempts = 1 },
5822+
HeartbeatTimeout = TimeSpan.FromSeconds(1),
5823+
});
5824+
}
5825+
5826+
public class CannotSerializeIntentionalFailureConverter : DefaultFailureConverter
5827+
{
5828+
public override Failure ToFailure(Exception exception, IPayloadConverter payloadConverter)
5829+
{
5830+
if (exception.Message == "Intentional exception")
5831+
{
5832+
throw new InvalidOperationException("Intentional conversion failure");
5833+
}
5834+
return base.ToFailure(exception, payloadConverter);
5835+
}
5836+
}
5837+
5838+
[Fact]
5839+
public async Task ExecuteWorkflowAsync_ActivityFailToFail_ProperlyHandled()
5840+
{
5841+
// Need client with failure converter
5842+
var newOptions = (TemporalClientOptions)Client.Options.Clone();
5843+
newOptions.DataConverter = DataConverter.Default with
5844+
{
5845+
FailureConverter = new CannotSerializeIntentionalFailureConverter(),
5846+
};
5847+
var client = new TemporalClient(Client.Connection, newOptions);
5848+
await ExecuteWorkerAsync<ActivityFailToFailWorkflow>(
5849+
async worker =>
5850+
{
5851+
var handle = await client.StartWorkflowAsync(
5852+
(ActivityFailToFailWorkflow wf) => wf.RunAsync(),
5853+
new(id: $"workflow-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!));
5854+
// Wait until activity started
5855+
await ActivityFailToFailWorkflow.WaitingForCancel.Task;
5856+
// Issue cancel and wait result
5857+
await handle.CancelAsync();
5858+
var err = await Assert.ThrowsAsync<WorkflowFailedException>(() =>
5859+
handle.GetResultAsync());
5860+
var errAct = Assert.IsType<ActivityFailureException>(err.InnerException);
5861+
var errFail = Assert.IsType<ApplicationFailureException>(errAct.InnerException);
5862+
Assert.Contains("Failed building completion", errFail.Message);
5863+
Assert.Contains("Intentional conversion failure", errFail.Message);
5864+
},
5865+
new TemporalWorkerOptions().AddAllActivities<ActivityFailToFailWorkflow>(null),
5866+
client);
5867+
}
5868+
57955869
[Workflow]
57965870
public class DetachedCancellationWorkflow
57975871
{

0 commit comments

Comments
 (0)