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; } }
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; }
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"]); }
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()); }
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 }); }
public void StateName_IsCorrect() { var state = new FailedState(new Exception()); Assert.Equal(FailedState.StateName, state.Name); }
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; } }
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 }); }
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; } }
public void IgnoreExceptions_ReturnsFalse() { var state = new FailedState(new Exception()); Assert.False(state.IgnoreJobLoadException); }
public void IsFinal_ReturnsFalse() { var state = new FailedState(new Exception()); Assert.False(state.IsFinal); }