IoTSharp / SilkierQuartz

SilkierQuartz can host jobs using HostService and Provide a web management tools for Quartz !
http://sq.iotsharp.io/
MIT License
357 stars 71 forks source link

System.NullReferenceException: Object reference not set to an instance of an object. #147

Closed swidz closed 1 year ago

swidz commented 1 year ago

I get the NullReferenceException if the job is not added using .AddQuartzJob<ExecutionWorkerJobsMonitorJob>()

The error is as follows:

Job DEFAULT.ExecutionWorkerJobsMonitorJob threw an exception.
Quartz.SchedulerException: Job threw an unhandled exception.
 ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Quartz.Core.JobRunShell.Run(CancellationToken cancellationToken)
   --- End of inner exception stack trace --- [See nested exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at Quartz.Core.JobRunShell.Run(CancellationToken cancellationToken)]

The AddSilkierQuartz method is called with assembly list. The job is properly decorated with SilkierQuartzAttribute

I expect the job to be discovered and triggered every 10 seconds I can see the job with trigger but every time they fire I get the Object reference not set to an instance of an object. error.

I am using Microsoft SQLite as store .Net 7, with EFCore SilkierQuartz 5.0.356

If I add .AddQuartzJob() to register the job in services collection then the error does not happen.

please find parts of the code below:

public class Startup
    {
        private readonly IConfiguration _configuration;
        private readonly IWebHostEnvironment _environment;

        public Startup(IConfiguration config, IWebHostEnvironment environment)
        {
            _configuration = config;
            _environment = environment;
        }

        public void ConfigureServices(IServiceCollection services)
        {            
            services.AddOptions();

            services.AddFeatureManagement(_configuration);
            services.AddLocalization(options =>
            {
                options.ResourcesPath = "Resources";
            });

            services.AddLazyCache();
            services.AddSerialization();
            services.AddDatabase(_configuration);
            services.AddApplicationLayer(_configuration);
            services.AddApplicationServices(_configuration);            

            services
                .AddMvc()
                .AddJsonOptions(o =>
                {
                    o.JsonSerializerOptions.PropertyNamingPolicy = null;
                    o.JsonSerializerOptions.DictionaryKeyPolicy = null;
                });

            services.AddFluentValidationAutoValidation();
            services.AddValidatorsFromAssemblies(
                AssemblyHelper.GetAssemblies());

            services.AddBackgroundWorker(_configuration);                     
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime appLifetime)
        {            
            app.UseExceptionHandling(env);
            app.UseHttpsRedirection();            
            app.UseAuthentication();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthorization();
            app.UseSilkierQuartz();
            app.UseEndpoints();            
        }

    }
internal static IServiceCollection AddBackgroundWorker(this IServiceCollection services, IConfiguration config)
        {            
            var quartzConfiguration = config.GetSection("Quartz");

            var referencedAssemblies = AssemblyHelper
                .GetAssembliesFromExecutionFolder(
                    Assembly.GetExecutingAssembly());

            services.AddSilkierQuartz(options =>
                {

                    options.EnableEditor = false;
                    options.VirtualPathRoot = "/quartz";
                    options.UseLocalTime = true;
                    options.DefaultDateFormat = "yyyy-MM-dd";
                    options.DefaultTimeFormat = "HH:mm:ss";
                    options.CronExpressionOptions = new CronExpressionDescriptor.Options()
                    {
                        DayOfWeekStartIndexZero = false, //Quartz uses 1-7 as the range
                        Use24HourTimeFormat = true
                    };
                },
                authenticationOptions =>
                {
                    authenticationOptions.AccessRequirement = SilkierQuartzAuthenticationOptions.SimpleAccessRequirement.AllowAnonymous;
                },
                stdSchedulerFactoryOptions =>
                {
                    stdSchedulerFactoryOptions.Add(quartzConfiguration
                        .Get<Dictionary<string, string>>()
                        .ToNameValueCollection());
                },
                () => referencedAssemblies
            );

            return services;

        }

My appsettings.json part related to Quartz


"Quartz": {
    "quartz.scheduler.instanceName": "Scheduler",
    "quartz.scheduler.instanceId": "AUTO",
    "quartz.threadPool.type": "Quartz.Simpl.SimpleThreadPool, Quartz",
    "quartz.threadPool.threadCount": "10",
    "quartz.serializer.type": "json",
    "quartz.jobStore.clustered": false,
    "quartz.jobStore.useProperties": false,
    "quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
    "quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.SQLiteDelegate, Quartz",
    "quartz.jobStore.tablePrefix": "QRTZ_",
    "quartz.jobStore.dataSource": "default",
    "quartz.dataSource.default.provider": "SQLite-Microsoft",
    "quartz.dataSource.default.connectionString": "DataSource=C:\\datamigration\\db\\Datamigration.db",
    "quartz.plugin.recentHistory.storeType": "Quartz.Plugins.RecentHistory.Impl.InProcExecutionHistoryStore, Quartz.Plugins.RecentHistory",
    "quartz.plugin.recentHistory.type": "Quartz.Plugins.RecentHistory.ExecutionHistoryPlugin, Quartz.Plugins.RecentHistory"
  },
[SilkierQuartz(10, "Data migration job monitor", "Executes data migration jobs taking care of dependencies", TriggerGroup = "SilkierQuartz")]
[DisallowConcurrentExecution]
[PersistJobDataAfterExecution]
public class ExecutionWorkerJobsMonitorJob : IJob
{    
    private readonly ILogger<ExecutionWorkerJobsMonitorJob> _logger;
    private readonly IDatamigrationDbContext _ctx;
    private readonly ISchedulerFactory _schedulerFactory;

    public ExecutionWorkerJobsMonitorJob(
        ISchedulerFactory schedulerFactory,
        ILogger<ExecutionWorkerJobsMonitorJob> logger,
        IDatamigrationDbContext ctx)
    {
        _schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory));        
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _ctx = ctx ?? throw new ArgumentNullException(nameof(ctx));
    }

    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogDebug("ExecutionWorkerJobsMonitorJob executed");

        ArgumentNullException.ThrowIfNull(context, nameof(context));                        
        var scheduler = await _schedulerFactory.GetScheduler(context.CancellationToken);
}
}

UPDATE: It is important to mention that ExecutionWorkerJobsMonitorJob is defined in an external assembly. When I define a job in the same assembly as the quartz host the job runs without problems. I was trying to do the same on example project but cannot reproduce - external assembly jobs run without problems Need to check if db storage has anything to do about it.

UPDATE2: I changed the example project to use persistent storage on SQLServer I get the same System.NullReferenceException: Object reference not set to an instance of an object. It seems the issue is related to using job store with persistence, external assembly and autodiscover using SilkierQuartzAttribute

Working example: https://github.com/swidz/SilkierQuartz Job that returns NullReferenceExceptionis called HelloJobAutoExtin TestJobsassembly (project)

swidz commented 1 year ago

Started a discussion on Quartz.Net discussion group Help needed - System.NullReferenceException: Object reference not set to an instance of an object

swidz commented 1 year ago

For posterity, I have found the solution. The issue was how assembly list was loaded that was passed to services.AddSilkierQuartz To create that list, I used a helper method that internally used System.Reflection.Assembly.Load(assemblyName) or System.Reflection.Assembly.LoadFile(assemblyPath)

but instead it should use

System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName) or System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath).

References: About System.Runtime.Loader.AssemblyLoadContext

Regards, Sebastian