Saturday, April 16, 2016

Advanced Quartz.NET scenarios


To enable Quartz.NET internal logging, add the following to Quartz.Server.exe.config - 


1) Use the following <common> config section
  <common>
     <logging>
      <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Net1213">
        <arg key="configType" value="INLINE"/>
      </factoryAdapter>
    </logging>
  </common>

2) <appender name="QuartzInternalLog" type="log4net.Appender.RollingFileAppender">
            <file type="log4net.Util.PatternString" value="Trace/QuartzInternalLogFile_%date{yyyyMMdd}.txt"/>
            <appendToFile value="true"/>
            <maximumFileSize value="1024KB"/>
     <maxSizeRollBackups value="1000" />
            <rollingStyle value="Size"/>
   <datePattern value="yyyyMMdd" />
   <staticLogFileName value="true" />
   <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%d{HH:mm:ss} [%t] %-5p %c - %m%n"/>
            </layout>
    </appender>


3) <logger name="Quartz">
<level value="DEBUG" />
<appender-ref ref="QuartzInternalLog" />
    </logger>

(NO NEED TO ADD QUARTZINTERNALLOG TO ROOT LEVEL LOGGER. THIS IS TO KEEP QUARTZ INTERNAL LOG SEPARATE).



To enable clustering in Quartz.NET, add the following to Quartz.Server.exe.config - 

<quartz>
<add key="quartz.checkConfiguration" value="false"/>
  <add key="quartz.server.serviceName" value="ABC-Project-QuartzServer-UAT"/>
  <add key="quartz.server.serviceDisplayName" value="ABC-Project-QuartzServer-UAT"/>
  <add key="quartz.server.serviceDescription" value="ABC-Project-QuartzServer-UAT"/>
  <add key="quartz.scheduler.instanceName" value="ABCProjectScheduler"/>
  <add key="quartz.scheduler.instanceId" value = "AUTO"/>
  <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz"/>
  <add key="quartz.threadPool.threadCount" value="20"/>
  <add key="quartz.threadPool.threadPriority" value="Normal"/>
  <add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz"/>
  <add key="quartz.plugin.xml.fileNames" value="~/quartz_jobs.xml"/>
  <add key="quartz.scheduler.exporter.type" value="Quartz.Simpl.RemotingSchedulerExporter, Quartz"/>
  <add key="quartz.scheduler.exporter.port" value="555"/>
  <add key="quartz.scheduler.exporter.bindName" value="QuartzScheduler"/>
  <add key="quartz.scheduler.exporter.channelType" value="tcp"/>
  <add key="quartz.scheduler.exporter.channelName" value="httpQuartz"/>
  <add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"/>
  <add key="quartz.jobStore.dataSource" value="default"/>
  <add key="quartz.jobStore.tablePrefix" value="QRTZ_"/>
  <add key="quartz.jobStore.clustered" value="true"/>

  <add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz"/>
  <add key="quartz.dataSource.default.provider" value="SqlServer-20"/>
  <add key="quartz.jobStore.useProperties" value="true"/>
  <add key="quartz.jobStore.misfireThreshold" value="600000" />
 
  <add key="quartz.dataSource.default.connectionStringName" value="QuartzStore"/>
  <add key="quartz.dataSource.default.connectionString" value="Data Source=MyServer;Initial Catalog=ABC_Project_quartz_scheduler;User ID=MyUser;Password=MyPass"/>
</quartz>


NOTE THE ABOVE SETTINGS CAN ALSO BE DONE IN Quartz.Config file but Quartz.Server.exe.config WILL TAKE THE PRECEDENCE. I personally like keeping all the settings in one place i.e. Quartz.Server.exe.config.

To prevent concurrent execution of the job, do the following steps - 



       1) Use the following attribute – [DisallowConcurrentExecution] in the job class


 2) Use the following misfire instruction:
<trigger>
      <cron>
        <name>LongRunningJobTrigger</name>
        <group>CronTriggerGroup</group>
        <description>CronTrigger description</description>
        <job-name>LongRunningJob</job-name>
        <job-group>QuartzJobLib</job-group>
        <misfire-instruction>DoNothing</misfire-instruction>
        <cron-expression>0 0/1 * 1/1 * ? *</cron-expression>
      </cron>
    </trigger>



     3) Default behavior would fire new job instance in a separate thread. We need to avoid that.

4) With the above approach, the below output is produced –  (with trigger every minute (60 sec), and each job taking 90 seconds)

[13396] 10/15/2015 1:16:00 AM 635804487114616685 Main Job Process - Start
[13396] 10/15/2015 1:16:00 AM 635804487114616685 Internal Job Process - Start
[13396] 10/15/2015 1:17:30 AM 635804487114616685 Internal Job Process - End
[13396] 10/15/2015 1:17:30 AM 635804487114616685 Main Job Process - End
[13396] 10/15/2015 1:18:00 AM 635804487114616723 Main Job Process - Start
[13396] 10/15/2015 1:18:00 AM 635804487114616723 Internal Job Process - Start
[13396] 10/15/2015 1:19:30 AM 635804487114616723 Internal Job Process - End
[13396] 10/15/2015 1:19:30 AM 635804487114616723 Main Job Process - End
[13396] 10/15/2015 1:20:00 AM 635804487114616724 Main Job Process - Start
[13396] 10/15/2015 1:20:00 AM 635804487114616724 Internal Job Process - Start
[13396] 10/15/2015 1:21:30 AM 635804487114616724 Internal Job Process - End
[13396] 10/15/2015 1:21:30 AM 635804487114616724 Main Job Process - End
[13396] 10/15/2015 1:22:00 AM 635804487114616725 Main Job Process - Start
[13396] 10/15/2015 1:22:00 AM 635804487114616725 Internal Job Process – Start



Note  from the above output
a)      The new job (ID) does not starts until the previous job is finished.
b)     The new job did not start at 1:17:30, instead it started at 1:18:00 (Because of the misfire instruction “DoNothing”)

5) Note misfire-instruction property is available only with CronTrigger (not with SimpleTrigger). My suggestion is to always use CronTrigger instead of SimpleTrigger.