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.FromDays(30)); 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, oldStateName: null, profiler: context.Profiler); _stateMachine.ApplyState(applyContext); transaction.Commit(); } } return(backgroundJob); }
public static void EnqueueBackgroundJob( [NotNull] this IStateMachine stateMachine, [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] IWriteOnlyTransaction transaction, [NotNull] RecurringJobEntity recurringJob, [NotNull] BackgroundJob backgroundJob, [CanBeNull] string reason, [NotNull] IProfiler profiler) { if (stateMachine == null) { throw new ArgumentNullException(nameof(stateMachine)); } if (storage == null) { throw new ArgumentNullException(nameof(storage)); } if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (transaction == null) { throw new ArgumentNullException(nameof(transaction)); } if (recurringJob == null) { throw new ArgumentNullException(nameof(recurringJob)); } if (backgroundJob == null) { throw new ArgumentNullException(nameof(backgroundJob)); } if (profiler == null) { throw new ArgumentNullException(nameof(profiler)); } var state = new EnqueuedState { Reason = reason }; if (recurringJob.Queue != null) { state.Queue = recurringJob.Queue; } stateMachine.ApplyState(new ApplyStateContext( storage, connection, transaction, backgroundJob, state, null, profiler)); }
private IState ChangeState( StateChangeContext context, BackgroundJob backgroundJob, IState toState, string oldStateName) { Exception exception = null; for (var i = 0; i < MaxStateChangeAttempts; i++) { try { 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); } } catch (Exception ex) { exception = ex; } } var failedState = new FailedState(exception) { Reason = $"Failed to change state to a '{toState.Name}' one due to an exception after {MaxStateChangeAttempts} retry attempts" }; using (var transaction = context.Connection.CreateWriteTransaction()) { _coreStateMachine.ApplyState(new ApplyStateContext( context.Storage, context.Connection, transaction, backgroundJob, failedState, oldStateName)); transaction.Commit(); } return(failedState); }
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) { electContext.Profiler.InvokeMeasured( Tuple.Create(filter, electContext), InvokeOnStateElection, $"OnStateElection for {electContext.BackgroundJob.Id}"); } 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) { context.Profiler.InvokeMeasured( Tuple.Create(filter, context), InvokeOnStateUnapplied, $"OnStateUnapplied for {context.BackgroundJob.Id}"); } foreach (var filter in applyFilters) { context.Profiler.InvokeMeasured( Tuple.Create(filter, context), InvokeOnStateApplied, $"OnStateApplied for {context.BackgroundJob.Id}"); } return(_innerStateMachine.ApplyState(context)); }
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 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)); }
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); } } }