elsa-workflows / elsa-workflows/elsa-core
NaN in Output leads to Exception
- Dominant language
- C#
- Stars
- 7.9k
- Forks
- 1.5k
- Avg merge
- 15h 22m
- Merged PRs (30d)
- 114
Description
## Description
When I use a NaN directly in a Output or in an Object what has variables of type double inside it leeds to excpetions in combination with Elsa Studio
## Steps to Reproduce
- Create a simple activity that set a double.NaN to a double Output
```
[Activity("Elsa.Tests", nameof(NanTestActivity), Category = "Test", DisplayName = "NaNTest", Version = 1)]
public class NanTestActivity : Activity
{
[Output(DisplayName = "Result")]
public Output Result { get; set; } = default!;
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
var result = double.NaN;
context.Set(Result, result);
}
}
```
- Create a workflow in Elsa Studio that just uses this activity
- Start the workflow
- In the instance view of the workflow click on the activity
- you won't see a value in the output and the following exeption is logged:
```
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.ArgumentException: .NET number values such as positive and negative infinity cannot be written as valid JSON. To make it work when using 'JsonSerializer', consider specifying 'JsonNumberHandling.AllowNamedFloatingPointLiterals' (see https://docs.microsoft.com/dotnet/api/system.text.json.serialization.jsonnumberhandling).
at System.Text.Json.ThrowHelper.ThrowArgumentException_ValueNotSupported()
at System.Text.Json.Utf8JsonWriter.WriteNumberValue(Double value)
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.TryWriteAsObject(Utf8JsonWriter writer, Object value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.Converters.DictionaryDefaultConverter`3.OnWriteResume(Utf8JsonWriter writer, TDictionary value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonDictionaryConverter`3.OnTryWrite(Utf8JsonWriter writer, TDictionary dictionary, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at Elsa.Workflows.Api.Endpoints.ActivityExecutions.Get.Endpoint.HandleAsync(CancellationToken cancellationToken)
at FastEndpoints.Endpoint`2.ExecAsync(CancellationToken ct)
at FastEndpoints.Endpoint`2.ExecAsync(CancellationToken ct)
at Elsa.Http.Middleware.HttpWorkflowsMiddleware.InvokeAsync(HttpContext httpContext, IServiceProvider serviceProvider)
at Elsa.Workflows.Api.Middleware.JsonSerializationErrorHandlerMiddleware.InvokeAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
```

- if a variable is linked to the output the following exception will happen before and no state will be visible in the instance view:
```
warn: Elsa.Workflows.Middleware.Workflows.EngineExceptionHandlingMiddleware[0]
An exception was caught from a downstream middleware component
System.ArgumentException: .NET number values such as positive and negative infinity cannot be written as valid JSON. To make it work when using 'JsonSerializer', consider specifying 'JsonNumberHandling.AllowNamedFloatingPointLiterals' (see https://docs.microsoft.com/dotnet/api/system.text.json.serialization.jsonnumberhandling).
at System.Text.Json.ThrowHelper.ThrowArgumentException_ValueNotSupported()
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Serialize(Utf8JsonWriter writer, T& rootValue, Object rootValueBoxed)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsObject(Utf8JsonWriter writer, Object rootValue)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Serialize(Utf8JsonWriter writer, T& rootValue, Object rootValueBoxed)
at System.Text.Json.JsonSerializer.WriteNode[TValue](TValue& value, JsonTypeInfo`1 jsonTypeInfo)
at System.Text.Json.JsonSerializer.SerializeToNode[TValue](TValue value, JsonSerializerOptions options)
at Elsa.Workflows.WorkflowInstanceStorageDriver.<>c__DisplayClass7_0.b__0(VariablesDictionary dictionary)
at Elsa.Workflows.WorkflowInstanceStorageDriver.UpdateVariablesDictionary(StorageDriverContext context, Action`1 update)
at Elsa.Workflows.WorkflowInstanceStorageDriver.WriteAsync(String id, Object value, StorageDriverContext context)
at Elsa.Workflows.VariablePersistenceManager.SaveVariablesAsync(WorkflowExecutionContext workflowExecutionContext)
at Elsa.Workflows.Runtime.Middleware.Workflows.PersistentVariablesMiddleware.InvokeAsync(WorkflowExecutionContext context)
at Elsa.Workflows.Runtime.Middleware.Workflows.PersistWorkflowExecutionLogMiddleware.InvokeAsync(WorkflowExecutionContext context)
at Elsa.Workflows.Runtime.Middleware.Workflows.PersistActivityExecutionLogMiddleware.InvokeAsync(WorkflowExecutionContext context)
at Elsa.Workflows.Runtime.Middleware.Workflows.PersistBookmarkMiddleware.InvokeAsync(WorkflowExecutionContext context)
at Elsa.Workflows.Middleware.Workflows.EngineExceptionHandlingMiddleware.InvokeAsync(WorkflowExecutionContext context)
```
- I had one more exception following these two, but at the moment I am not able to reproduce and is maybe related to my investigation.
Workflow:
[nan-test.json](https://github.com/user-attachments/files/20127368/nan-test.json)
## Expected Behavior
Would be great, if I can use NaN.
## Environment
- **Elsa Package Version**: 3.3.5.
- **Operating System**: Windows 11
- **Browser and Version**: Microsoft Edge
## Troubleshooting Attempts
I tried to add a JsonSerializerOption to set the demanded Option
```
services.AddTransient(provider =>
{
var options = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
};
return options;
});
```
But I reckognized that this is not passed to Elsa.Workflows.WorkflowInstanceStorageDriver
So i tried the following:
```
public ValueTask WriteAsync(string id, object value, StorageDriverContext context)
{
var options = payloadSerializer.GetOptions();
UpdateVariablesDictionary(context, dictionary =>
{
var node = JsonSerializer.SerializeToNode(value, **options**);
dictionary[id] = node;
});
return ValueTask.CompletedTask;
}
```
This seems to solve the variable topic, but I don't know what influence this change can have.
Next investigation leads to Elsa.Common.Serialization.ConfigurableSerializer which I try to change to:
```
private JsonSerializerOptions CreateOptionsInternal()
{
var options = ServiceProvider.GetService() ?? new();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.PropertyNameCaseInsensitive = true;
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(JsonMetadataServices.TimeSpanConverter);
options.Converters.Add(new IntegerJsonConverter());
options.Converters.Add(new BigIntegerJsonConverter());
options.Converters.Add(new DecimalJsonConverter());
return options;
}
```
So that the external Options are used. But I am aware that this might not be intended. This would be the next test on monday.
Hope that helps.
Thank you a lot!
Best regards
Maui
Contributor guide
Research direction
Start with Elsa.Workflows.WorkflowInstanceStorageDriver.WriteAsync and Elsa.Common.Serialization.ConfigurableSerializer, then trace the ActivityExecutions.Get endpoint involved in the output failure. Reproduce the workflow with a double.NaN output and with a variable linked to it. Done means both persistence and instance-view serialization handle the value without exceptions, with regression coverage added where the relevant tests reside.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100