ZiggyCreatures / FusionCache

FusionCache is an easy to use, fast and robust hybrid cache with advanced resiliency features.
MIT License
1.91k stars 97 forks source link

[BUG] Null value issue if return type is discriminated union #120

Closed akoken closed 1 year ago

akoken commented 1 year ago

Hi again @jodydonetti,

I'm experiencing a problem with the GetOrSetAsync method and can't figure out what's causing it. I personally don't like throwing exception in my code. That's why I prefer ErrorOr package to be able to use discriminated union. But the problem is that Fusion Cache returns null if I use ErrorOr type. That appears to be a serialization issue, however there is no serialization error log in Fusion Cache. Is there something I'm missing?

Steps to reproduce:

  1. Run the application and call both endpoints ( Memory + Distributed)
  2. Stop the application ( Distributed)
  3. Run the application again and call both endpoints.

The first endpoint loads data from distributed to memory and then returns it successfully. The second one loads data from distributed to memory successfully but returns null.

You may find the related code here to reproduce.

Versions I've encountered this issue on:

Additional context There is a catch here. The one with the ErrorOr type keeps working until I stop the application for the first time.

jodydonetti commented 1 year ago

Hi @akoken and thanks for using FusionCache!

This is not a FusionCache bug, but a problem with the ErrorOr library and json serialization, or at least with the json System.Text.Json serializer.

To be more precise, the ErrorOr<int> values seem to be serializable but not deserializable.

See this sample:

using ErrorOr;
using System.Text.Json;

ErrorOr<int> e1 = 42;
ErrorOr<int> e2 = Error.NotFound();

Console.WriteLine($"e1: {e1.Value}/{e1.IsError}");
Console.WriteLine($"e2: {e2.Value}/{e2.IsError}");

var j1 = JsonSerializer.Serialize(e1);
var j2 = JsonSerializer.Serialize(e2);

Console.WriteLine("------");

Console.WriteLine($"j1: {j1}");
Console.WriteLine($"j2: {j2}");

e1 = JsonSerializer.Deserialize<ErrorOr<int>>(j1);
e2 = JsonSerializer.Deserialize<ErrorOr<int>>(j2);

Console.WriteLine("------");

Console.WriteLine($"e1: {e1.Value}/{e1.IsError}");
Console.WriteLine($"e2: {e2.Value}/{e2.IsError}");

The output of this is:

e1: 42/False
e2: 0/True
------
j1: {"IsError":false,"Errors":[{"Code":"ErrorOr.NoErrors","Description":"Error list cannot be retrieved from a successful ErrorOr.","Type":1,"NumericType":1}],"ErrorsOrEmptyList":[],"Value":42,"FirstError":{"Code":"ErrorOr.NoFirstError","Description":"First error cannot be retrieved from a successful ErrorOr.","Type":1,"NumericType":1}}
j2: {"IsError":true,"Errors":[{"Code":"General.NotFound","Description":"A \u0027Not Found\u0027 error has occurred.","Type":4,"NumericType":4}],"ErrorsOrEmptyList":[{"Code":"General.NotFound","Description":"A \u0027Not Found\u0027 error has occurred.","Type":4,"NumericType":4}],"Value":0,"FirstError":{"Code":"General.NotFound","Description":"A \u0027Not Found\u0027 error has occurred.","Type":4,"NumericType":4}}
------
e1: 0/False
e2: 0/False

As you can see, the serialized json seems to be there, but when deserializing it doesn't "come back" correctly.

I assume you can solve this by using a custom converter.

Regarding the fact that FusionCache is not throwing serialization errors: that is because, as you can see in the sample above, the System.Text.Json serializer is not throwing an error, but simply creating an ErrorOr instance with default values (0 and false), so FusionCache cannot possibly know that something went "wrong".

Let me know if you need more help.

Hope this helps!

akoken commented 1 year ago

Thanks for the quick and detailed reply @jodydonetti.

As you can see, the serialized json seems to be there, but when deserializing it doesn't "come back" correctly.

To be honest, I did not expect that :)

I assume you can solve this by using a custom converter.

Yes, that could work!

Regarding the fact that FusionCache is not throwing serialization errors: that is because, as you can see in the sample above, the System.Text.Json serializer is not throwing an error, but simply creating an ErrorOr instance with default values (0 and false), so FusionCache cannot possibly know that something went "wrong".

I'm not sure, but record struct can cause it. I'll dig deeper into ErrorOr source code.

Thank you for taking the time!