Particular / NServiceBus.Storage.MongoDB

NServiceBus MongoDB persistence
Other
4 stars 6 forks source link

SagaPersister has issues with SagaId when using global GuidSerializer #613

Open rvriens opened 5 months ago

rvriens commented 5 months ago

Describe the bug

Description

Combination of Guid Serializer as string with Saga does not work with Timeouts.

https://github.com/Particular/NServiceBus.Storage.MongoDB/blob/04be6e3ef8a0565a3dcb1283198e41aab4c36ac1/src/NServiceBus.Storage.MongoDB/Sagas/SagaPersister.cs#L67

Expected behavior

Start an Saga on receive.

Set timeout: await RequestTimeout(context, TimeSpan.FromSeconds(30));

When timeout message is received the Saga is found (by SagaId) and can be resolved.

Using the global guid serializer should not change de behavior of the saga.

Actual behavior

Normal behavior without the guid serializer: SagaId/_id is saved in mongo as Binary like Binary.createFromBase64('peWT50SIxUOJcrDZAJixTg==', 3)

When using global guidserializer as string this is converted to string like "eaa845b0-da1b-4fb2-b263-b0d90098b0a7"

In this case the document is not found. BsonValue.Create does not use the registered serializers. var document = await storageSession.Find<TSagaData>(new BsonDocument(elementName, BsonValue.Create(elementValue))).ConfigureAwait(false);

When receiving timeout we get the exception: "No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired"

Versions

NServicebus.Core 7.8.4.0 NServicebus.Storage.MongoDB 2.3.2.0 MongoDB.Driver 2.23.1.0

Steps to reproduce

Register global guid serializer. BsonSerializer.RegisterSerializationProvider(new GuidSerializerProvider());

  public class GuidSerializerProvider : IBsonSerializationProvider
  {
      public IBsonSerializer GetSerializer(Type type)
      {
          if (type == typeof(Guid))
              return new GuidSerializer(BsonType.String);
          return null!;
      } 
}

Create Saga with DemoData

public class DemoSaga : Saga<DemoData>,
        IAmStartedByMessages<StartCommand>,
        IHandleTimeouts<DemoTimeoutState>
{
public async Task Handle(StartCommandmessage, IMessageHandlerContext context)
{
await RequestTimeout<DemoTimeoutState>(context, TimeSpan.FromSeconds(30));
}
public async Task Handle(DemoTimeoutState, IMessageHandlerContext context)
{
MarkAsComplete();
}
}

The saga will not be resolved and still exists after 30 seconds.

Relevant log output

No response

Additional Information

Workarounds

As work around do not use the RegisterSerializationProvider for guids.

Possible solutions

Replace: https://github.com/Particular/NServiceBus.Storage.MongoDB/blob/04be6e3ef8a0565a3dcb1283198e41aab4c36ac1/src/NServiceBus.Storage.MongoDB/Sagas/SagaPersister.cs#L71C13-L72C1 var document = await storageSession.Find<TSagaData>(new BsonDocument(elementName, BsonValue.Create(elementValue))).ConfigureAwait(false);

With:

var bsonSerializer = BsonSerializer.LookupSerializer(elementValue.GetType());
var document = await storageSession.Find<TSagaData>(new BsonDocument(elementName, serializer.ToBsonValue(elementValue)).ConfigureAwait(false);

Or convert the Saga::Id to string.

Additional information

helenktsai commented 5 months ago

Hi @rvriens,

Thanks for bringing this to our attention. We will add it to the backlog and fix it in a future release.