Messaging
urihendler
—
2016-08-17T14:19:56Z —
#1
Here's something that bit me, maybe you can add a note to the docs on the wiki (https://github.com/ServiceStack/ServiceStack/wiki/Messaging)
The example looks like this:
public class AppHost : AppSelfHostBase
{
public override void Configure(Container container)
{
//Register to use a Rabbit MQ Server
container.Register<IMessageService>(c => new RabbitMqServer());
var mqServer = container.Resolve<IMessageService>();
//Register message handlers here...
mqServer.RegisterHandler<HandleExampleMessageRequest>(ServiceController.ExecuteMessage);
//Start the Rabbit MQ Server listening for incoming MQ Requests
mqServer.Start();
}
}
However, if your message handler calls a service, which publishes a new message, you could get a NullReferenceException on the first message or two (depending how busy the queue is).
public class ExampleService : Service
{
public void Post(HandleExampleMessageRequest request)
{
//Do stuff here
//This throws NRE on the first call if the MQ service is started in AppHost.Configure
PublishMessage(new DoNextStep
{
Id = request.Id
});
}
}
Stack trace
System.NullReferenceException: Object reference not set to an instance of an object.
at ServiceStack.ServiceStackHost.GetMessageProducer(IRequest req)
at ServiceStack.Service.get_MessageProducer()
at ServiceStack.Service.PublishMessage[T](T message)
at ServiceInterface.ExampleService.Post(HandleExampleMessageRequest request) in ExampleService.cs:line 10
at lambda_method(Closure , Object , Object )
at ServiceStack.Host.ServiceExec`1.<>c__DisplayClass6.<CreateExecFn>b__5(Object service, Object request)
at ServiceStack.Host.ServiceRunner`1.Execute(IRequest request, Object instance, TRequest requestDto)
This is because the IMessageFactory
(which is automatically registered in ServiceStackHost.OnAfterInit
) is not available until the AppHost has completed initializing.
To avoid this problem, the MQ service should be started in an AfterInitCallback.
public class AppHost : AppSelfHostBase
{
public AppHost()
: base("My Services", new[] { typeof(HandleExampleMessageRequest).Assembly })
{
AfterInitCallbacks.Add(StartMqServer);
}
private void StartMqServer(IAppHost appHost)
{
//Start MQ consumers
appHost.GetContainer().Resolve<IMessageService>().Start();
}
}
mythz
—
2016-08-17T14:39:23Z —
#2
Looks like an odd race condition (not experienced myself) where the Code on the main thread doesn't complete before the IO Request to fetch a message from the MQ completes. Are you doing any blocking calls after mqServer.Start()
that's slowing AppHost.Configure()
down?
You could register the MessageFactory yourself which IMO is preferable:
container.Register(c => mqServer.MessageFactory);
Otherwise it's easier if you just queue up starting in AppHost.Configure()
, e.g:
AfterInitCallbacks.Add(host => mqServer.Start());
But I've just added a fallback in GetMessageProducer() so it resolves IMessageService
if IMessageFactory
wasn't registered yet.
urihendler
—
2016-08-18T11:02:14Z —
#3
I tried moving the mqServer.Start()
call right to the end of the Configure
method, but still had the problem.
I see that the auto-registration of the IMessageFactory
happens after plugins are loaded.
I have a few plugins that look through my service model assemblies for all types implementing a certain interface, then do something for each type.
Loading these plugins can take a few seconds, which is long enough for the MQ consumer threads to start processing messages.
I used the AfterInitCallbacks
solution in the end, but the new fallback solves the problem either way.