예제 #1
1
        public bool ChangeState(StateContext context, IState toState, string oldStateName)
        {
            try
            {
                var filterInfo = GetFilters(context.Job);

                var electStateContext = new ElectStateContext(context, toState, oldStateName);
                var electedState = ElectState(electStateContext, filterInfo.ElectStateFilters);

                var applyStateContext = new ApplyStateContext(context, electedState, oldStateName);
                ApplyState(applyStateContext, filterInfo.ApplyStateFilters);

                // State transition was succeeded.
                return true;
            }
            catch (Exception ex)
            {
                var failedState = new FailedState(ex)
                {
                    Reason = "An exception occurred during the transition of job's state"
                };

                var applyStateContext = new ApplyStateContext(context, failedState, oldStateName);

                // We should not use any state changed filters, because
                // some of the could cause an exception.
                ApplyState(applyStateContext, Enumerable.Empty<IApplyStateFilter>());

                // State transition was failed due to exception.
                return false;
            }
        }
예제 #2
0
        public IState ApplyState(ApplyStateContext context)
        {
            var handlers = _stateHandlersThunk(context.Storage);

            foreach (var handler in handlers.GetHandlers(context.OldStateName))
            {
                handler.Unapply(context, context.Transaction);
            }

            context.Transaction.SetJobState(context.BackgroundJob.Id, context.NewState);

            foreach (var handler in handlers.GetHandlers(context.NewState.Name))
            {
                handler.Apply(context, context.Transaction);
            }

            if (context.NewState.IsFinal)
            {
                context.Transaction.ExpireJob(context.BackgroundJob.Id, context.JobExpirationTimeout);
            }
            else
            {
                context.Transaction.PersistJob(context.BackgroundJob.Id);
            }

            return context.NewState;
        }
예제 #3
0
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.AddToSet(
         "failed",
         context.JobId,
         JobHelper.ToTimestamp(DateTime.UtcNow));
 }
        public BackgroundJob Create(CreateContext context)
        {
            var parameters = context.Parameters.ToDictionary(x => x.Key, x => JobHelper.ToJson(x.Value));

            var createdAt = DateTime.UtcNow;
            var jobId = context.Connection.CreateExpiredJob(
                context.Job,
                parameters,
                createdAt,
                TimeSpan.FromHours(1));

            var backgroundJob = new BackgroundJob(jobId, context.Job, createdAt);

            if (context.InitialState != null)
            {
                using (var transaction = context.Connection.CreateWriteTransaction())
                {
                    var applyContext = new ApplyStateContext(
                        context.Storage,
                        context.Connection,
                        transaction,
                        backgroundJob,
                        context.InitialState,
                        null);

                    _stateMachine.ApplyState(applyContext);

                    transaction.Commit();
                }
            }

            return backgroundJob;
        }
 public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     var awaitingState = context.NewState as AwaitingState;
     if (awaitingState != null)
     {
         context.JobExpirationTimeout = awaitingState.Expiration;
     }
 }
 public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     var failedState = context.NewState as FailedState;
     if (failedState != null)
     {
         Logger.ErrorException(
             String.Format("Background job #{0} was failed with an exception.", context.JobId), 
             failedState.Exception);
     }
 }
예제 #7
0
        public void Ctor_ShouldSetPropertiesCorrectly()
        {
            var context = new ApplyStateContext(
                _stateContext.Object,
                _newState.Object,
                OldState);

            Assert.Equal(OldState, context.OldStateName);
            Assert.Same(_newState.Object, context.NewState);
            Assert.Same(_job, context.Job);
        }
예제 #8
0
            public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
            {
                var enqueuedState = context.NewState as EnqueuedState;
                if (enqueuedState == null)
                {
                    throw new InvalidOperationException(String.Format(
                        "`{0}` state handler can be registered only for the Enqueued state.",
                        typeof(Handler).FullName));
                }

                transaction.AddToQueue(enqueuedState.Queue, context.JobId);
            }
예제 #9
0
            public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
            {
                var scheduledState = context.NewState as ScheduledState;
                if (scheduledState == null)
                {
                    throw new InvalidOperationException(String.Format(
                        "`{0}` state handler can be registered only for the Scheduled state.",
                        typeof(Handler).FullName));
                }

                var timestamp = JobHelper.ToTimestamp(scheduledState.EnqueueAt);
                transaction.AddToSet("schedule", context.JobId, timestamp);
            }
예제 #10
0
        public void ApplyState(IWriteOnlyTransaction transaction, ApplyStateContext context)
        {
            var filterInfo = GetFilters(context.Job);
            var filters = filterInfo.ApplyStateFilters;

            foreach (var state in context.TraversedStates)
            {
                transaction.AddJobState(context.JobId, state);
            }

            foreach (var handler in _handlers.GetHandlers(context.OldStateName))
            {
                handler.Unapply(context, transaction);
            }

            foreach (var filter in filters)
            {
                filter.OnStateUnapplied(context, transaction);
            }

            transaction.SetJobState(context.JobId, context.NewState);

            foreach (var handler in _handlers.GetHandlers(context.NewState.Name))
            {
                handler.Apply(context, transaction);
            }

            foreach (var filter in filters)
            {
                filter.OnStateApplied(context, transaction);
            }

            if (context.NewState.IsFinal)
            {
                transaction.ExpireJob(context.JobId, context.JobExpirationTimeout);
            }
            else
            {
                transaction.PersistJob(context.JobId);
            }
        }
예제 #11
0
        public IState ApplyState(ApplyStateContext initialContext)
        {
            var filterInfo = GetFilters(initialContext.BackgroundJob.Job);
            var electFilters = filterInfo.ElectStateFilters;
            var applyFilters = filterInfo.ApplyStateFilters;

            // Electing a a state
            var electContext = new ElectStateContext(initialContext);

            foreach (var filter in electFilters)
            {
                filter.OnStateElection(electContext);
            }

            foreach (var state in electContext.TraversedStates)
            {
                initialContext.Transaction.AddJobState(electContext.BackgroundJob.Id, state);
            }

            // Applying the elected state
            var context = new ApplyStateContext(initialContext.Transaction, electContext)
            {
                JobExpirationTimeout = initialContext.JobExpirationTimeout
            };

            foreach (var filter in applyFilters)
            {
                filter.OnStateUnapplied(context, context.Transaction);
            }

            foreach (var filter in applyFilters)
            {
                filter.OnStateApplied(context, context.Transaction);
            }

            return _innerStateMachine.ApplyState(context);
        }
예제 #12
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
 }
 void IApplyStateFilter.OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
 }
예제 #14
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.RemoveFromList("deleted", context.BackgroundJob.Id);
 }
예제 #15
0
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.InsertToList("succeeded", context.JobId);
     transaction.TrimList("succeeded", 0, 99);
 }
예제 #16
0
        public void Ctor_ShouldSetPropertiesCorrectly()
        {
            var context = new ApplyStateContext(
                _storage.Object,
                _connection.Object,
                _transaction.Object,
                _backgroundJob.Object,
                _newState.Object,
                OldState);

            Assert.Same(_storage.Object, context.Storage);
            Assert.Same(_connection.Object, context.Connection);
            Assert.Same(_transaction.Object, context.Transaction);
            Assert.Same(_backgroundJob.Object, context.BackgroundJob);
            Assert.Equal(OldState, context.OldStateName);
            Assert.Same(_newState.Object, context.NewState);
        }
예제 #17
0
		public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
		{
			transaction.RemoveFromSet("failed", context.JobId);
		}
        public IState ChangeState(StateChangeContext context)
        {
            // To ensure that job state will be changed only from one of the
            // specified states, we need to ensure that other users/workers
            // are not able to change the state of the job during the
            // execution of this method. To guarantee this behavior, we are
            // using distributed application locks and rely on fact, that
            // any state transitions will be made only within a such lock.
            using (context.Connection.AcquireDistributedJobLock(context.BackgroundJobId, JobLockTimeout))
            {
                var jobData = GetJobData(context);

                if (jobData == null)
                {
                    return(null);
                }

                if (context.ExpectedStates != null && !context.ExpectedStates.Contains(jobData.State, StringComparer.OrdinalIgnoreCase))
                {
                    return(null);
                }

                var stateToApply = context.NewState;

                try
                {
                    jobData.EnsureLoaded();
                }
                catch (JobLoadException ex)
                {
                    // This happens when Hangfire couldn't find the target method,
                    // serialized within a background job. There are many reasons
                    // for this case, including refactored code, or a missing
                    // assembly reference due to a mistake or erroneous deployment.
                    //
                    // The problem is that in this case we can't get any filters,
                    // applied at a method or a class level, and we can't proceed
                    // with the state change without breaking a consistent behavior:
                    // in some cases our filters will be applied, and in other ones
                    // will not.

                    // TODO 1.X/2.0:
                    // There's a problem with filters related to handling the states
                    // which ignore this exception, i.e. fitlers for the FailedState
                    // and the DeletedState, such as AutomaticRetryAttrubute filter.
                    //
                    // We should document that such a filters may not be fired, when
                    // we can't find a target method, and these filters should be
                    // applied only at the global level to get consistent results.
                    //
                    // In 2.0 we should have a special state for all the errors, when
                    // Hangfire doesn't know what to do, without any possibility to
                    // add method or class-level filters for such a state to provide
                    // the same behavior no matter what.

                    if (!stateToApply.IgnoreJobLoadException)
                    {
                        stateToApply = new FailedState(ex.InnerException)
                        {
                            Reason = $"Can not change the state to '{stateToApply.Name}': target method was not found."
                        };
                    }
                }

                using (var transaction = context.Connection.CreateWriteTransaction())
                {
                    var applyContext = new ApplyStateContext(
                        context.Storage,
                        context.Connection,
                        transaction,
                        new BackgroundJob(context.BackgroundJobId, jobData.Job, jobData.CreatedAt),
                        stateToApply,
                        jobData.State);

                    var appliedState = _stateMachine.ApplyState(applyContext);

                    transaction.Commit();

                    return(appliedState);
                }
            }
        }
예제 #19
0
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.IncrementCounter("stats:succeeded");
 }
예제 #20
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
 }
예제 #21
0
            public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
            {
                var enqueuedState = context.NewState as EnqueuedState;
                if (enqueuedState == null)
                {
                    throw new InvalidOperationException(
                        $"`{typeof (Handler).FullName}` state handler can be registered only for the Enqueued state.");
                }

                transaction.AddToQueue(enqueuedState.Queue, context.BackgroundJob.Id);
            }
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.RemoveFromSet("schedule", context.BackgroundJob.Id);
 }
예제 #23
0
        private void ApplyState(ApplyStateContext context, IEnumerable<IApplyStateFilter> filters)
        {
            using (var transaction = context.Connection.CreateWriteTransaction())
            {
                foreach (var handler in _handlers.GetHandlers(context.OldStateName))
                {
                    handler.Unapply(context, transaction);
                }

                foreach (var filter in filters)
                {
                    filter.OnStateUnapplied(context, transaction);
                }

                transaction.SetJobState(context.JobId, context.NewState);

                foreach (var handler in _handlers.GetHandlers(context.NewState.Name))
                {
                    handler.Apply(context, transaction);
                }

                foreach (var filter in filters)
                {
                    filter.OnStateApplied(context, transaction);
                }

                if (context.NewState.IsFinal)
                {
                    transaction.ExpireJob(context.JobId, context.JobExpirationTimeout);
                }
                else
                {
                    transaction.PersistJob(context.JobId);
                }

                transaction.Commit();
            }
        }
예제 #24
0
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.AddToSet("awaiting", context.JobId, JobHelper.ToTimestamp(DateTime.UtcNow));
 }
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.InsertToList("succeeded", context.JobId);
     transaction.TrimList("succeeded", 0, RedisStorage.SucceededListSize);
 }
예제 #26
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.RemoveFromSet("awaiting", context.JobId);
 }
예제 #27
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.DecrementCounter("stats:deleted");
 }
예제 #28
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.DecrementCounter("stats:deleted");
 }
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.AddToSet("processing", context.BackgroundJob.Id, JobHelper.ToTimestamp(DateTime.UtcNow));
 }
예제 #30
0
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.IncrementCounter("stats:succeeded");
 }
예제 #31
0
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.RemoveFromList("succeeded", context.JobId);
 }
예제 #32
0
 /// <inheritdoc />
 public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     if (context.NewState is ScheduledState &&
         context.NewState.Reason != null &&
         context.NewState.Reason.StartsWith("Retry attempt"))
     {
         transaction.AddToSet("retries", context.BackgroundJob.Id);
     }
 }
예제 #33
0
 public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.InsertToList("deleted", context.BackgroundJob.Id);
     transaction.TrimList("deleted", 0, 99);
 }
예제 #34
0
 /// <inheritdoc />
 public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     if (context.OldStateName == ScheduledState.StateName)
     {
         transaction.RemoveFromSet("retries", context.BackgroundJob.Id);
     }
 }
        private IState ChangeState(
            StateChangeContext context, BackgroundJob backgroundJob, IState toState, string oldStateName)
        {
            using (var transaction = context.Connection.CreateWriteTransaction())
            {
                var applyContext = new ApplyStateContext(
                    context.Storage,
                    context.Connection,
                    transaction,
                    backgroundJob,
                    toState,
                    oldStateName);

                var appliedState = _stateMachine.ApplyState(applyContext);

                transaction.Commit();

                return appliedState;
            }
        }
 public void OnStateUnapplied(ApplyStateContext content, IWriteOnlyTransaction transcation)
 {
 }
 public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
 {
     transaction.RemoveFromSet("processing", context.BackgroundJob.Id);
 }
예제 #38
0
        public IState ChangeState(StateChangeContext context)
        {
            // To ensure that job state will be changed only from one of the
            // specified states, we need to ensure that other users/workers
            // are not able to change the state of the job during the
            // execution of this method. To guarantee this behavior, we are
            // using distributed application locks and rely on fact, that
            // any state transitions will be made only within a such lock.
            using (context.Connection.AcquireDistributedJobLock(context.BackgroundJobId, JobLockTimeout))
            {
                var jobData = GetJobData(context);

                if (jobData == null)
                {
                    return(null);
                }

                if (context.ExpectedStates != null && !context.ExpectedStates.Contains(jobData.State, StringComparer.OrdinalIgnoreCase))
                {
                    return(null);
                }

                var stateToApply = context.NewState;

                try
                {
                    jobData.EnsureLoaded();
                }
                catch (JobLoadException ex)
                {
                    // This happens when Hangfire couldn't find the target method,
                    // serialized within a background job. There are many reasons
                    // for this case, including refactored code, or a missing
                    // assembly reference due to a mistake or erroneous deployment.
                    //
                    // The problem is that in this case we can't get any filters,
                    // applied at a method or a class level, and we can't proceed
                    // with the state change without breaking a consistent behavior:
                    // in some cases our filters will be applied, and in other ones
                    // will not.
                    if (!stateToApply.IgnoreJobLoadException)
                    {
                        stateToApply = new FailedState(ex.InnerException)
                        {
                            Reason = $"Can not change the state to '{stateToApply.Name}': target method was not found."
                        };
                    }
                }

                using (var transaction = context.Connection.CreateWriteTransaction())
                {
                    var applyContext = new ApplyStateContext(
                        context.Storage,
                        context.Connection,
                        transaction,
                        new BackgroundJob(context.BackgroundJobId, jobData.Job, jobData.CreatedAt),
                        stateToApply,
                        jobData.State,
                        context.Profiler);

                    // State changing process can fail due to an exception in state filters themselves,
                    // and DisableFilters property will cause state machine to perform a state transition
                    // without calling any filters. This is required when all the other state change
                    // attempts failed and we need to remove such a job from the processing pipeline.
                    // In this case all the filters are ignored, which may lead to confusion, so it's
                    // highly recommended to use the DisableFilters property only when changing state
                    // to the FailedState.
                    var stateMachine = context.DisableFilters ? _innerStateMachine : _stateMachine;
                    var appliedState = stateMachine.ApplyState(applyContext);

                    transaction.Commit();

                    return(appliedState);
                }
            }
        }