UseInMemoryOutbox in Masstransit State Machine, Preserve Order of Publish Messages - masstransit

I have a MT state machine which is configured with UseInMemoryOutbox() so any messages that are part of the event handling will be published only after the final step in the pipeline is successfully done, which in my case is, state persistence to Couchbase.
Initially(When(A).
ThenAsync(async context =>
{
context.Instance.IsAutoApprove = context.Data.IsAutoApprove;
IList<Task> publishTasks = context.Data.SomeList.Select(item =>
{
MessageA message = new MessageA
{
Content = "contentA"
};
return context.Publish(message);
}).ToList();
await Task.WhenAll(publishTasks);
}).TransitionTo(B));
WhenEnter(B, f => f.If(c => c.Instance.IsAutoApprove,
callback =>
callback.TransitionTo(C)
));
WhenEnter(C, c => c.Publish<SagaState, MessageC>(context =>
new MessageC
{
Content = "contentC"
}));
I've noticed that the publish messages are published eventually, in an inconsistent order.
For example - assume there are two items within the "SomeList" in the first event, so the messages are published sometimes in this order:
MessageC
MessageA
MessageA
and sometimes
MessageA
MessageA
MessageC
and so on.
The order of the logical flow is not preserved.
Can you please assist me with this behavior?
I'm using
MassTransit 3.5.2
MassTransit.Automatonymous 3.5.2
MassTransit.RabbitMQ 3.5.2
Automatonymous 3.5.11
GreenPipes 1.0.9
Thanks,

This was something that was done for performance reasons, but doesn't really make sense, so I've submitted an issue and closed it.
https://github.com/MassTransit/MassTransit/issues/945
With v4, actions will be executed in order.

Related

MassTransit StateMachine Saga - running behind LoadBalncer, How to stop consuming the same published message more than once

In MassTransit Send and RequestClient will be mapped to exchange or queue, That will be handled by LoadBalanced Consumer.
But for Publish Message, It will be consumed by all the instances that are running and waiting for the Message.
So, In StateMachine, Consumer has to publish the Events, That will make if more than once StateMachine Instance running it will be Picked by both StateMachine and Process will be duplicated? This is what happening at my work. So, We end up running Single StateMachine Instance.
await context.Publish(new
{
context.Message.OrderId,
context.Message.Timestamp,
context.Message.CustomerNumber,
context.Message.PaymentCardNumber,
context.Message.Notes
});
This publishes the events to Saga, if Saga is running in LoadBalancer. Both Instance will be receiving the SameEvent. And Start Processing the Event and changing the Next State.
If this is the Case, How to solve this. Only one StateMachine Should Pick the published message at once.
We end up running Single StateMachine Instance. So, the Published message wont be picked by both instance and will endup haivng duplicate process.
The Current Implmentation:
Have a REST Api - That receives the request to Start the Initial State.
var sendToUri =
new Uri(
$"rabbitMq://{_rabbitMqConfig.Host}/{_rabbitMqConfig.VirtualHost}-{_rabbitMqConfig.WfSagaQueue}");
var endPoint = await bus.GetSendEndpoint(sendToUri);
var req = wfRequest;
await endPoint.Send<IWfExecRequest>(req);
In the StateMachine :
services.AddMassTransit(x =>
{
x.AddConsumer<WfExecRequestConsumer>();
x.AddConsumer<WfTaskCompletedConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
var wfTaskExecHandler = context.GetRequiredService<IWfTaskExecHandler>();
var wfManagementClient = context.GetRequiredService<IWfManagementClient>();
var wfSagaStateMachine = new MsrAutomationStateMachine(wfTaskExecHandler, wfManagementClient);
cfg.Host(HostCredets);
cfg.ReceiveEndpoint(queueName: "msr-automation-wf-exec-request", configureEndpoint: e =>
{
e.PrefetchCount = 1;
e.ConfigureConsumer<WfExecRequestConsumer>(context);
e.StateMachineSaga(wfSagaStateMachine, repo);
});
cfg.ReceiveEndpoint(queueName: "WfTaskCompleted", configureEndpoint: e =>
{
e.PrefetchCount = 1;
e.ConfigureConsumer<WfTaskCompletedConsumer>(context);
});
});
});
This StateMachine Receives , WfExecRequest (Inital Event), TaskCompleted and TaskFaulted (From Muliple Consumer Saga/Consumer) - This was done at Consumer Side as Context.Publish.
So, What I see if we Run more than one Instance of the same StateMachine the TaskCompled Message getting Consumed by both Instances.
Thanks Again.
First, clearly something is wrong with your configuration. If the saga state machine is running on a single queue (receive endpoint, regardless of how many instances of your service are running) it will automatically load balance on that single queue across all running instances.
If you are running multiple instances of the saga state machine on different queues, well yeah, you're basically doing it wrong.
Second, I'm not sure what "LoadBalancer" is but typically something that unnecessary when using a message broker. If "LoadBalancer" is something for your HTTP/API endpoints, that's fine, but the broker and the queue are the scale out points in a message-based system.
If you had posted actual code, or shared some explicit details that would help as it is now it's entirely based on supposition.

MassTransit - GetSendEndpoint for using In Memory

I am beginning with MassTransit, for a publisher/consumer scenario. In production we will be using SQS, however i would like to be able to use "In Memory" for development locally.
I am having trouble with forming the correct Uri for the call to ISendEndpointProvider.GetSendEnpoint(), as per:
//THE SET UP CODE:
x.AddConsumer<MTConsumer, MTMessageConsumerDefinition>()
.Endpoint(e =>
{
// override the default endpoint name
e.Name = "process-input-item";
//... more configurations as per docs here...
})
;
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
//The Publish Code:
var endpoint = await SendEndpointProvider.GetSendEndpoint(new Uri("/ProcessInputItem"));
await endpoint.Send(new MTMessage { InputItemId = item.Id});
Note
I have tried the various cases for the endpoint string.
I do not want to capture the instance of IBus to call Send as that is not the 'closest' instance to the consumer, which according to the docs is important to consider.
Mass Transit document reference: https://masstransit-project.com/usage/configuration.html#receive-endpoints
Thank you for any guidance with this,
Dylan
As explained in the documentation, there are short endpoint addresses which can be used. In your case:
await SendEndpointProvider.GetSendEndpoint(new Uri("queue:process-input-item"));

Lagom publish entity events as Source for websocket connection

Is it possible (and if yes, how), to publish a stream of entity updates directly (without any additional topic) as Source returned from a service like this?
public ServiceCall<NotUsed, Source<EntityPublicEvent, ?>> entityUpdates()
What I'm trying to achieve is to consume those events in Angular 5 over websocket.
#Override
public ServiceCall<String, Source<EntityPublicEvent, ?>> getUpdateNotifications() {
return request -> CompletableFuture.completedFuture(updatesTopic.subscriber().map(this::convertEvent));
}
I can see in Angular gets connected and the service is also called to return the Source but no updates are sent as messages to Angular.
There is also this line appearing in logs, which I suppose could be the reason why I cant get any messages in Angular:
[info] akka.actor.RepointableActorRef
[sourceThread=currency-impl-application-akka.actor.default-dispatcher-3,
akkaTimestamp=06:59:55.778UTC,
akkaSource=akka://currency-impl-application/system/distributed
PubSubMediator, sourceActorSystem=currency-impl-application] - Message
[tcs.currency.impl.CurrencyEvent$Created] without sender to
Actor[akka://currency-impl-application/system/distributedPubSubMediator#128487187]
was not delivered. [1] dead letters encountered. This logging can be
turned off or adjusted with configuration settings
'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
Solved, in this case my Angular component was creating the connection but not listening for messages. I was trying to start with the code provided here https://tutorialedge.net/typescript/angular/angular-websockets-tutorial/, which is an echo service and not just a simple listener.
With this simple code I'm now able to receive the update messages:
let subject = Observable.webSocket('ws://localhost:9000/ws/api/currency/updates');
subject
.retry()
.subscribe(
(msg) => console.log('message received: ' + msg),
(err) => console.log(err),
() => console.log('complete')
);

MassTransit And Service Fabric Stateful Service?

I've been trying to come up with a demo of a website that uses MassTransit with RabbitMQ to post messages to a service running on Service Fabric as a Stateful service.
Everything was going fine, my client would post a message:
IBusControl bus = BusConfigurator.ConfigureBus();
Uri sendToUri = new Uri($"{RabbitMqConstants.RabbitMqUri}" + $"{RabbitMqConstants.PeopleServiceQueue}");
ISendEndpoint endPoint = await bus.GetSendEndpoint(sendToUri);
await endPoint.Send<ICompanyRequest>(new {CompanyId = id });
My consumer in my service fabric service was defined like:
IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), h =>
{
h.Username(RabbitMqConstants.UserName);
h.Password(RabbitMqConstants.Password);
});
cfg.ReceiveEndpoint(host, RabbitMqConstants.PeopleServiceQueue, e =>
{
e.Consumer<PersonInformationConsumer>();
});
});
busControl.Start();
This does allow me to consume the message in my class and I can process it fine. The problem comes when we want to use IReliableDictonary or IReliableQueue or anything that needs to reference the context that is run from the RunAsync function in the service fabric service.
So my question is, how can I configure (is it possible) MassTransit to work within a Stateful Service Fabric Service which knowledge of the service context itself?
Many thanks in advance.
Mike
Update
Ok, I've made some progress on this, if I point the register routines to my message consumer class (eg):
ServiceRuntime.RegisterServiceAsync("ServiceType", context => new PersonInformationConsumer(context)).GetAwaiter().GetResult();
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(PersonInformationConsumer).Name);
Then in my consumer class for my messages I can do the following:
internal sealed class PersonInformationConsumer : StatefulService, IConsumer<ICompanyRequest>
{
private static StatefulServiceContext _currentContext;
#region Constructors
public PersonInformationConsumer(StatefulServiceContext serviceContext) : base(serviceContext)
{
_currentContext = serviceContext;
}
public PersonInformationConsumer() : base(_currentContext)
{
}
I can now successfully call the service message:
ServiceEventSource.Current.ServiceMessage(this.Context, "Message has been consumed, request Id: {0}", context.Message.CompanyId);
The problem I have now is trying to store something on the IReliableDictionary, doing this causes as "Object reference not set to an instance of an object" error :( ... any ideas would be appreciated (although may not read until new year now!)
public async Task Consume(ConsumeContext<ICompanyRequest> context)
{
ServiceEventSource.Current.ServiceMessage(this.Context, "Message has been consumed, request Id: {0}", context.Message.CompanyId);
using (ITransaction tx = StateManager.CreateTransaction())
{
try
{
var myDictionary = await StateManager.GetOrAddAsync<IReliableDictionary<string, long>>("myDictionary");
This is causing the error.... HELP! :)
You'll need to do a bit more to get MassTransit and stateful services working together, there's a few issues to concern yourself here.
Only the master within a stateful partition (n masters within n partitions) will be able to write/update to the stateful service, all replicas will throw exceptions when trying to write back any state. So you'll need to deal with this issue, on the surface it sounds easy until you take in to consideration the master can move around the cluster due to re-balancing the cluster, the default for general service fabric applications is to just turn off the processing on the replicas and only run the work on the master. This is all done by the RunAsync method (try it out, run 3 stateful services with something noddy in the RunAsync method, then terminate the master).
There is also partitioning of your data to consider, due to stateful services scale with partitions, you'll need to create a way to distributing data to separate endpoint on your service bus, maybe have a separate queue that only listens to a given partition range? Say you have a UserCreated message, you might split this on country UK goes to partition 1, US goes to partition 2 etc...
If you just want to get something basic up and running, I'd limit it to one partition and just try putting your bus creation within the the RunAsync and shutdown the bus once a cancelation is requested on the cancelation token.
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), h =>
{
h.Username(RabbitMqConstants.UserName);
h.Password(RabbitMqConstants.Password);
});
cfg.ReceiveEndpoint(host, RabbitMqConstants.PeopleServiceQueue, e =>
{
// Pass in the stateful service context
e.Consumer(c => new PersonInformationConsumer(Context));
});
});
busControl.Start();
while (true)
{
if(cancellationToken.CancellationRequested)
{
//Service Fabric wants us to stop
busControl.Stop();
cancellationToken.ThrowIfCancellationRequested();
}
await Task.Delay(TimeSpan.FromSeconds(1));
}
}

Why am I getting messages in the skipped queue

I have a saga setup in a fork/join configuration.
Events defined on the saga
FileMetadataMsg
FileReadyMsg
SomeOtherMsg
Process starts off when a file comes in on a separate listener.
Publishes SagaStart(correlationId)
Publishes FileSavedToMsg(correlationId, fileLoc)
Publishes FileMetadataMsg(correlationId, metadata)
Publishes FileReadyMsg(correlationId, fileLoc)
Downstream endpoint of does some work on the file
Consumer<FileSavedToMsg>
Publishes SomeOtherMsg(GotTheFileMsg.correlationId, data)
I am getting a FileSavedToMsg in the saga_skipped queue. I can only assume it's due to having a correlationId on the FileSavedToMsg because the saga itself is not using FileSavedToMsg in its state machine and does not have an Event<FileSavedToMsg>.
If this is the reason why...should I be passing the correlationId along in a field other than the CorrelationId, so the saga doesn't see it? I need it somewhere so I can tag SomeOtherMsg with it.
Here is how the saga endpoint is defined
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost/"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ReceiveEndpoint(host, "study_saga", epCfg =>
{
epCfg.StateMachineSaga(machine, repository);
});
});
Here is how the worker endpoint is defined
return Bus.Factory.CreateUsingRabbitMq(x =>
{
var host = x.Host(new Uri("rabbitmq://localhost/"), h =>
{
h.Username("guest");
h.Password("guest");
});
x.ReceiveEndpoint(host, "study_3d_volume_worker", c =>
{
c.PrefetchCount = 1;
c.Instance(_studyCreatedMsgConsumer);
});
});
These are running on the same machine, but in seperate Console/Topshelf applications.
If you are getting messages on a queue that are not consumed by a consumer on that receive endpoint, it might be that you either previously were consuming that message type and removed it from the consumer (or saga, in your case) or you were using the queue from some other purpose and it consumed that message type.
Either way, if you go into the RabbitMQ management console and look for the queue, you can expand the Bindings chevron, click to go to the exchange of the same name (that's a standard MassTransit convention), and then expand the bindings of the exchange to see which message types (the exchanges named like .NET type names) are bound to that exchange.
If you see one that is not consumed by the endpoint, that's the culprit. You can Unbind it using the UI, after which messages published will no longer be sent to the queue.

Resources