Пример #1
0
        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 RetryAttributeFacts()
        {
            _failedState = new FailedState(new InvalidOperationException());
            _connection = new Mock<IStorageConnection>();

            _context = new ElectStateContextMock();
            _context.StateContextValue.JobIdValue = JobId;
            _context.StateContextValue.ConnectionValue = _connection;
            _context.CandidateStateValue = _failedState;
        }
Пример #3
0
        public void SerializeData_ReturnsCorrectData()
        {
            var state = new FailedState(new Exception("Message"));

            var serializedData = state.SerializeData();

            Assert.Equal(JobHelper.ToStringTimestamp(state.FailedAt), serializedData["FailedAt"]);
            Assert.Equal("System.Exception", serializedData["ExceptionType"]);
            Assert.Equal("Message", serializedData["ExceptionMessage"]);
            Assert.Equal(state.Exception.ToString(), serializedData["ExceptionDetails"]);
        }
Пример #4
0
        public void GetStateData_ReturnsCorrectData()
        {
            var state = new FailedState(new Exception("Message"));

            DictionaryAssert.ContainsFollowingItems(
                new Dictionary<string, string>
                {
                    { "FailedAt", "<UtcNow timestamp>" },
                    { "ExceptionType", "System.Exception" },
                    { "ExceptionMessage", "Message" },
                    { "ExceptionDetails", "<Non-empty>" }
                }, 
                state.Serialize());
        }
Пример #5
0
        private void ProcessJob(
            string jobId,
            IStorageConnection connection, 
            IJobPerformanceProcess process,
            CancellationToken shutdownToken)
        {
            var stateMachine = _context.StateMachineFactory.Create(connection);
            var processingState = new ProcessingState(_context.ServerId, _context.WorkerNumber);

            if (!stateMachine.TryToChangeState(
                jobId,
                processingState,
                new[] { EnqueuedState.StateName, ProcessingState.StateName }))
            {
                return;
            }

            // Checkpoint #3. Job is in the Processing state. However, there are
            // no guarantees that it was performed. We need to re-queue it even
            // it was performed to guarantee that it was performed AT LEAST once.
            // It will be re-queued after the JobTimeout was expired.

            IState state;

            try
            {
                var jobData = connection.GetJobData(jobId);
                jobData.EnsureLoaded();

                var cancellationToken = new ServerJobCancellationToken(
                    jobId, connection, _context, shutdownToken);

                var performContext = new PerformContext(
                    _context, connection, jobId, jobData.Job, jobData.CreatedAt, cancellationToken);

                var latency = (DateTime.UtcNow - jobData.CreatedAt).TotalMilliseconds;
                var duration = Stopwatch.StartNew();

                process.Run(performContext, jobData.Job);
                duration.Stop();

                state = new SucceededState((long) latency, duration.ElapsedMilliseconds);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (JobPerformanceException ex)
            {
                state = new FailedState(ex.InnerException)
                {
                    Reason = ex.Message
                };
            }
            catch (Exception ex)
            {
                state = new FailedState(ex)
                {
                    Reason = "Internal HangFire Server exception occurred. Please, report it to HangFire developers."
                };
            }

            // Ignore return value, because we should not do
            // anything when current state is not Processing.
            stateMachine.TryToChangeState(jobId, state, new[] { ProcessingState.StateName });
        }
Пример #6
0
 public void StateName_IsCorrect()
 {
     var state = new FailedState(new Exception());
     Assert.Equal(FailedState.StateName, state.Name);
 }
Пример #7
0
        public bool TryToChangeState(
            string jobId, State toState, string[] fromStates)
        {
            if (jobId == null) throw new ArgumentNullException("jobId");
            if (toState == null) throw new ArgumentNullException("toState");
            if (fromStates == null) throw new ArgumentNullException("fromStates");

            // 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 (_connection.AcquireJobLock(jobId))
            {
                var jobData = _connection.GetJobStateAndInvocationData(jobId);

                if (jobData == null)
                {
                    // The job does not exist. This may happen, because not
                    // all storage backends support foreign keys. 
                    return false;
                }

                if (!fromStates.Contains(jobData.State, StringComparer.OrdinalIgnoreCase))
                {
                    return false;
                }

                MethodData methodData = null;
                bool loadSucceeded = true;

                try
                {
                    methodData = MethodData.Deserialize(jobData.InvocationData);
                }
                catch (JobLoadException ex)
                {
                    // If the job type could not be loaded, we are unable to
                    // load corresponding filters, unable to process the job
                    // and sometimes unable to change its state (the enqueued
                    // state depends on the type of a job).

                    toState = new FailedState(ex)
                    {
                        Reason = String.Format(
                            "Could not change the state of the job '{0}' to the '{1}'. See the inner exception for details.",
                            toState.Name, jobId)
                    };

                    loadSucceeded = false;
                }

                var context = new StateContext(jobId, methodData);
                var stateChanged = ChangeState(context, toState, jobData.State);

                return loadSucceeded && stateChanged;
            }
        }
Пример #8
0
        private void PerformJob(IStorageConnection connection, JobPayload payload)
        {
            if (payload == null)
            {
                return;
            }

            var stateMachine = new StateMachine(connection);
            var processingState = new ProcessingState(_context.ServerName);

            if (!stateMachine.TryToChangeState(
                payload.Id, 
                processingState, 
                new [] { EnqueuedState.StateName, ProcessingState.StateName }))
            {
                return;
            }

            // Checkpoint #3. Job is in the Processing state. However, there are
            // no guarantees that it was performed. We need to re-queue it even
            // it was performed to guarantee that it was performed AT LEAST once.
            // It will be re-queued after the JobTimeout was expired.

            State state;

            try
            {
                IJobPerformStrategy performStrategy;

                var methodData = MethodData.Deserialize(payload.InvocationData);
                if (methodData.OldFormat)
                {
                    // For compatibility with the Old Client API.
                    // TODO: remove it in version 1.0
                    var arguments = JobHelper.FromJson<Dictionary<string, string>>(
                        payload.Args);

                    performStrategy = new JobAsClassPerformStrategy(
                        methodData, arguments);
                }
                else
                {
                    var arguments = JobHelper.FromJson<string[]>(payload.Arguments);

                    performStrategy = new JobAsMethodPerformStrategy(
                        methodData, arguments);
                }
                
                var performContext = new PerformContext(_context, connection, payload.Id, methodData);
                _context.PerformancePipeline.Run(performContext, performStrategy);

                state = new SucceededState();
            }
            catch (JobPerformanceException ex)
            {
                state = new FailedState(ex.InnerException)
                {
                    Reason = ex.Message
                };
            }
            catch (Exception ex)
            {
                state = new FailedState(ex)
                {
                    Reason = "Internal HangFire Server exception occurred. Please, report it to HangFire developers."
                };
            }

            // TODO: check return value
            stateMachine.TryToChangeState(payload.Id, state, new [] { ProcessingState.StateName });
        }
Пример #9
0
        public bool TryToChangeState(
            string jobId, IState toState, string[] fromStates)
        {
            if (jobId == null) throw new ArgumentNullException("jobId");
            if (toState == null) throw new ArgumentNullException("toState");
            if (fromStates != null && fromStates.Length == 0)
            {
                throw new ArgumentException("From states array should be null or non-empty.", "fromStates");
            }

            // 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 (_connection.AcquireDistributedLock(
                String.Format("job:{0}:state-lock", jobId),
                JobLockTimeout))
            {
                var jobData = _connection.GetJobData(jobId);

                if (jobData == null)
                {
                    // The job does not exist. This may happen, because not
                    // all storage backends support foreign keys. 
                    return false;
                }

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

                bool loadSucceeded = true;

                try
                {
                    jobData.EnsureLoaded();
                }
                catch (JobLoadException ex)
                {
                    // If the job type could not be loaded, we are unable to
                    // load corresponding filters, unable to process the job
                    // and sometimes unable to change its state (the enqueued
                    // state depends on the type of a job).

                    if (!toState.IgnoreJobLoadException)
                    {
                        toState = new FailedState(ex.InnerException)
                        {
                            Reason = String.Format(
                                "Can not change the state of a job to '{0}': target method was not found.",
                                toState.Name)
                        };

                        loadSucceeded = false;
                    }
                }

                var context = new StateContext(jobId, jobData.Job, jobData.CreatedAt, _connection);
                var stateChanged = _stateChangeProcess.ChangeState(context, toState, jobData.State);

                return loadSucceeded && stateChanged;
            }
        }
Пример #10
0
 public void IgnoreExceptions_ReturnsFalse()
 {
     var state = new FailedState(new Exception());
     Assert.False(state.IgnoreJobLoadException);
 }
Пример #11
0
        public void IsFinal_ReturnsFalse()
        {
            var state = new FailedState(new Exception());

            Assert.False(state.IsFinal);
        }