MassTransit: MassTransit Quartz integration breaks Quartz jobs
Is this a bug report?
Yes
Can you also reproduce the problem with the latest version?
Yes
Environment
- Operating system: Windows 10
- Visual Studio version: Rider 2020.3
- Dotnet version: 5.0-rc1
Steps to Reproduce
- Add Quartz with AspNetCore integration
- Add MassTransit and configure message scheduler
- Add any Quartz job with injected service (DbContext, Logger, eg)
- Start host
Expected Behavior
Quartz jobs creating with MicrosoftDependencyInjectionScopedJobFactory, MassTransit jobs creating with MassTransitJobFactory
Actual Behavior
MassTransit overrides JobFactory and breaks all existing Quartz jobs
// SchedulerBusObserver.PostStart
_scheduler.JobFactory = new MassTransitJobFactory(bus);
Reproducible Demo
services.AddQuartz(options =>
{
// Store configuration, etc
var jobKey = new JobKey("RequestSentJob", "Template");
options.AddJob<RequestSentJob>(job =>
{
job.WithIdentity(jobKey)
.StoreDurably()
.WithDescription("Periodical request sent");
});
var triggerKey = new TriggerKey("RequestSentJobTrigger", "Template");
options.AddTrigger(trigger =>
{
trigger.ForJob(jobKey)
.WithIdentity(triggerKey)
.WithCronSchedule("*/3 * * * * ?*");
});
}).AddQuartzServer(options => options.WaitForJobsToComplete = true);
services.AddMassTransit(options =>
{
options.AddMessageScheduler(new Uri("queue:masstransit.quartz"));
options.UsingRabbitMq((context, rabbitmq) =>
{
// Host configuration, etc
rabbitmq.UseInMemoryScheduler(
context.GetRequiredService<ISchedulerFactory>(),
"masstransit.quartz");
}
}
public class RequestSentJob : IJob
{
private readonly IBus bus;
private readonly ILogger<RequestSentJob> logger;
public RequestSentJob(IBus bus, ILogger<RequestSentJob> logger)
{
this.bus = bus;
this.logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
logger.LogInformation("Sending: RequestSent");
await bus.Publish(new RequestSent
{
Text = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)
});
}
}
Thrown exception
Quartz.Core.ErrorLogger[0]
An error occurred instantiating job to be executed. job= 'Template.RequestSentJob, message=Exception has been thrown by the target of an invocation.'
Quartz.SchedulerException: Problem instantiating type 'AppAny.Template.RequestSentJob: Exception has been thrown by the target of an invocation.'
---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> Quartz.SchedulerException: The job class does not have a supported constructor: AppAny.Template.RequestSentJob
at MassTransit.QuartzIntegration.MassTransitJobFactory`1.CreateConstructor()
at MassTransit.QuartzIntegration.MassTransitJobFactory`1..ctor(IBus bus)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Activator.CreateInstance(Type type, Object[] args)
at MassTransit.QuartzIntegration.MassTransitJobFactory.CreateJobFactory(Type type)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at MassTransit.QuartzIntegration.MassTransitJobFactory.NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
at Quartz.Core.JobRunShell.Initialize(QuartzScheduler sched, CancellationToken cancellationToken)
--- End of inner exception stack trace --- [See nested exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an
invocation.
---> Quartz.SchedulerException: The job class does not have a supported constructor: AppAny.Template.RequestSentJob
at MassTransit.QuartzIntegration.MassTransitJobFactory`1.CreateConstructor()
at MassTransit.QuartzIntegration.MassTransitJobFactory`1..ctor(IBus bus)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Activator.CreateInstance(Type type, Object[] args)
at MassTransit.QuartzIntegration.MassTransitJobFactory.CreateJobFactory(Type type)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at MassTransit.QuartzIntegration.MassTransitJobFactory.NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
at Quartz.Core.JobRunShell.Initialize(QuartzScheduler sched, CancellationToken cancellationToken)]
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 16 (16 by maintainers)
OK some rough edges but showing what I’ve played with. I’ve tweaked Quartz code a bit so this won’t compile yet without Quartz release.
https://github.com/MassTransit/MassTransit/pull/2067
https://github.com/MassTransit/Sample-Quartz/pull/1
Sorry for the noise on the second one, I was playing around and wanted to see recurring schedule in action with custom job.
Scheduler factory is a kind of a singleton, so it’s a holder of configuration how to create a scheduler. Well actually you can build multiple schedulers by name but usually you have on scheduler per process so having multiple named ones isn’t the most battle-tested use case. But for one service collection it only makes sense to have one such single factory as there’s no support for named singletons.
So far my thinking is to adapt MassTransit’s job factory to either work the same way as scoped/regular DI job factory that Quartz ships with (do service provider lookup) or even add possibility to hook some lifecycle events on Quartz’s side when Quartz produces jobs. It’s not a big deal to add service provider support which would allow jobs being injected with services in the container - or that’s what I think so far.
What I’ve seen so far MassTransit wants to add some metadata to job data map and inject properties. The property injection on MT side probably is a bit more clever/faster and then there’s the support for Uri type (haven’t tested what Quartz feels about that OOTB).
But like Chris mentioned I probably will toy around with the possibilities and try to produce some kind of PR for discussion and refinement. This might involve opening some APIs or allowing variation in Quartz implementation. Would of course love to hear what bugs the most and hinders the ability to use polymorphism or similar tools.