/// <summary> /// Returns a list of Dates that are the next fire times of a /// <see cref="ITrigger" />. /// The input trigger will be cloned before any work is done, so you need /// not worry about its state being altered by this method. /// </summary> /// <param name="trigg">The trigger upon which to do the work</param> /// <param name="cal">The calendar to apply to the trigger's schedule</param> /// <param name="numTimes">The number of next fire times to produce</param> public static IReadOnlyList <DateTimeOffset> ComputeFireTimes(IOperableTrigger trigg, ICalendar?cal, int numTimes) { List <DateTimeOffset> lst = new List <DateTimeOffset>(); IOperableTrigger t = (IOperableTrigger)trigg.Clone(); if (t.GetNextFireTimeUtc() == null || !t.GetNextFireTimeUtc().HasValue) { t.ComputeFirstFireTimeUtc(cal); } for (int i = 0; i < numTimes; i++) { DateTimeOffset?d = t.GetNextFireTimeUtc(); if (d.HasValue) { lst.Add(d.Value); t.Triggered(cal); } else { break; } } return(lst.AsReadOnly()); }
/// <summary> /// Compute the <see cref="DateTimeOffset" /> that is 1 second after the Nth firing of /// the given <see cref="ITrigger" />, taking the trigger's associated /// <see cref="ICalendar" /> into consideration. /// </summary> /// <remarks> /// The input trigger will be cloned before any work is done, so you need /// not worry about its state being altered by this method. /// </remarks> /// <param name="trigger">The trigger upon which to do the work</param> /// <param name="calendar">The calendar to apply to the trigger's schedule</param> /// <param name="numberOfTimes">The number of next fire times to produce</param> /// <returns>the computed Date, or null if the trigger (as configured) will not fire that many times</returns> public static DateTimeOffset?ComputeEndTimeToAllowParticularNumberOfFirings(IOperableTrigger trigger, ICalendar?calendar, int numberOfTimes) { IOperableTrigger t = (IOperableTrigger)trigger.Clone(); if (t.GetNextFireTimeUtc() == null) { t.ComputeFirstFireTimeUtc(calendar); } int c = 0; DateTimeOffset?endTime = null; for (int i = 0; i < numberOfTimes; i++) { DateTimeOffset?d = t.GetNextFireTimeUtc(); if (d != null) { c++; t.Triggered(calendar); if (c == numberOfTimes) { endTime = d; } } else { break; } } if (endTime == null) { return(null); } endTime = endTime.Value.AddSeconds(1); return(endTime); }
/// <summary> /// Returns a list of Dates that are the next fire times of a <see cref="ITrigger" /> /// that fall within the given date range. The input trigger will be cloned /// before any work is done, so you need not worry about its state being /// altered by this method. /// <para> /// NOTE: if this is a trigger that has previously fired within the given /// date range, then firings which have already occurred will not be listed /// in the output List. /// </para> /// </summary> /// <param name="trigg">The trigger upon which to do the work</param> /// <param name="cal">The calendar to apply to the trigger's schedule</param> /// <param name="from">The starting date at which to find fire times</param> /// <param name="to">The ending date at which to stop finding fire times</param> public static IReadOnlyList <DateTimeOffset> ComputeFireTimesBetween(IOperableTrigger trigg, ICalendar?cal, DateTimeOffset from, DateTimeOffset to) { List <DateTimeOffset> lst = new List <DateTimeOffset>(); IOperableTrigger t = (IOperableTrigger)trigg.Clone(); if (t.GetNextFireTimeUtc() == null || !t.GetNextFireTimeUtc().HasValue) { t.StartTimeUtc = from; t.EndTimeUtc = to; t.ComputeFirstFireTimeUtc(cal); } // TODO: this method could be more efficient by using logic specific // to the type of trigger ... while (true) { DateTimeOffset?d = t.GetNextFireTimeUtc(); if (d.HasValue) { if (d.Value < from) { t.Triggered(cal); continue; } if (d.Value > to) { break; } lst.Add(d.Value); t.Triggered(cal); } else { break; } } return(lst.AsReadOnly()); }
// Adapted from https://github.com/quartznet/quartznet/blob/master/src/Quartz/TriggerUtils.cs public static int ComputeFireTimeCountBetween(IOperableTrigger trigg, ICalendar cal, DateTimeOffset from, DateTimeOffset to) { int count = 0; IOperableTrigger t = (IOperableTrigger)trigg.Clone(); if (t.GetNextFireTimeUtc() == null || !t.GetNextFireTimeUtc().HasValue) { t.StartTimeUtc = from; t.EndTimeUtc = to; t.ComputeFirstFireTimeUtc(cal); } // TODO: this method could be more efficient by using logic specific // to the type of trigger ... while (true) { DateTimeOffset?d = t.GetNextFireTimeUtc(); if (d.HasValue) { if (d.Value < from) { t.Triggered(cal); continue; } if (d.Value > to) { break; } count++; t.Triggered(cal); } else { break; } } return(count); }
[Platform(Exclude = "Linux")] // TODO seems that we have some trouble on Linux with this public async Task ReschedulingTriggerShouldKeepOriginalNextFireTime() { NameValueCollection properties = new NameValueCollection(); properties["quartz.serializer.type"] = TestConstants.DefaultSerializerType; ISchedulerFactory factory = new StdSchedulerFactory(properties); IScheduler scheduler = await factory.GetScheduler(); await scheduler.Start(); var job = JobBuilder.Create <NoOpJob>().Build(); IOperableTrigger trigger = (IOperableTrigger)TriggerBuilder.Create() .WithSimpleSchedule(x => x.WithIntervalInHours(1).RepeatForever()) .ForJob(job) .StartNow() .Build(); await scheduler.ScheduleJob(job, trigger); trigger = (IOperableTrigger)await scheduler.GetTrigger(trigger.Key); Assert.That(trigger.GetPreviousFireTimeUtc(), Is.EqualTo(null)); var previousFireTimeUtc = DateTimeOffset.UtcNow.AddDays(1); trigger.SetPreviousFireTimeUtc(previousFireTimeUtc); trigger.SetNextFireTimeUtc(trigger.GetFireTimeAfter(previousFireTimeUtc)); await scheduler.RescheduleJob(trigger.Key, trigger); trigger = (IOperableTrigger)await scheduler.GetTrigger(trigger.Key); Assert.That(trigger.GetNextFireTimeUtc().Value.UtcDateTime, Is.EqualTo(previousFireTimeUtc.AddHours(1).UtcDateTime).Within(TimeSpan.FromSeconds(5))); await scheduler.Shutdown(true); }
/// <summary> /// Inform the <see cref="IJobStore" /> that the scheduler has completed the /// firing of the given <see cref="ITrigger" /> (and the execution its /// associated <see cref="IJob" />), and that the <see cref="JobDataMap" /> /// in the given <see cref="IJobDetail" /> should be updated if the <see cref="IJob" /> /// is stateful. /// </summary> public virtual void TriggeredJobComplete(IOperableTrigger trigger, IJobDetail jobDetail, SchedulerInstruction triggerInstCode) { lock (lockObject) { this.ReleaseAcquiredTrigger(trigger); // It's possible that the job is null if: // 1- it was deleted during execution // 2- RAMJobStore is being used only for volatile jobs / triggers // from the JDBC job store if (jobDetail.PersistJobDataAfterExecution) { this.Jobs.Update( Query.EQ("_id", jobDetail.Key.ToBsonDocument()), Update.Set("JobDataMap", jobDetail.JobDataMap.ToBsonDocument())); } if (jobDetail.ConcurrentExecutionDisallowed) { IList<Spi.IOperableTrigger> jobTriggers = this.GetTriggersForJob(jobDetail.Key); IEnumerable<BsonDocument> triggerKeys = jobTriggers.Select(t => t.Key.ToBsonDocument()); this.Triggers.Update( Query.And( Query.In("_id", triggerKeys), Query.EQ("State", "Blocked")), Update.Set("State", "Waiting")); this.Triggers.Update( Query.And( Query.In("_id", triggerKeys), Query.EQ("State", "PausedAndBlocked")), Update.Set("State", "Paused")); signaler.SignalSchedulingChange(null); } // even if it was deleted, there may be cleanup to do this.BlockedJobs.Remove( Query.EQ("_id", jobDetail.Key.ToBsonDocument())); // check for trigger deleted during execution... if (triggerInstCode == SchedulerInstruction.DeleteTrigger) { log.Debug("Deleting trigger"); DateTimeOffset? d = trigger.GetNextFireTimeUtc(); if (!d.HasValue) { // double check for possible reschedule within job // execution, which would cancel the need to delete... d = trigger.GetNextFireTimeUtc(); if (!d.HasValue) { this.RemoveTrigger(trigger.Key); } else { log.Debug("Deleting cancelled - trigger still active"); } } else { this.RemoveTrigger(trigger.Key); signaler.SignalSchedulingChange(null); } } else if (triggerInstCode == SchedulerInstruction.SetTriggerComplete) { this.Triggers.Update( Query.EQ("_id", trigger.Key.ToBsonDocument()), Update.Set("State", "Complete")); signaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetTriggerError) { Log.Info(string.Format(CultureInfo.InvariantCulture, "Trigger {0} set to ERROR state.", trigger.Key)); this.Triggers.Update( Query.EQ("_id", trigger.Key.ToBsonDocument()), Update.Set("State", "Error")); signaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersError) { Log.Info(string.Format(CultureInfo.InvariantCulture, "All triggers of Job {0} set to ERROR state.", trigger.JobKey)); IList<Spi.IOperableTrigger> jobTriggers = this.GetTriggersForJob(jobDetail.Key); IEnumerable<BsonDocument> triggerKeys = jobTriggers.Select(t => t.Key.ToBsonDocument()); this.Triggers.Update( Query.In("_id", triggerKeys), Update.Set("State", "Error")); signaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersComplete) { IList<Spi.IOperableTrigger> jobTriggers = this.GetTriggersForJob(jobDetail.Key); IEnumerable<BsonDocument> triggerKeys = jobTriggers.Select(t => t.Key.ToBsonDocument()); this.Triggers.Update( Query.In("_id", triggerKeys), Update.Set("State", "Complete")); signaler.SignalSchedulingChange(null); } } }
private void DoUpdateOfMisfiredTrigger(ConnectionAndTransactionHolder conn, IOperableTrigger trig, bool forceState, string newStateIfNotComplete, bool recovering) { ICalendar cal = null; if (trig.CalendarName != null) { cal = RetrieveCalendar(conn, trig.CalendarName); } schedSignaler.NotifyTriggerListenersMisfired(trig); trig.UpdateAfterMisfire(cal); if (!trig.GetNextFireTimeUtc().HasValue) { StoreTrigger(conn, trig, null, true, StateComplete, forceState, recovering); schedSignaler.NotifySchedulerListenersFinalized(trig); } else { StoreTrigger(conn, trig, null, true, newStateIfNotComplete, forceState, false); } }
internal static void PopulateBaseTriggerProperties(IOperableTrigger quartzTrigger, DbAbstractTrigger dbTrigger, string instanceName) { dbTrigger.CalendarName = quartzTrigger.CalendarName; dbTrigger.Description = quartzTrigger.Description; dbTrigger.JobGroup = quartzTrigger.JobKey.Group; dbTrigger.JobName = quartzTrigger.JobKey.Name; dbTrigger.MisfireInstruction = quartzTrigger.MisfireInstruction; dbTrigger.Priority = quartzTrigger.Priority; dbTrigger.Scheduler = instanceName; if (quartzTrigger.JobDataMap != null) { dbTrigger.JobData = new List<DbJobData>(); foreach (var jobDataItem in quartzTrigger.JobDataMap) { DbJobData dbJobData = new DbJobData(); dbJobData.Key = jobDataItem.Key; dbJobData.Value = jobDataItem.Value; dbJobData.Type = GetStorableJobTypeName(jobDataItem.Value.GetType()); dbTrigger.JobData.Add(dbJobData); } } dbTrigger.StartTime = new DateEpoch { Date = quartzTrigger.StartTimeUtc.UtcDateTime }; var endTime = quartzTrigger.EndTimeUtc; if (endTime.HasValue) dbTrigger.EndTime = new DateEpoch { Date = endTime.Value.UtcDateTime }; var nextFireTime = quartzTrigger.GetNextFireTimeUtc(); if (nextFireTime.HasValue) dbTrigger.NextFireTime = new DateEpoch { Date = nextFireTime.Value.UtcDateTime }; var previousFireTime = quartzTrigger.GetPreviousFireTimeUtc(); if (previousFireTime.HasValue) dbTrigger.PreviousFireTime = new DateEpoch { Date = previousFireTime.Value.UtcDateTime }; }
protected virtual void TriggeredJobComplete(ConnectionAndTransactionHolder conn, IOperableTrigger trigger, IJobDetail jobDetail, SchedulerInstruction triggerInstCode) { try { if (triggerInstCode == SchedulerInstruction.DeleteTrigger) { if (!trigger.GetNextFireTimeUtc().HasValue) { // double check for possible reschedule within job // execution, which would cancel the need to delete... TriggerStatus stat = Delegate.SelectTriggerStatus(conn, trigger.Key); if (stat != null && !stat.NextFireTimeUtc.HasValue) { RemoveTrigger(conn, trigger.Key); } } else { RemoveTrigger(conn, trigger.Key); SignalSchedulingChangeOnTxCompletion(null); } } else if (triggerInstCode == SchedulerInstruction.SetTriggerComplete) { Delegate.UpdateTriggerState(conn, trigger.Key, StateComplete); SignalSchedulingChangeOnTxCompletion(null); } else if (triggerInstCode == SchedulerInstruction.SetTriggerError) { Log.Info("Trigger " + trigger.Key + " set to ERROR state."); Delegate.UpdateTriggerState(conn, trigger.Key, StateError); SignalSchedulingChangeOnTxCompletion(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersComplete) { Delegate.UpdateTriggerStatesForJob(conn, trigger.JobKey, StateComplete); SignalSchedulingChangeOnTxCompletion(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersError) { Log.Info("All triggers of Job " + trigger.JobKey + " set to ERROR state."); Delegate.UpdateTriggerStatesForJob(conn, trigger.JobKey, StateError); SignalSchedulingChangeOnTxCompletion(null); } if (jobDetail.ConcurrentExecutionDisallowed) { Delegate.UpdateTriggerStatesForJobFromOtherState(conn, jobDetail.Key, StateWaiting, StateBlocked); Delegate.UpdateTriggerStatesForJobFromOtherState(conn, jobDetail.Key, StatePaused, StatePausedBlocked); SignalSchedulingChangeOnTxCompletion(null); } if (jobDetail.PersistJobDataAfterExecution) { try { if (jobDetail.JobDataMap.Dirty) { Delegate.UpdateJobData(conn, jobDetail); } } catch (IOException e) { throw new JobPersistenceException("Couldn't serialize job data: " + e.Message, e); } catch (Exception e) { throw new JobPersistenceException("Couldn't update job data: " + e.Message, e); } } } catch (Exception e) { throw new JobPersistenceException("Couldn't update trigger state(s): " + e.Message, e); } try { Delegate.DeleteFiredTrigger(conn, trigger.FireInstanceId); } catch (Exception e) { throw new JobPersistenceException("Couldn't delete fired trigger: " + e.Message, e); } }
/// <summary> /// Inform the <see cref="T:Quartz.Spi.IJobStore"/> that the scheduler has completed the /// firing of the given <see cref="T:Quartz.ITrigger"/> (and the execution its /// associated <see cref="T:Quartz.IJob"/>), and that the <see cref="T:Quartz.JobDataMap"/> /// in the given <see cref="T:Quartz.IJobDetail"/> should be updated if the <see cref="T:Quartz.IJob"/> /// is stateful. /// </summary> public override void TriggeredJobComplete(IOperableTrigger trigger, IJobDetail jobDetail, SchedulerInstruction triggerInstCode) { var jobHashKey = this.RedisJobStoreSchema.JobHashKey(jobDetail.Key); var jobDataMapHashKey = this.RedisJobStoreSchema.JobDataMapHashKey(jobDetail.Key); var triggerHashKey = this.RedisJobStoreSchema.TriggerHashkey(trigger.Key); if (this.Db.KeyExists(jobHashKey)) { Logger.InfoFormat("{0} - Job has completed", jobHashKey); if (jobDetail.PersistJobDataAfterExecution) { var jobDataMap = jobDetail.JobDataMap; Db.KeyDelete(jobDataMapHashKey); if (jobDataMap != null && !jobDataMap.IsEmpty) { Db.HashSet(jobDataMapHashKey, ConvertToHashEntries(jobDataMap)); } } if (jobDetail.ConcurrentExecutionDisallowed) { Db.SetRemove(this.RedisJobStoreSchema.BlockedJobsSet(), jobHashKey); Db.KeyDelete(this.RedisJobStoreSchema.JobBlockedKey(jobDetail.Key)); var jobTriggersSetKey = this.RedisJobStoreSchema.JobTriggersSetKey(jobDetail.Key); foreach (var nonConcurrentTriggerHashKey in this.Db.SetMembers(jobTriggersSetKey)) { var score = this.Db.SortedSetScore(this.RedisJobStoreSchema.TriggerStateSetKey(RedisTriggerState.Blocked), nonConcurrentTriggerHashKey); if (score.HasValue) { this.SetTriggerState(RedisTriggerState.Paused, score.Value, nonConcurrentTriggerHashKey); } else { score = this.Db.SortedSetScoreAsync( this.RedisJobStoreSchema.TriggerStateSetKey(RedisTriggerState.PausedBlocked), nonConcurrentTriggerHashKey).Result; if (score.HasValue) { this.SetTriggerState(RedisTriggerState.Paused, score.Value, nonConcurrentTriggerHashKey); } } } this.SchedulerSignaler.SignalSchedulingChange(null); } } else { this.Db.SetRemove(this.RedisJobStoreSchema.BlockedJobsSet(), jobHashKey); } if (this.Db.KeyExists(triggerHashKey)) { if (triggerInstCode == SchedulerInstruction.DeleteTrigger) { if (trigger.GetNextFireTimeUtc().HasValue == false) { if (string.IsNullOrEmpty(this.Db.HashGet(triggerHashKey, RedisJobStoreSchema.NextFireTime))) { RemoveTrigger(trigger.Key); } } else { this.RemoveTrigger(trigger.Key); this.SchedulerSignaler.SignalSchedulingChange(null); } } else if (triggerInstCode == SchedulerInstruction.SetTriggerComplete) { this.SetTriggerState(RedisTriggerState.Completed, DateTimeOffset.UtcNow.DateTime.ToUnixTimeMilliSeconds(), triggerHashKey); this.SchedulerSignaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetTriggerError) { double score = trigger.GetNextFireTimeUtc().HasValue ? trigger.GetNextFireTimeUtc().Value.DateTime.ToUnixTimeMilliSeconds() : 0; this.SetTriggerState(RedisTriggerState.Error, score, triggerHashKey); this.SchedulerSignaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersError) { var jobTriggersSetKey = this.RedisJobStoreSchema.JobTriggersSetKey(jobDetail.Key); foreach (var errorTriggerHashKey in this.Db.SetMembersAsync(jobTriggersSetKey).Result) { var nextFireTime = this.Db.HashGetAsync(errorTriggerHashKey.ToString(), RedisJobStoreSchema.NextFireTime).Result; var score = string.IsNullOrEmpty(nextFireTime) ? 0 : double.Parse(nextFireTime); this.SetTriggerState(RedisTriggerState.Error, score, errorTriggerHashKey); } this.SchedulerSignaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersComplete) { var jobTriggerSetKey = this.RedisJobStoreSchema.JobTriggersSetKey(jobDetail.Key); foreach (var completedTriggerHashKey in this.Db.SetMembersAsync(jobTriggerSetKey).Result) { this.SetTriggerState(RedisTriggerState.Completed, DateTimeOffset.UtcNow.DateTime.ToUnixTimeMilliSeconds(), completedTriggerHashKey); } this.SchedulerSignaler.SignalSchedulingChange(null); } } }
public Trigger(IOperableTrigger newTrigger, string schedulerInstanceName) { if (newTrigger == null) { return; } Name = newTrigger.Key.Name; Group = newTrigger.Key.Group; JobName = newTrigger.JobKey.Name; JobKey = $"{JobName}/{newTrigger.JobKey.Group}"; Scheduler = schedulerInstanceName; State = InternalTriggerState.Waiting; Description = newTrigger.Description; CalendarName = newTrigger.CalendarName; JobDataMap = newTrigger.JobDataMap.WrappedMap; FinalFireTimeUtc = newTrigger.FinalFireTimeUtc; MisfireInstruction = newTrigger.MisfireInstruction; Priority = newTrigger.Priority; HasMillisecondPrecision = newTrigger.HasMillisecondPrecision; FireInstanceId = newTrigger.FireInstanceId; EndTimeUtc = newTrigger.EndTimeUtc; StartTimeUtc = newTrigger.StartTimeUtc; NextFireTimeUtc = newTrigger.GetNextFireTimeUtc(); PreviousFireTimeUtc = newTrigger.GetPreviousFireTimeUtc(); if (NextFireTimeUtc != null) { NextFireTimeTicks = NextFireTimeUtc.Value.UtcTicks; } // Init trigger specific properties according to type of newTrigger. // If an option doesn't apply to the type of trigger it will stay null by default. switch (newTrigger) { case CronTriggerImpl cronTriggerImpl: Cron = new CronOptions { CronExpression = cronTriggerImpl.CronExpressionString, TimeZoneId = cronTriggerImpl.TimeZone.Id }; return; case SimpleTriggerImpl simpTriggerImpl: Simp = new SimpleOptions { RepeatCount = simpTriggerImpl.RepeatCount, RepeatInterval = simpTriggerImpl.RepeatInterval }; return; case CalendarIntervalTriggerImpl calTriggerImpl: Cal = new CalendarOptions { RepeatIntervalUnit = calTriggerImpl.RepeatIntervalUnit, RepeatInterval = calTriggerImpl.RepeatInterval, TimesTriggered = calTriggerImpl.TimesTriggered, TimeZoneId = calTriggerImpl.TimeZone.Id, PreserveHourOfDayAcrossDaylightSavings = calTriggerImpl.PreserveHourOfDayAcrossDaylightSavings, SkipDayIfHourDoesNotExist = calTriggerImpl.SkipDayIfHourDoesNotExist }; return; case DailyTimeIntervalTriggerImpl dayTriggerImpl: Day = new DailyTimeOptions { RepeatCount = dayTriggerImpl.RepeatCount, RepeatIntervalUnit = dayTriggerImpl.RepeatIntervalUnit, RepeatInterval = dayTriggerImpl.RepeatInterval, StartTimeOfDay = dayTriggerImpl.StartTimeOfDay, EndTimeOfDay = dayTriggerImpl.EndTimeOfDay, DaysOfWeek = dayTriggerImpl.DaysOfWeek, TimesTriggered = dayTriggerImpl.TimesTriggered, TimeZoneId = dayTriggerImpl.TimeZone.Id }; break; } }
/// <summary> /// Determine whether or not the given trigger has misfired.If so, notify {SchedulerSignaler} and update the trigger. /// </summary> /// <param name="trigger">IOperableTrigger</param> /// <returns>applied or not</returns> protected bool ApplyMisfire(IOperableTrigger trigger) { double misfireTime = DateTimeOffset.UtcNow.DateTime.ToUnixTimeMilliSeconds(); double score = misfireTime; if (MisfireThreshold > 0) { misfireTime = misfireTime - MisfireThreshold; } //if the trigger has no next fire time or exceeds the misfirethreshold or enable ignore misfirepolicy // then dont apply misfire. DateTimeOffset? nextFireTime = trigger.GetNextFireTimeUtc(); if (nextFireTime.HasValue == false || (nextFireTime.HasValue && nextFireTime.Value.DateTime.ToUnixTimeMilliSeconds() > misfireTime) || trigger.MisfireInstruction == -1) { return false; } ICalendar calendar = null; if (!string.IsNullOrEmpty(trigger.CalendarName)) { calendar = RetrieveCalendar(trigger.CalendarName); } SchedulerSignaler.NotifyTriggerListenersMisfired((IOperableTrigger)trigger.Clone()); trigger.UpdateAfterMisfire(calendar); StoreTrigger(trigger, true); if (nextFireTime.HasValue == false) { SetTriggerState(RedisTriggerState.Completed, score, RedisJobStoreSchema.TriggerHashkey(trigger.Key)); SchedulerSignaler.NotifySchedulerListenersFinalized(trigger); } else if (nextFireTime.Equals(trigger.GetNextFireTimeUtc())) { return false; } return true; }
/// <summary> /// Inform the <see cref="T:Quartz.Spi.IJobStore"/> that the scheduler no longer plans to /// fire the given <see cref="T:Quartz.ITrigger"/>, that it had previously acquired /// (reserved). /// </summary> public void ReleaseAcquiredTrigger(IOperableTrigger trigger) { var triggerHashKey = RedisJobStoreSchema.TriggerHashkey(trigger.Key); var score = Db.SortedSetScore(RedisJobStoreSchema.TriggerStateSetKey(RedisTriggerState.Acquired), triggerHashKey); if (score.HasValue) { if (trigger.GetNextFireTimeUtc().HasValue) { SetTriggerState(RedisTriggerState.Waiting, trigger.GetNextFireTimeUtc().Value.DateTime.ToUnixTimeMilliSeconds(), triggerHashKey); } else { this.UnsetTriggerState(triggerHashKey); } } }
/// <summary> /// Inform the <see cref="T:Quartz.Spi.IJobStore"/> that the scheduler has completed the /// firing of the given <see cref="T:Quartz.ITrigger"/> (and the execution its /// associated <see cref="T:Quartz.IJob"/>), and that the <see cref="T:Quartz.JobDataMap"/> /// in the given <see cref="T:Quartz.IJobDetail"/> should be updated if the <see cref="T:Quartz.IJob"/> /// is stateful. /// </summary> public override void TriggeredJobComplete(IOperableTrigger trigger, IJobDetail jobDetail, SchedulerInstruction triggerInstCode) { var jobHashKey = this.RedisJobStoreSchema.JobHashKey(jobDetail.Key); var jobDataMapHashKey = this.RedisJobStoreSchema.JobDataMapHashKey(jobDetail.Key); var triggerHashKey = this.RedisJobStoreSchema.TriggerHashkey(trigger.Key); if (this.Db.KeyExists(jobHashKey)) { Logger.InfoFormat("{0} - Job has completed", jobHashKey); if (jobDetail.PersistJobDataAfterExecution) { var jobDataMap = jobDetail.JobDataMap; Db.KeyDelete(jobDataMapHashKey); if (jobDataMap != null && !jobDataMap.IsEmpty) { Db.HashSet(jobDataMapHashKey, ConvertToHashEntries(jobDataMap)); } } if (jobDetail.ConcurrentExecutionDisallowed) { Db.SetRemove(this.RedisJobStoreSchema.BlockedJobsSet(), jobHashKey); Db.KeyDelete(this.RedisJobStoreSchema.JobBlockedKey(jobDetail.Key)); var jobTriggersSetKey = this.RedisJobStoreSchema.JobTriggersSetKey(jobDetail.Key); foreach (var nonConcurrentTriggerHashKey in this.Db.SetMembers(jobTriggersSetKey)) { var score = this.Db.SortedSetScore(this.RedisJobStoreSchema.TriggerStateSetKey(RedisTriggerState.Blocked), nonConcurrentTriggerHashKey); if (score.HasValue) { this.SetTriggerState(RedisTriggerState.Paused, score.Value, nonConcurrentTriggerHashKey); } else { score = this.Db.SortedSetScoreAsync( this.RedisJobStoreSchema.TriggerStateSetKey(RedisTriggerState.PausedBlocked), nonConcurrentTriggerHashKey).Result; if (score.HasValue) { this.SetTriggerState(RedisTriggerState.Paused, score.Value, nonConcurrentTriggerHashKey); } } } this.SchedulerSignaler.SignalSchedulingChange(null); } } else { this.Db.SetRemove(this.RedisJobStoreSchema.BlockedJobsSet(), jobHashKey); } if (this.Db.KeyExists(triggerHashKey)) { if (triggerInstCode == SchedulerInstruction.DeleteTrigger) { if (trigger.GetNextFireTimeUtc().HasValue == false) { if (string.IsNullOrEmpty(this.Db.HashGet(triggerHashKey, RedisJobStoreSchema.NextFireTime))) { RemoveTrigger(trigger.Key); } } else { this.RemoveTrigger(trigger.Key); this.SchedulerSignaler.SignalSchedulingChange(null); } } else if (triggerInstCode == SchedulerInstruction.SetTriggerComplete) { this.SetTriggerState(RedisTriggerState.Completed, DateTimeOffset.UtcNow.DateTime.ToUnixTimeMilliSeconds(), triggerHashKey); this.SchedulerSignaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetTriggerError) { double score = trigger.GetNextFireTimeUtc().HasValue ? trigger.GetNextFireTimeUtc().Value.DateTime.ToUnixTimeMilliSeconds() : 0; this.SetTriggerState(RedisTriggerState.Error, score, triggerHashKey); this.SchedulerSignaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersError) { var jobTriggersSetKey = this.RedisJobStoreSchema.JobTriggersSetKey(jobDetail.Key); foreach (var errorTriggerHashKey in this.Db.SetMembersAsync(jobTriggersSetKey).Result) { var nextFireTime = this.Db.HashGetAsync(errorTriggerHashKey.ToString(), RedisJobStoreSchema.NextFireTime).Result; var score = string.IsNullOrEmpty(nextFireTime) ? 0 : double.Parse(nextFireTime); this.SetTriggerState(RedisTriggerState.Error, score, errorTriggerHashKey); } this.SchedulerSignaler.SignalSchedulingChange(null); } else if (triggerInstCode == SchedulerInstruction.SetAllJobTriggersComplete) { var jobTriggerSetKey = this.RedisJobStoreSchema.JobTriggersSetKey(jobDetail.Key); foreach (var completedTriggerHashKey in this.Db.SetMembersAsync(jobTriggerSetKey).Result) { this.SetTriggerState(RedisTriggerState.Completed, DateTimeOffset.UtcNow.DateTime.ToUnixTimeMilliSeconds(), completedTriggerHashKey); } this.SchedulerSignaler.SignalSchedulingChange(null); } } }
/// <summary> /// Applies the misfire. /// </summary> /// <param name="tw">The trigger wrapper.</param> /// <returns></returns> protected virtual bool ApplyMisfire(IOperableTrigger trigger) { DateTimeOffset misfireTime = SystemTime.UtcNow(); if (MisfireThreshold > TimeSpan.Zero) { misfireTime = misfireTime.AddMilliseconds(-1 * MisfireThreshold.TotalMilliseconds); } DateTimeOffset? tnft = trigger.GetNextFireTimeUtc(); if (!tnft.HasValue || tnft.Value > misfireTime || trigger.MisfireInstruction == MisfireInstruction.IgnoreMisfirePolicy) { return false; } ICalendar cal = null; if (trigger.CalendarName != null) { cal = this.RetrieveCalendar(trigger.CalendarName); } signaler.NotifyTriggerListenersMisfired(trigger); trigger.UpdateAfterMisfire(cal); this.StoreTrigger(trigger, true); if (!trigger.GetNextFireTimeUtc().HasValue) { this.Triggers.Update( Query.EQ("_id", trigger.Key.ToBsonDocument()), Update.Set("State", "Complete")); signaler.NotifySchedulerListenersFinalized(trigger); } else if (tnft.Equals(trigger.GetNextFireTimeUtc())) { return false; } return true; }
protected virtual TriggerFiredBundle TriggerFired(ConnectionAndTransactionHolder conn, IOperableTrigger trigger) { IJobDetail job; ICalendar cal = null; // Make sure trigger wasn't deleted, paused, or completed... try { // if trigger was deleted, state will be StateDeleted string state = Delegate.SelectTriggerState(conn, trigger.Key); if (!state.Equals(StateAcquired)) { return null; } } catch (Exception e) { throw new JobPersistenceException("Couldn't select trigger state: " + e.Message, e); } try { job = RetrieveJob(conn, trigger.JobKey); if (job == null) { return null; } } catch (JobPersistenceException jpe) { try { Log.Error("Error retrieving job, setting trigger state to ERROR.", jpe); Delegate.UpdateTriggerState(conn, trigger.Key, StateError); } catch (Exception sqle) { Log.Error("Unable to set trigger state to ERROR.", sqle); } throw; } if (trigger.CalendarName != null) { cal = RetrieveCalendar(conn, trigger.CalendarName); if (cal == null) { return null; } } try { Delegate.UpdateFiredTrigger(conn, trigger, StateExecuting, job); } catch (Exception e) { throw new JobPersistenceException("Couldn't insert fired trigger: " + e.Message, e); } DateTimeOffset? prevFireTime = trigger.GetPreviousFireTimeUtc(); // call triggered - to update the trigger's next-fire-time state... trigger.Triggered(cal); string state2 = StateWaiting; bool force = true; if (job.ConcurrentExecutionDisallowed) { state2 = StateBlocked; force = false; try { Delegate.UpdateTriggerStatesForJobFromOtherState(conn, job.Key, StateBlocked, StateWaiting); Delegate.UpdateTriggerStatesForJobFromOtherState(conn, job.Key, StateBlocked, StateAcquired); Delegate.UpdateTriggerStatesForJobFromOtherState(conn, job.Key, StatePausedBlocked, StatePaused); } catch (Exception e) { throw new JobPersistenceException("Couldn't update states of blocked triggers: " + e.Message, e); } } if (!trigger.GetNextFireTimeUtc().HasValue) { state2 = StateComplete; force = true; } StoreTrigger(conn, trigger, job, true, state2, force, false); job.JobDataMap.ClearDirtyFlag(); return new TriggerFiredBundle( job, trigger, cal, trigger.Key.Group.Equals(SchedulerConstants.DefaultRecoveryGroup), SystemTime.UtcNow(), trigger.GetPreviousFireTimeUtc(), prevFireTime, trigger.GetNextFireTimeUtc()); }
/// <summary> /// Store and schedule the identified <see cref="IOperableTrigger"/> /// </summary> /// <param name="trig"></param> public void TriggerJob(IOperableTrigger trig) { ValidateState(); trig.ComputeFirstFireTimeUtc(null); bool collision = true; while (collision) { try { resources.JobStore.StoreTrigger(trig, false); collision = false; } catch (ObjectAlreadyExistsException) { trig.Key = new TriggerKey(NewTriggerId(), SchedulerConstants.DefaultGroup); } } NotifySchedulerThread(trig.GetNextFireTimeUtc()); NotifySchedulerListenersScheduled(trig); }
private void DoUpdateOfMisfiredTrigger(IOperableTrigger trig, bool forceState, InternalTriggerState newStateIfNotComplete, bool recovering) { ICalendar cal = null; if (trig.CalendarName != null) { cal = RetrieveCalendar(trig.CalendarName); } signaler.NotifyTriggerListenersMisfired(trig); trig.UpdateAfterMisfire(cal); // TODO: Decide if we need to replace the whole trigger or could just update the status and next-fire-time if (!trig.GetNextFireTimeUtc().HasValue) { StoreTrigger(trig, null, true, InternalTriggerState.Complete, forceState, recovering); } else { StoreTrigger(trig, null, true, newStateIfNotComplete, forceState, false); } }