示例#1
0
        /// <summary>
        /// Get a handle to the next trigger to be fired, and mark it as 'reserved'
        /// by the calling scheduler.
        /// </summary>
        /// <seealso cref="ITrigger" />
        public virtual IList<IOperableTrigger> AcquireNextTriggers(DateTimeOffset noLaterThan, int maxCount, TimeSpan timeWindow)
        {
            lock (lockObject)
            {
                // multiple instances management
                this.Schedulers.Save(new BsonDocument(
                    new BsonElement("_id", this.instanceId),
                    new BsonElement("Expires", (SystemTime.Now() + new TimeSpan(0, 10, 0)).UtcDateTime),
                    new BsonElement("State", "Running")));

                this.Schedulers.Remove(
                    Query.LT("Expires", SystemTime.Now().UtcDateTime));

                IEnumerable<BsonValue> activeInstances = this.Schedulers.Distinct("_id");

                this.Triggers.Update(
                    Query.NotIn("SchedulerInstanceId", activeInstances),
                    Update.Unset("SchedulerInstanceId")
                        .Set("State", "Waiting"));

                List<IOperableTrigger> result = new List<IOperableTrigger>();
                Collection.ISet<JobKey> acquiredJobKeysForNoConcurrentExec = new Collection.HashSet<JobKey>();
                DateTimeOffset? firstAcquiredTriggerFireTime = null;

                var candidates = this.Triggers.FindAs<Spi.IOperableTrigger>(
                    Query.And(
                        Query.EQ("State", "Waiting"),
                        Query.LTE("nextFireTimeUtc", (noLaterThan + timeWindow).UtcDateTime)))
                    .OrderBy(t => t.GetNextFireTimeUtc()).ThenByDescending(t => t.Priority);

                foreach (IOperableTrigger trigger in candidates)
                {
                    if (trigger.GetNextFireTimeUtc() == null)
                    {
                        continue;
                    }

                    // it's possible that we've selected triggers way outside of the max fire ahead time for batches
                    // (up to idleWaitTime + fireAheadTime) so we need to make sure not to include such triggers.
                    // So we select from the first next trigger to fire up until the max fire ahead time after that...
                    // which will perfectly honor the fireAheadTime window because the no firing will occur until
                    // the first acquired trigger's fire time arrives.
                    if (firstAcquiredTriggerFireTime != null
                        && trigger.GetNextFireTimeUtc() > (firstAcquiredTriggerFireTime.Value + timeWindow))
                    {
                        break;
                    }

                    if (this.ApplyMisfire(trigger))
                    {
                        if (trigger.GetNextFireTimeUtc() == null
                            || trigger.GetNextFireTimeUtc() > noLaterThan + timeWindow)
                        {
                            continue;
                        }
                    }

                    // If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then
                    // put it back into the timeTriggers set and continue to search for next trigger.
                    JobKey jobKey = trigger.JobKey;
                    IJobDetail job = this.Jobs.FindOneByIdAs<IJobDetail>(jobKey.ToBsonDocument());

                    if (job.ConcurrentExecutionDisallowed)
                    {
                        if (acquiredJobKeysForNoConcurrentExec.Contains(jobKey))
                        {
                            continue; // go to next trigger in store.
                        }
                        else
                        {
                            acquiredJobKeysForNoConcurrentExec.Add(jobKey);
                        }
                    }

                    trigger.FireInstanceId = this.GetFiredTriggerRecordId();
                    var acquired = this.Triggers.FindAndModify(
                        Query.And(
                            Query.EQ("_id", trigger.Key.ToBsonDocument()),
                            Query.EQ("State", "Waiting")),
                        SortBy.Null,
                        Update.Set("State", "Acquired")
                            .Set("SchedulerInstanceId", this.instanceId)
                            .Set("FireInstanceId", trigger.FireInstanceId));

                    if (acquired.ModifiedDocument != null)
                    {
                        result.Add(trigger);

                        if (firstAcquiredTriggerFireTime == null)
                        {
                            firstAcquiredTriggerFireTime = trigger.GetNextFireTimeUtc();
                        }
                    }

                    if (result.Count == maxCount)
                    {
                        break;
                    }
                }

                return result;
            }
        }
示例#2
0
        // TODO: this really ought to return something like a FiredTriggerBundle,
        // so that the fireInstanceId doesn't have to be on the trigger...
        protected virtual IList<IOperableTrigger> AcquireNextTrigger(ConnectionAndTransactionHolder conn, DateTimeOffset noLaterThan, int maxCount, TimeSpan timeWindow)
        {
            List<IOperableTrigger> acquiredTriggers = new List<IOperableTrigger>();
            Collection.ISet<JobKey> acquiredJobKeysForNoConcurrentExec = new Collection.HashSet<JobKey>();
            const int MaxDoLoopRetry = 3;
            int currentLoopCount = 0;
            DateTimeOffset? firstAcquiredTriggerFireTime = null;

            do
            {
                currentLoopCount ++;
                try
                {
                    IList<TriggerKey> keys;

                    // If timeWindow is specified, then we need to select trigger fire time with wider range!
                    if (timeWindow > TimeSpan.Zero)
                    {
                        keys = Delegate.SelectTriggerToAcquire(conn, noLaterThan + timeWindow, MisfireTime, maxCount);
                    }
                    else
                    {
                        keys = Delegate.SelectTriggerToAcquire(conn, noLaterThan, MisfireTime, maxCount);
                    }

                    // No trigger is ready to fire yet.
                    if (keys == null || keys.Count == 0)
                    {
                        return acquiredTriggers;
                    }

                    foreach (TriggerKey triggerKey in keys)
                    {
                        // If our trigger is no longer available, try a new one.
                        IOperableTrigger nextTrigger = RetrieveTrigger(conn, triggerKey);
                        if (nextTrigger == null)
                        {
                            continue; // next trigger
                        }
                        // it's possible that we've selected triggers way outside of the max fire ahead time for batches
                        // (up to idleWaitTime + fireAheadTime) so we need to make sure not to include such triggers.
                        // So we select from the first next trigger to fire up until the max fire ahead time after that...
                        // which will perfectly honor the fireAheadTime window because the no firing will occur until
                        // the first acquired trigger's fire time arrives.
                        if (firstAcquiredTriggerFireTime != null && nextTrigger.GetNextFireTimeUtc() > (firstAcquiredTriggerFireTime.Value + timeWindow))
                        {
                            break;
                        }

                        // If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then
                        // put it back into the timeTriggers set and continue to search for next trigger.
                        JobKey jobKey = nextTrigger.JobKey;
                        IJobDetail job = Delegate.SelectJobDetail(conn, jobKey, TypeLoadHelper);
                        if (job.ConcurrentExecutionDisallowed)
                        {
                            if (acquiredJobKeysForNoConcurrentExec.Contains(jobKey))
                            {
                                continue; // next trigger
                            }
                            else
                            {
                                acquiredJobKeysForNoConcurrentExec.Add(jobKey);
                            }
                        }

                        // We now have a acquired trigger, let's add to return list.
                        // If our trigger was no longer in the expected state, try a new one.
                        int rowsUpdated = Delegate.UpdateTriggerStateFromOtherState(conn, triggerKey, StateAcquired, StateWaiting);
                        if (rowsUpdated <= 0)
                        {
                            // TODO: Hum... shouldn't we log a warning here?
                            continue; // next trigger
                        }
                        nextTrigger.FireInstanceId = GetFiredTriggerRecordId();
                        Delegate.InsertFiredTrigger(conn, nextTrigger, StateAcquired, null);

                        acquiredTriggers.Add(nextTrigger);
                        if (firstAcquiredTriggerFireTime == null)
                        {
                            firstAcquiredTriggerFireTime = nextTrigger.GetNextFireTimeUtc();
                        }
                    }

                    // if we didn't end up with any trigger to fire from that first
                    // batch, try again for another batch. We allow with a max retry count.
                    if (acquiredTriggers.Count == 0 && currentLoopCount < MaxDoLoopRetry)
                    {
                        continue;
                    }

                    // We are done with the while loop.
                    break;
                }
                catch (Exception e)
                {
                    throw new JobPersistenceException("Couldn't acquire next trigger: " + e.Message, e);
                }
            } while (true);

            // Return the acquired trigger list
            return acquiredTriggers;
        }