/// <summary> /// Construct a new <see cref="QuartzSchedulerThread" /> for the given /// <see cref="QuartzScheduler" /> as a <see cref="Thread" /> with the given /// attributes. /// </summary> internal QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs, bool setDaemon, int threadPrio) : base(qsRsrcs.ThreadName) { log = LogManager.GetLogger(GetType().FullName); //ThreadGroup generatedAux = qs.SchedulerThreadGroup; this.qs = qs; this.qsRsrcs = qsRsrcs; IsBackground = setDaemon; Priority = (ThreadPriority)threadPrio; // start the underlying thread, but put this object into the 'paused' // state // so processing doesn't start yet... paused = true; halted = false; }
/// <summary> /// The main processing loop of the <see cref="QuartzSchedulerThread" />. /// </summary> public override void Run() { bool lastAcquireFailed = false; while (!halted) { try { // check if we're supposed to pause... lock (sigLock) { while (paused && !halted) { try { // wait until togglePause(false) is called... Monitor.Wait(sigLock, 1000); } catch (ThreadInterruptedException) { } } if (halted) { break; } } int availThreadCount = qsRsrcs.ThreadPool.BlockForAvailableThreads(); if (availThreadCount > 0) // will always be true, due to semantics of blockForAvailableThreads... { IList <IOperableTrigger> triggers = null; DateTimeOffset now = SystemTime.UtcNow(); ClearSignaledSchedulingChange(); try { triggers = qsRsrcs.JobStore.AcquireNextTriggers( now + idleWaitTime, Math.Min(availThreadCount, qsRsrcs.MaxBatchSize), qsRsrcs.BatchTimeWindow); lastAcquireFailed = false; if (log.IsDebugEnabled) { log.Debug("Batch acquisition of {0} triggers", (triggers == null ? 0 : triggers.Count)); } } catch (JobPersistenceException jpe) { if (!lastAcquireFailed) { qs.NotifySchedulerListenersError( "An error occurred while scanning for the next trigger to fire.", jpe); } lastAcquireFailed = true; } catch (Exception e) { if (!lastAcquireFailed) { Log.Error("quartzSchedulerThreadLoop: RuntimeException " + e.Message, e); } lastAcquireFailed = true; } if (triggers != null && triggers.Count > 0) { now = SystemTime.UtcNow(); DateTimeOffset triggerTime = triggers[0].GetNextFireTimeUtc().Value; TimeSpan timeUntilTrigger = triggerTime - now; while (timeUntilTrigger > TimeSpan.FromMilliseconds(2)) { if (ReleaseIfScheduleChangedSignificantly(triggers, triggerTime)) { break; } lock (sigLock) { if (halted) { break; } if (!IsCandidateNewTimeEarlierWithinReason(triggerTime, false)) { try { // we could have blocked a long while // on 'synchronize', so we must recompute now = SystemTime.UtcNow(); timeUntilTrigger = triggerTime - now; if (timeUntilTrigger > TimeSpan.Zero) { Monitor.Wait(sigLock, timeUntilTrigger); } } catch (ThreadInterruptedException) { } } } if (ReleaseIfScheduleChangedSignificantly(triggers, triggerTime)) { break; } now = SystemTime.UtcNow(); timeUntilTrigger = triggerTime - now; } // this happens if releaseIfScheduleChangedSignificantly decided to release triggers if (triggers.Count == 0) { continue; } // set triggers to 'executing' IList <TriggerFiredResult> bndles = new List <TriggerFiredResult>(); bool goAhead = true; lock (sigLock) { goAhead = !halted; } if (goAhead) { try { IList <TriggerFiredResult> res = qsRsrcs.JobStore.TriggersFired(triggers); if (res != null) { bndles = res; } } catch (SchedulerException se) { qs.NotifySchedulerListenersError("An error occurred while firing triggers '" + triggers + "'", se); // QTZ-179 : a problem occurred interacting with the triggers from the db // we release them and loop again foreach (IOperableTrigger t in triggers) { ReleaseTriggerRetryLoop(t); } continue; } } for (int i = 0; i < bndles.Count; i++) { TriggerFiredResult result = bndles[i]; TriggerFiredBundle bndle = result.TriggerFiredBundle; Exception exception = result.Exception; IOperableTrigger trigger = triggers[i]; // TODO SQL exception? if (exception != null && (exception is DbException || exception.InnerException is DbException)) { Log.Error("DbException while firing trigger " + trigger, exception); // db connection must have failed... keep // retrying until it's up... ReleaseTriggerRetryLoop(trigger); continue; } // it's possible to get 'null' if the triggers was paused, // blocked, or other similar occurrences that prevent it being // fired at this time... or if the scheduler was shutdown (halted) if (bndle == null) { try { qsRsrcs.JobStore.ReleaseAcquiredTrigger(trigger); } catch (SchedulerException se) { qs.NotifySchedulerListenersError( "An error occurred while releasing triggers '" + trigger.Key + "'", se); // db connection must have failed... keep retrying // until it's up... ReleaseTriggerRetryLoop(trigger); } continue; } // TODO: improvements: // // 2- make sure we can get a job runshell before firing trigger, or // don't let that throw an exception (right now it never does, // but the signature says it can). // 3- acquire more triggers at a time (based on num threads available?) JobRunShell shell = null; try { shell = qsRsrcs.JobRunShellFactory.CreateJobRunShell(bndle); shell.Initialize(qs); } catch (SchedulerException) { try { qsRsrcs.JobStore.TriggeredJobComplete(trigger, bndle.JobDetail, SchedulerInstruction.SetAllJobTriggersError); } catch (SchedulerException se2) { qs.NotifySchedulerListenersError( "An error occurred while placing job's triggers in error state '" + trigger.Key + "'", se2); // db connection must have failed... keep retrying // until it's up... ErrorTriggerRetryLoop(bndle); } continue; } if (qsRsrcs.ThreadPool.RunInThread(shell) == false) { try { // this case should never happen, as it is indicative of the // scheduler being shutdown or a bug in the thread pool or // a thread pool being used concurrently - which the docs // say not to do... Log.Error("ThreadPool.runInThread() return false!"); qsRsrcs.JobStore.TriggeredJobComplete(trigger, bndle.JobDetail, SchedulerInstruction. SetAllJobTriggersError); } catch (SchedulerException se2) { qs.NotifySchedulerListenersError( string.Format(CultureInfo.InvariantCulture, "An error occurred while placing job's triggers in error state '{0}'", trigger.Key), se2); // db connection must have failed... keep retrying // until it's up... ReleaseTriggerRetryLoop(trigger); } } } continue; // while (!halted) } } else // if(availThreadCount > 0) { // should never happen, if threadPool.blockForAvailableThreads() follows contract continue; // while (!halted) } DateTimeOffset utcNow = SystemTime.UtcNow(); DateTimeOffset waitTime = utcNow.Add(GetRandomizedIdleWaitTime()); TimeSpan timeUntilContinue = waitTime - utcNow; lock (sigLock) { if (!halted) { try { Monitor.Wait(sigLock, timeUntilContinue); } catch (ThreadInterruptedException) { } } } } catch (Exception re) { if (Log != null) { Log.Error("Runtime error occurred in main trigger firing loop.", re); } } } // while (!halted) // drop references to scheduler stuff to aid garbage collection... qs = null; qsRsrcs = null; }
/// <summary> /// Construct a new <see cref="QuartzSchedulerThread" /> for the given /// <see cref="QuartzScheduler" /> as a non-daemon <see cref="Thread" /> /// with normal priority. /// </summary> internal QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs) : this(qs, qsRsrcs, qsRsrcs.MakeSchedulerThreadDaemon, (int)ThreadPriority.Normal) { }