public bool EnqueueNextScheduledJob() { using (var connection = _storage.GetConnection()) { var timestamp = JobHelper.ToTimestamp(DateTime.UtcNow); // TODO: it is very slow. Add batching. var jobId = connection .GetFirstByLowestScoreFromSet("schedule", 0, timestamp); if (String.IsNullOrEmpty(jobId)) { return false; } var stateMachine = new StateMachine(connection); var enqueuedState = new EnqueuedState { Reason = "Enqueued as a scheduled job" }; stateMachine.TryToChangeState(jobId, enqueuedState, new [] { ScheduledState.StateName }); return true; } }
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 }); }
private bool RequeueJobIfTimedOut(IRedisClient redis, string jobId, string queue) { var flags = redis.GetValuesFromHash( String.Format("hangfire:job:{0}", jobId), "Fetched", "Checked"); var fetched = flags[0]; var @checked = flags[1]; if (String.IsNullOrEmpty(fetched) && String.IsNullOrEmpty(@checked)) { // If the job does not have these flags set, then it is // in the implicit 'Fetched' state. This state has no // information about the time it was fetched. So we // can not do anything with the job in this state, because // there are two options: // 1. It is going to move to the implicit 'Fetched' state // in a short time. // 2. It will stay in the 'Fetched' state forever due to // its processing server is dead. // To ensure its server is dead, we'll move the job to // the implicit 'Checked' state with the current timestamp // and will not do anything else at this pass of the watcher. // If job's state will still be 'Checked' on the later passes // and after the CheckedTimeout expired, then the server // is dead, and we'll re-queue the job. redis.SetEntryInHash( String.Format("hangfire:job:{0}", jobId), "Checked", JobHelper.ToStringTimestamp(DateTime.UtcNow)); // Checkpoint #1-2. The job is in the implicit 'Checked' state. // It will be re-queued after the CheckedTimeout will be expired. } else { if (TimedOutByFetchedTime(fetched) || TimedOutByCheckedTime(fetched, @checked)) { var stateMachine = new StateMachine(new RedisConnection(_storage, redis)); var state = new EnqueuedState{ Reason = "Re-queued due to time out" }; stateMachine.TryToChangeState( jobId, state, new [] { EnqueuedState.StateName, ProcessingState.StateName }); RedisConnection.RemoveFromFetchedList(redis, queue, jobId); return true; } } return false; }