0

Using Azure Service Bus as transport, but scheduled messages not working other than when calling from inside an IConsumer.

I spent hours and days and still have little idea what is going on.

Can someone explain what I need to do to get schedules working from the state machine using azure service bus? And perhaps why schedule message works from IConsumer context but not anywhere else.

 public class BatchCollector : MassTransitStateMachine<BufferSaga>
{
    public BatchCollector(IBatchFactory batchFactory)
    {
        InstanceState(saga => saga.State);
        Event(() => BufferedCommandDetected,
            _ => _.CorrelateById(context => context.Message.GetBatchId()));

       Schedule(() => WindowElapsed, x => x.BatchCompletionId, x =>
        {
            x.Delay = TimeSpan.FromSeconds(5);
            x.Received = e => e.CorrelateById(context => context.Message.CorrelationId);
        });


        Initially(
            When(BufferedCommandDetected)
                .Then(
                    context =>
                    {
                        context.Instance.CorrelationId = context.Data.GetBatchId();
                        context.Instance.Id = Guid.NewGuid().ToString("N");
                        context.Instance.Buffer.Add(context.Data);
                        context.Instance.BatchStartTime = DateTimeOffset.Now;
                        context.Instance.AbsoluteDeadLine = DateTimeOffset.Now + context.Data.AbsoluteWindowSpan;
                        context.Instance.SlidingDeadLine = DateTimeOffset.Now + context.Data.SlidingWindowSpan;
                    })
                .Schedule(WindowElapsed,
                    context => new WindowElapsed {CorrelationId = context.Instance.CorrelationId },
                    delayProvider: scheduleDelayProvider => scheduleDelayProvider.Data.SlidingWindowSpan < scheduleDelayProvider.Data.AbsoluteWindowSpan ? scheduleDelayProvider.Data.SlidingWindowSpan : scheduleDelayProvider.Data.AbsoluteWindowSpan)
                .TransitionTo(Waiting));

        During(Waiting,
            When(BufferedCommandDetected)
                .Then(context =>
                {
                    context.Instance.SlidingDeadLine += context.Data.SlidingWindowSpan;
                    context.Instance.Buffer.Add(context.Data);
                }),
            When(WindowElapsed.Received, context => context.Instance.SlidingDeadLine > DateTimeOffset.Now && context.Instance.AbsoluteDeadLine > DateTimeOffset.Now)
                .Schedule(WindowElapsed, context => new WindowElapsed { CorrelationId = context.Instance.CorrelationId }),
            When(WindowElapsed.Received, context => context.Instance.SlidingDeadLine <= DateTimeOffset.Now || context.Instance.AbsoluteDeadLine <= DateTimeOffset.Now)
                //.Unschedule(WindowElapsed)
                .Publish(context => new Batch()
                {
                    BatchId = context.Instance.BatchCompletionId ?? Guid.NewGuid(),
                    Content = context.Instance.Buffer,
                    StartTime = context.Instance.BatchStartTime,
                    EndTime = DateTimeOffset.Now
                })
                .Finalize()
                .TransitionTo(BufferCompleted));

        SetCompletedWhenFinalized();
    }

    public Event<BufferedCommand> BufferedCommandDetected { get; private set; }


    public Schedule<BufferSaga, WindowElapsed> WindowElapsed { get; private set; }

    public State Waiting { get; private set; }

    public State BufferCompleted { get; private set; }
}

The bus init:

container.RegisterType<IBusControl>(
            new HierarchicalLifetimeManager(),
            new InjectionFactory(c =>
            {
                var bus = Bus.Factory.CreateUsingAzureServiceBus(
                    cfg =>
                    {
                        var azSbHost = cfg.Host(new Uri(CloudConfigurationManager.GetSetting("ServiceBus.Url"))
                            , host =>
                            {
                                host.TokenProvider = TokenProvider
                                    .CreateSharedAccessSignatureTokenProvider
                                    (CloudConfigurationManager.GetSetting("ServiceBus.SharedAccessKeyName"),
                                        CloudConfigurationManager.GetSetting("ServiceBus.AccessKey"),
                                        TokenScope.Namespace);
                            });

                        cfg.ReceiveEndpoint(
                            azSbHost,
                            "Quartz.Scheduler",
                            sbConfig =>
                                {
                                    cfg.UseMessageScheduler(sbConfig.InputAddress);
                                    sbConfig.Consumer(() => new ScheduleMessageConsumer(c.Resolve<IScheduler>()));
                                }
                        );

                        cfg.ReceiveEndpoint(
                            azSbHost,
                            Assembly.GetExecutingAssembly().GetName().Name,
                            sbConfig =>
                            {
                                AllClasses.FromAssembliesInBasePath()
                                    .Where(
                                        @class =>
                                            (@class?.Namespace?.StartsWith("bcn",
                                                 StringComparison.OrdinalIgnoreCase) ?? false)
                                            &&
                                            @class.GetParentClasses()
                                                .Any(
                                                    parent =>
                                                            parent.Name.StartsWith("MassTransitStateMachine`1")))
                                    .ForEach(@class =>
                                    {
                                        //dynamic cast to avoid having to deal with generic typing when type is not known until runtime.                                                
                                        dynamic stateMachineExtension =
                                            new DynamicStaticWrapper(typeof(StateMachineSubscriptionExtensions));
                                        stateMachineExtension
                                            .StateMachineSaga(
                                                sbConfig,
                                                c.Resolve(@class),
                                                c.Resolve(typeof(ISagaRepository<>).MakeGenericType(
                                                    @class.GetParentClasses().First(parent =>
                                                                parent.Name.StartsWith("MassTransitStateMachine`1"))
                                                        .GetGenericArguments().First())));
                                    });



                                AllClasses.FromAssembliesInBasePath()
                                    .Where(
                                        @class =>
                                            (@class?.Namespace?.StartsWith("bcn", StringComparison.OrdinalIgnoreCase) ??
                                             false)
                                            && @class.GetInterfaces().Any(
                                                @interface =>
                                                    @interface?.FullName?.StartsWith("MassTransit.IConsumer`1") ??
                                                    false))
                                    .ForEach(@class =>
                                    {
                                        var factoryType = typeof(UnityConsumerFactory<>).MakeGenericType(@class);
                                        //Automatically register consumers.
                                        dynamic consumerFactory = Activator.CreateInstance(
                                            factoryType,
                                            container);
                                        var consumingMethod = typeof(ConsumerExtensions).
                                            GetMethods()
                                            .First(
                                                m =>
                                                    m.Name == "Consumer" && m.IsGenericMethod &&
                                                    m.GetGenericArguments().Length == 1 &&
                                                    m.GetParameters().Length == 3)
                                            .MakeGenericMethod(@class)
                                            .Invoke(null, new object[] {sbConfig, consumerFactory, null});

                                        //Automatically detect which payload contains message data. This message data is stored in blob.
                                        @class.GetInterfaces().Where(
                                                @interface =>
                                                        @interface.FullName.StartsWith("MassTransit.IConsumer`1"))
                                            .Select(@interface => @interface.GetGenericArguments().First())
                                            .Where(payload => payload.GetProperties()
                                                .Any(prop => prop.PropertyType.Name.StartsWith("MessageData`1")))
                                            .ForEach(
                                                BlobType =>
                                                    typeof(MessageDataConfiguratorExtensions)
                                                        .GetMethods()
                                                        .First(
                                                            method =>
                                                                method.GetParameters().First().ParameterType ==
                                                                typeof(IConsumePipeConfigurator)
                                                                &&
                                                                method.GetParameters().Last().ParameterType ==
                                                                typeof(IMessageDataRepository))
                                                        .MakeGenericMethod(BlobType)
                                                        .Invoke(null,
                                                            new object[]
                                                                {sbConfig, c.Resolve<IMessageDataRepository>()}));
                                    });
                            });

                        cfg.UseServiceBusMessageScheduler();
                        //azSbHost.
                    });

                return bus;
            }));
        container.RegisterType<IBus, IBusControl>();
        container.RegisterType<IBus, IBusControl>(new ContainerControlledLifetimeManager());

And then started:

  var container = UnityConfig.GetConfiguredContainer();
        var bus = container.Resolve<IBusControl>();
        bus.Start();

        var scheduler = container.Resolve<IScheduler>();
        scheduler.Start();

        bus.Publish<BufferedCommand>(new BufferedCommandAdapter<decimal>(10m, TimeSpan.FromSeconds(5),
            TimeSpan.FromSeconds(5)));
Alwyn
  • 8,079
  • 12
  • 59
  • 107

1 Answers1

0

Are you setting up the job factory for quartz? Take a look at how the QuartzIntegration library does the setup:

https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit.QuartzIntegration/QuartzIntegrationExtensions.cs

Also, use the observer around the bus so that quartz is started/paused/stopped inline with the bus.

https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit.QuartzIntegration/Configuration/SchedulerBusObserver.cs

Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
  • Chris after a long time, it seems the RavenDb saga provider is broken :( I can't get it to work. In memory it works fine. Looking at the project it hasn't been updated in 4 years... that's not confidence inspiring. – Alwyn Sep 12 '16 at 02:39
  • I think there is a newer one perhaps designed for MT3. – Chris Patterson Sep 12 '16 at 02:40
  • There's this project https://github.com/alexeyzimarev/MassTransit.RavenDbIntegration The nuget package is corrupted so I just download the source and reference directly. Seems to work better so far. The lock is still slower than I'd like and hence tendency to run in parallel when events should be joined. Good enough for now. – Alwyn Sep 13 '16 at 13:25