/// <inheritdoc /> public override void SaveJobDetails(JobDetails jobDetails) { if (jobDetails == null) { throw new ArgumentNullException("jobDetails"); } VersionedJobDetails versionedJobDetails = (VersionedJobDetails)jobDetails; lock (jobs) { ThrowIfDisposed(); string jobName = jobDetails.JobSpec.Name; VersionedJobDetails existingJobDetails; if (!jobs.TryGetValue(jobName, out existingJobDetails)) { throw new ConcurrentModificationException( "The job details could not be saved because the job was concurrently deleted."); } if (existingJobDetails.Version != versionedJobDetails.Version) { throw new ConcurrentModificationException( "The job details could not be saved because the job was concurrently modified."); } versionedJobDetails.Version += 1; jobs[jobName] = (VersionedJobDetails)versionedJobDetails.Clone(); Monitor.PulseAll(jobs); } }
private void InternalUpdateJob(VersionedJobDetails existingJobDetails, JobSpec updatedJobSpec) { if (existingJobDetails.JobSpec.Name != updatedJobSpec.Name) { if (jobs.ContainsKey(updatedJobSpec.Name)) { throw new SchedulerException(String.Format(CultureInfo.CurrentCulture, "Cannot rename job '{0}' to '{1}' because there already exists another job with the new name.", existingJobDetails.JobSpec.Name, updatedJobSpec.Name)); } jobs.Remove(existingJobDetails.JobSpec.Name); jobs.Add(updatedJobSpec.Name, existingJobDetails); } existingJobDetails.Version += 1; existingJobDetails.JobSpec = updatedJobSpec.Clone(); if (existingJobDetails.JobState == JobState.Scheduled) { existingJobDetails.JobState = JobState.Pending; } Monitor.PulseAll(jobs); }
/// <inheritdoc /> protected override JobDetails GetNextJobToProcessOrWaitUntilSignaled(Guid schedulerGuid) { lock (syncRoot) { ThrowIfDisposed(); DateTime timeBasisUtc = DateTime.UtcNow; DateTime?nextTriggerFireTimeUtc; try { VersionedJobDetails nextJob = jobStoreDao.GetNextJobToProcess(clusterName, schedulerGuid, timeBasisUtc, schedulerExpirationTimeInSeconds, out nextTriggerFireTimeUtc); if (nextJob != null) { return(nextJob); } } catch (Exception ex) { Logger.Warn(String.Format("The job store was unable to poll the database for the next job to process. " + "It will try again in {0} seconds.", pollIntervalInSeconds), ex); nextTriggerFireTimeUtc = null; } // Wait for a signal or the next fire time whichever comes first. if (nextTriggerFireTimeUtc.HasValue) { // Update the time basis because the SP may have taken a non-trivial // amount of time to run. timeBasisUtc = DateTime.UtcNow; if (nextTriggerFireTimeUtc.Value <= timeBasisUtc) { return(null); } // Need to ensure that wait time in millis will fit in a 32bit integer, otherwise the // Monitor.Wait will throw an ArgumentException (even when using the TimeSpan based overload). // This can happen when the next trigger fire time is very far out into the future like DateTime.MaxValue. TimeSpan waitTimeSpan = nextTriggerFireTimeUtc.Value - timeBasisUtc; int waitMillis = Math.Min((int)Math.Min(int.MaxValue, waitTimeSpan.TotalMilliseconds), pollIntervalInSeconds * 1000); Monitor.Wait(syncRoot, waitMillis); } else { Monitor.Wait(syncRoot, pollIntervalInSeconds * 1000); } } return(null); }
/// <inheritdoc /> public override void SaveJobDetails(JobDetails jobDetails) { if (jobDetails == null) { throw new ArgumentNullException("jobStatus"); } VersionedJobDetails versionedJobDetails = (VersionedJobDetails)jobDetails; ThrowIfDisposed(); jobStoreDao.SaveJobDetails(clusterName, versionedJobDetails); }
/// <summary> /// Builds a job details object from the result set returned by the spSCHED_GetJobDetails /// and spSCHED_GetNextJob stored procedures. /// </summary> /// <param name="reader">The reader for the result set</param> /// <returns>The job details object</returns> protected virtual VersionedJobDetails BuildJobDetailsFromResultSet(IDataReader reader) { string jobName = reader.GetString(0); string jobDescription = reader.GetString(1); string jobKey = reader.GetString(2); Trigger trigger = (Trigger)DbUtils.DeserializeObject(DbUtils.MapDbValueToObject <byte[]>(reader.GetValue(3))); JobData jobData = (JobData)DbUtils.DeserializeObject(DbUtils.MapDbValueToObject <byte[]>(reader.GetValue(4))); DateTime creationTimeUtc = DateTimeUtils.AssumeUniversalTime(reader.GetDateTime(5)); JobState jobState = (JobState)reader.GetInt32(6); DateTime?nextTriggerFireTimeUtc = DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable <DateTime>(reader.GetValue(7))); int? nextTriggerMisfireThresholdSeconds = DbUtils.MapDbValueToNullable <int>(reader.GetValue(8)); TimeSpan?nextTriggerMisfireThreshold = nextTriggerMisfireThresholdSeconds.HasValue ? new TimeSpan(0, 0, nextTriggerMisfireThresholdSeconds.Value) : (TimeSpan?)null; Guid? lastExecutionSchedulerGuid = DbUtils.MapDbValueToNullable <Guid>(reader.GetValue(9)); DateTime?lastExecutionStartTimeUtc = DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable <DateTime>(reader.GetValue(10))); DateTime?lastExecutionEndTimeUtc = DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable <DateTime>(reader.GetValue(11))); bool? lastExecutionSucceeded = DbUtils.MapDbValueToNullable <bool>(reader.GetValue(12)); string lastExecutionStatusMessage = DbUtils.MapDbValueToObject <string>(reader.GetValue(13)); int version = reader.GetInt32(14); JobSpec jobSpec = new JobSpec(jobName, jobDescription, jobKey, trigger); jobSpec.JobData = jobData; VersionedJobDetails details = new VersionedJobDetails(jobSpec, creationTimeUtc, version); details.JobState = jobState; details.NextTriggerFireTimeUtc = nextTriggerFireTimeUtc; details.NextTriggerMisfireThreshold = nextTriggerMisfireThreshold; if (lastExecutionSchedulerGuid.HasValue && lastExecutionStartTimeUtc.HasValue) { JobExecutionDetails execution = new JobExecutionDetails(lastExecutionSchedulerGuid.Value, lastExecutionStartTimeUtc.Value); execution.EndTimeUtc = lastExecutionEndTimeUtc; execution.Succeeded = lastExecutionSucceeded.GetValueOrDefault(); execution.StatusMessage = lastExecutionStatusMessage == null ? "" : lastExecutionStatusMessage; details.LastJobExecutionDetails = execution; } return(details); }
public void UpdateJob_IncrementsVersionNumber() { Mocks.ReplayAll(); VersionedJobDetails jobDetails = (VersionedJobDetails)CreatePendingJob("job", DateTime.UtcNow); int originalVersion = jobDetails.Version; jobDetails.JobSpec.Name = "renamedJob"; JobStore.UpdateJob("job", jobDetails.JobSpec); VersionedJobDetails updatedJobDetails = (VersionedJobDetails)JobStore.GetJobDetails("renamedJob"); Assert.AreEqual(originalVersion + 1, updatedJobDetails.Version, "Version number of saved object should be incremented in database."); }
public void SaveJobDetails_IncrementsVersionNumber() { Mocks.ReplayAll(); VersionedJobDetails jobDetails = (VersionedJobDetails)CreatePendingJob("job", DateTime.UtcNow); int originalVersion = jobDetails.Version; jobDetails.JobState = JobState.Stopped; JobStore.SaveJobDetails(jobDetails); Assert.AreEqual(originalVersion + 1, jobDetails.Version, "Version number of original object should be incremented in place."); VersionedJobDetails updatedJobDetails = (VersionedJobDetails)JobStore.GetJobDetails("job"); Assert.AreEqual(originalVersion + 1, updatedJobDetails.Version, "Version number of saved object should be incremented in memory too."); }
/// <inheritdoc /> public override bool CreateJob(JobSpec jobSpec, DateTime creationTimeUtc, CreateJobConflictAction conflictAction) { if (jobSpec == null) { throw new ArgumentNullException("jobSpec"); } if (!Enum.IsDefined(typeof(CreateJobConflictAction), conflictAction)) { throw new ArgumentOutOfRangeException("conflictAction"); } lock (jobs) { ThrowIfDisposed(); VersionedJobDetails existingJobDetails; if (jobs.TryGetValue(jobSpec.Name, out existingJobDetails)) { switch (conflictAction) { case CreateJobConflictAction.Ignore: return(false); case CreateJobConflictAction.Update: InternalUpdateJob(existingJobDetails, jobSpec); return(true); case CreateJobConflictAction.Replace: break; case CreateJobConflictAction.Throw: throw new SchedulerException(String.Format(CultureInfo.CurrentCulture, "There is already a job with name '{0}'.", jobSpec.Name)); } } VersionedJobDetails jobDetails = new VersionedJobDetails(jobSpec.Clone(), creationTimeUtc, 0); jobs[jobSpec.Name] = jobDetails; Monitor.PulseAll(jobs); return(true); } }
/// <inheritdoc /> public override bool CreateJob(JobSpec jobSpec, DateTime creationTimeUtc, CreateJobConflictAction conflictAction) { if (jobSpec == null) throw new ArgumentNullException("jobSpec"); if (!Enum.IsDefined(typeof (CreateJobConflictAction), conflictAction)) throw new ArgumentOutOfRangeException("conflictAction"); lock (jobs) { ThrowIfDisposed(); VersionedJobDetails existingJobDetails; if (jobs.TryGetValue(jobSpec.Name, out existingJobDetails)) { switch (conflictAction) { case CreateJobConflictAction.Ignore: return false; case CreateJobConflictAction.Update: InternalUpdateJob(existingJobDetails, jobSpec); return true; case CreateJobConflictAction.Replace: break; case CreateJobConflictAction.Throw: throw new SchedulerException(String.Format(CultureInfo.CurrentCulture, "There is already a job with name '{0}'.", jobSpec.Name)); } } VersionedJobDetails jobDetails = new VersionedJobDetails(jobSpec.Clone(), creationTimeUtc, 0); jobs[jobSpec.Name] = jobDetails; Monitor.PulseAll(jobs); return true; } }
/// <summary> /// Builds a job details object from the result set returned by the spSCHED_GetJobDetails /// and spSCHED_GetNextJob stored procedures. /// </summary> /// <param name="reader">The reader for the result set</param> /// <returns>The job details object</returns> protected virtual VersionedJobDetails BuildJobDetailsFromResultSet(IDataReader reader) { string jobName = reader.GetString(0); string jobDescription = reader.GetString(1); string jobKey = reader.GetString(2); Trigger trigger = (Trigger) DbUtils.DeserializeObject(DbUtils.MapDbValueToObject<byte[]>(reader.GetValue(3))); JobData jobData = (JobData) DbUtils.DeserializeObject(DbUtils.MapDbValueToObject<byte[]>(reader.GetValue(4))); DateTime creationTimeUtc = DateTimeUtils.AssumeUniversalTime(reader.GetDateTime(5)); JobState jobState = (JobState) reader.GetInt32(6); DateTime? nextTriggerFireTimeUtc = DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable<DateTime>(reader.GetValue(7))); int? nextTriggerMisfireThresholdSeconds = DbUtils.MapDbValueToNullable<int>(reader.GetValue(8)); TimeSpan? nextTriggerMisfireThreshold = nextTriggerMisfireThresholdSeconds.HasValue ? new TimeSpan(0, 0, nextTriggerMisfireThresholdSeconds.Value) : (TimeSpan?) null; Guid? lastExecutionSchedulerGuid = DbUtils.MapDbValueToNullable<Guid>(reader.GetValue(9)); DateTime? lastExecutionStartTimeUtc = DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable<DateTime>(reader.GetValue(10))); DateTime? lastExecutionEndTimeUtc = DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable<DateTime>(reader.GetValue(11))); bool? lastExecutionSucceeded = DbUtils.MapDbValueToNullable<bool>(reader.GetValue(12)); string lastExecutionStatusMessage = DbUtils.MapDbValueToObject<string>(reader.GetValue(13)); int version = reader.GetInt32(14); JobSpec jobSpec = new JobSpec(jobName, jobDescription, jobKey, trigger); jobSpec.JobData = jobData; VersionedJobDetails details = new VersionedJobDetails(jobSpec, creationTimeUtc, version); details.JobState = jobState; details.NextTriggerFireTimeUtc = nextTriggerFireTimeUtc; details.NextTriggerMisfireThreshold = nextTriggerMisfireThreshold; if (lastExecutionSchedulerGuid.HasValue && lastExecutionStartTimeUtc.HasValue) { JobExecutionDetails execution = new JobExecutionDetails(lastExecutionSchedulerGuid.Value, lastExecutionStartTimeUtc.Value); execution.EndTimeUtc = lastExecutionEndTimeUtc; execution.Succeeded = lastExecutionSucceeded.GetValueOrDefault(); execution.StatusMessage = lastExecutionStatusMessage == null ? "" : lastExecutionStatusMessage; details.LastJobExecutionDetails = execution; } return details; }
/// <summary> /// Saves details for the job. /// </summary> /// <param name="clusterName">The cluster name, never null</param> /// <param name="jobDetails">The job details, never null</param> /// <exception cref="SchedulerException">Thrown if an error occurs</exception> public virtual void SaveJobDetails(string clusterName, VersionedJobDetails jobDetails) { try { using (IDbConnection connection = CreateConnection()) { IDbCommand command = CreateStoredProcedureCommand(connection, "spSCHED_SaveJobDetails"); AddInputParameter(command, "ClusterName", DbType.String, clusterName); AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name); AddInputParameter(command, "JobDescription", DbType.String, jobDetails.JobSpec.Description); AddInputParameter(command, "JobKey", DbType.String, jobDetails.JobSpec.JobKey); AddInputParameter(command, "TriggerObject", DbType.Binary, DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.Trigger))); AddInputParameter(command, "JobDataObject", DbType.Binary, DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.JobData))); AddInputParameter(command, "JobState", DbType.Int32, jobDetails.JobState); AddInputParameter(command, "NextTriggerFireTime", DbType.DateTime, DbUtils.MapNullableToDbValue(jobDetails.NextTriggerFireTimeUtc)); int? nextTriggerMisfireThresholdSeconds = jobDetails.NextTriggerMisfireThreshold.HasValue ? (int?) jobDetails.NextTriggerMisfireThreshold.Value.TotalSeconds : null; AddInputParameter(command, "NextTriggerMisfireThresholdSeconds", DbType.Int32, DbUtils.MapNullableToDbValue(nextTriggerMisfireThresholdSeconds)); JobExecutionDetails execution = jobDetails.LastJobExecutionDetails; AddInputParameter(command, "LastExecutionSchedulerGUID", DbType.Guid, execution != null ? (object) execution.SchedulerGuid : DBNull.Value); AddInputParameter(command, "LastExecutionStartTime", DbType.DateTime, execution != null ? (object) execution.StartTimeUtc : DBNull.Value); AddInputParameter(command, "LastExecutionEndTime", DbType.DateTime, execution != null ? DbUtils.MapNullableToDbValue(execution.EndTimeUtc) : DBNull.Value); AddInputParameter(command, "LastExecutionSucceeded", DbType.Boolean, execution != null ? (object) execution.Succeeded : DBNull.Value); AddInputParameter(command, "LastExecutionStatusMessage", DbType.String, execution != null ? (object) execution.StatusMessage : DBNull.Value); AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version); IDbDataParameter wasSavedParam = AddOutputParameter(command, "WasSaved", DbType.Boolean); connection.Open(); command.ExecuteNonQuery(); bool wasSaved = (bool) wasSavedParam.Value; if (!wasSaved) throw new ConcurrentModificationException( String.Format("Job '{0}' does not exist or was concurrently modified in the database.", jobDetails.JobSpec.Name)); jobDetails.Version += 1; } } catch (Exception ex) { throw new SchedulerException("The job store was unable to save job details to the database.", ex); } }
/// <summary> /// Saves details for the job. /// </summary> /// <param name="clusterName">The cluster name, never null</param> /// <param name="jobDetails">The job details, never null</param> /// <exception cref="SchedulerException">Thrown if an error occurs</exception> public virtual void SaveJobDetails(string clusterName, VersionedJobDetails jobDetails) { try { using (IDbConnection connection = CreateConnection()) { IDbCommand command = CreateStoredProcedureCommand(connection, "spSCHED_SaveJobDetails"); AddInputParameter(command, "ClusterName", DbType.String, clusterName); AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name); AddInputParameter(command, "JobDescription", DbType.String, jobDetails.JobSpec.Description); AddInputParameter(command, "JobKey", DbType.String, jobDetails.JobSpec.JobKey); AddInputParameter(command, "TriggerObject", DbType.Binary, DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.Trigger))); AddInputParameter(command, "JobDataObject", DbType.Binary, DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.JobData))); AddInputParameter(command, "JobState", DbType.Int32, jobDetails.JobState); AddInputParameter(command, "NextTriggerFireTime", DbType.DateTime, DbUtils.MapNullableToDbValue(jobDetails.NextTriggerFireTimeUtc)); int?nextTriggerMisfireThresholdSeconds = jobDetails.NextTriggerMisfireThreshold.HasValue ? (int?)jobDetails.NextTriggerMisfireThreshold.Value.TotalSeconds : null; AddInputParameter(command, "NextTriggerMisfireThresholdSeconds", DbType.Int32, DbUtils.MapNullableToDbValue(nextTriggerMisfireThresholdSeconds)); JobExecutionDetails execution = jobDetails.LastJobExecutionDetails; AddInputParameter(command, "LastExecutionSchedulerGUID", DbType.Guid, execution != null ? (object)execution.SchedulerGuid : DBNull.Value); AddInputParameter(command, "LastExecutionStartTime", DbType.DateTime, execution != null ? (object)execution.StartTimeUtc : DBNull.Value); AddInputParameter(command, "LastExecutionEndTime", DbType.DateTime, execution != null ? DbUtils.MapNullableToDbValue(execution.EndTimeUtc) : DBNull.Value); AddInputParameter(command, "LastExecutionSucceeded", DbType.Boolean, execution != null ? (object)execution.Succeeded : DBNull.Value); AddInputParameter(command, "LastExecutionStatusMessage", DbType.String, execution != null ? (object)execution.StatusMessage : DBNull.Value); AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version); IDbDataParameter wasSavedParam = AddOutputParameter(command, "WasSaved", DbType.Boolean); connection.Open(); command.ExecuteNonQuery(); bool wasSaved = (bool)wasSavedParam.Value; if (!wasSaved) { throw new ConcurrentModificationException( String.Format("Job '{0}' does not exist or was concurrently modified in the database.", jobDetails.JobSpec.Name)); } jobDetails.Version += 1; } } catch (Exception ex) { throw new SchedulerException("The job store was unable to save job details to the database.", ex); } }
private void InternalUpdateJob(VersionedJobDetails existingJobDetails, JobSpec updatedJobSpec) { if (existingJobDetails.JobSpec.Name != updatedJobSpec.Name) { if (jobs.ContainsKey(updatedJobSpec.Name)) throw new SchedulerException(String.Format(CultureInfo.CurrentCulture, "Cannot rename job '{0}' to '{1}' because there already exists another job with the new name.", existingJobDetails.JobSpec.Name, updatedJobSpec.Name)); jobs.Remove(existingJobDetails.JobSpec.Name); jobs.Add(updatedJobSpec.Name, existingJobDetails); } existingJobDetails.Version += 1; existingJobDetails.JobSpec = updatedJobSpec.Clone(); if (existingJobDetails.JobState == JobState.Scheduled) existingJobDetails.JobState = JobState.Pending; Monitor.PulseAll(jobs); }
/// <summary> /// Saves details for the job. /// </summary> /// <param name="clusterName">The cluster name, never null</param> /// <param name="jobDetails">The job details, never null</param> /// <exception cref="SchedulerException">Thrown if an error occurs</exception> public virtual void SaveJobDetails(string clusterName, VersionedJobDetails jobDetails) { try { using (IDbConnection connection = CreateConnection()) { connection.Open(); using (IDbTransaction transaction = connection.BeginTransaction()) { try { // // Look for a job with the existing name and version. // using (IDbCommand command = CreateCommand(connection, transaction, SelectVersionedJobCount)) { AddInputParameter(command, "ClusterName", DbType.String, clusterName); AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name); AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version); int jobCount = (int)command.ExecuteScalar(); if (jobCount == 0) { throw new ConcurrentModificationException( String.Format("Job '{0}' does not exist or was concurrently modified in the database.", jobDetails.JobSpec.Name)); } } // // Update the job. // using (IDbCommand command = CreateCommand(connection, transaction, SaveJobDetailsUpdateJob)) { AddInputParameter(command, "ClusterName", DbType.String, clusterName); AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name); AddInputParameter(command, "JobDescription", DbType.String, jobDetails.JobSpec.Description); AddInputParameter(command, "JobKey", DbType.String, jobDetails.JobSpec.JobKey); AddInputParameter(command, "TriggerObject", DbType.Binary, DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.Trigger))); AddInputParameter(command, "JobDataObject", DbType.Binary, DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.JobData))); AddInputParameter(command, "JobState", DbType.Int32, jobDetails.JobState); AddInputParameter(command, "NextTriggerFireTime", DbType.DateTime, DbUtils.MapNullableToDbValue(jobDetails.NextTriggerFireTimeUtc)); int? nextTriggerMisfireThresholdSeconds = jobDetails.NextTriggerMisfireThreshold.HasValue ? (int?)jobDetails.NextTriggerMisfireThreshold.Value.TotalSeconds : null; AddInputParameter(command, "NextTriggerMisfireThresholdSeconds", DbType.Int32, DbUtils.MapNullableToDbValue(nextTriggerMisfireThresholdSeconds)); JobExecutionDetails execution = jobDetails.LastJobExecutionDetails; AddInputParameter(command, "LastExecutionSchedulerGUID", DbType.Guid, execution != null ? (object)execution.SchedulerGuid : DBNull.Value); AddInputParameter(command, "LastExecutionStartTime", DbType.DateTime, execution != null ? (object)execution.StartTimeUtc : DBNull.Value); AddInputParameter(command, "LastExecutionEndTime", DbType.DateTime, execution != null ? DbUtils.MapNullableToDbValue(execution.EndTimeUtc) : DBNull.Value); AddInputParameter(command, "LastExecutionSucceeded", DbType.Boolean, execution != null ? (object)execution.Succeeded : DBNull.Value); AddInputParameter(command, "LastExecutionStatusMessage", DbType.String, execution != null ? (object)execution.StatusMessage : DBNull.Value); AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version); command.ExecuteNonQuery(); } transaction.Commit(); jobDetails.Version += 1; } catch (Exception) { transaction.Rollback(); throw; } } } } catch (Exception ex) { throw new SchedulerException("The job store was unable to save job details to the database.", ex); } }