Beispiel #1
0
        public void SchedulerHandlesJobWatcherObjectDisposedExceptionByStopping(bool throwSecondExceptionInDispose)
        {
            // The mock job watcher will throw ObjectDisposedException on the first
            // call to GetNextJobToProcess.  This should cause the scheduler's job watching
            // thread to begin shutting down and eventually call the job watcher's Dispose.
            // That will wake us up from sleep so we can verify that the scheduler has stopped
            // on its own.
            // We also check what happens if a second exception occurs in Dispose during shutdown.
            IJobWatcher mockJobWatcher = Mocks.CreateMock <IJobWatcher>();

            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Throw(new ObjectDisposedException("Uh oh!"));

            mockJobWatcher.Dispose();
            LastCall.Do((DisposeDelegate) delegate
            {
                Wake();

                if (throwSecondExceptionInDispose)
                {
                    throw new Exception("Yikes!  We're trying to shut down here!");
                }
            });

            Expect.Call(mockJobStore.CreateJobWatcher(scheduler.Guid)).Return(mockJobWatcher);

            Mocks.ReplayAll();

            scheduler.Start();
            WaitUntilWake();

            Assert.IsFalse(scheduler.IsRunning, "The scheduler should have stopped itself automatically.");
        }
Beispiel #2
0
        public void SchedulerHandlesJobWatcherExceptionByInsertingAnErrorRecoveryDelay()
        {
            // The mock job watcher will throw on the first GetNextJobToProcess.
            // On the second one it will cause us to wake up.
            // There should be a total delay at least as big as the error recovery delay.
            IJobWatcher mockJobWatcher = Mocks.CreateMock <IJobWatcher>();

            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Throw(new Exception("Uh oh!"));
            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Do((GetNextJobToProcessDelegate) delegate
            {
                Wake();
                return(null);
            });

            mockJobWatcher.Dispose();
            LastCall.Repeat.AtLeastOnce();

            Expect.Call(mockJobStore.CreateJobWatcher(scheduler.Guid)).Return(mockJobWatcher);

            Mocks.ReplayAll();

            scheduler.ErrorRecoveryDelayInSeconds = 2;

            Stopwatch stopWatch = Stopwatch.StartNew();

            RunSchedulerUntilWake();
            Assert.That(stopWatch.ElapsedMilliseconds, NUnit.Framework.Is.GreaterThanOrEqualTo(2000).And.LessThanOrEqualTo(10000));
        }
        public void JobWatcher_UnblocksWhenPendingJobAdded()
        {
            IJobWatcher watcher = jobStore.CreateJobWatcher(SchedulerGuid);

            ThreadPool.QueueUserWorkItem(delegate
            {
                Thread.Sleep(2000);
                CreatePendingJob("pending", DateTime.UtcNow);
            });

            // Wait for job to become ready.
            JobDetails triggered = watcher.GetNextJobToProcess();

            // Job should come back pending.
            Assert.AreEqual("pending", triggered.JobSpec.Name);
            Assert.AreEqual(JobState.Pending, triggered.JobState);

            watcher.Dispose();
        }
Beispiel #4
0
        public void SchedulerHandlesJobStoreConcurrentModificationExceptionByIgnoringTheJob()
        {
            // The mock job watcher will return job detail on the first GetNextJobToProcess
            // but the mock job store will throw ConcurrentModificationException during SaveJobDetails.
            // On the second call to GetNextJobToProcess the mock job watcher will cause us to wake up.
            // There should be no noticeable delay, particularly not one as big as the error recovery delay.
            JobDetails jobDetails = new JobDetails(dummyJobSpec, DateTime.UtcNow);

            jobDetails.JobState = JobState.Pending;

            Expect.Call(mockTrigger.Schedule(TriggerScheduleCondition.Latch, DateTime.UtcNow, null))
            .Constraints(Rhino.Mocks.Constraints.Is.Equal(TriggerScheduleCondition.Latch), Rhino.Mocks.Constraints.Is.Anything(), Rhino.Mocks.Constraints.Is.Null())
            .Return(TriggerScheduleAction.Stop);
            Expect.Call(mockTrigger.NextFireTimeUtc).Return(new DateTime(1970, 1, 5, 0, 0, 0, DateTimeKind.Utc));
            Expect.Call(mockTrigger.NextMisfireThreshold).Return(new TimeSpan(0, 1, 0));

            mockJobStore.SaveJobDetails(jobDetails);
            LastCall.Throw(new ConcurrentModificationException("Another scheduler grabbed it."));

            IJobWatcher mockJobWatcher = Mocks.CreateMock <IJobWatcher>();

            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Return(jobDetails);
            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Do((GetNextJobToProcessDelegate) delegate
            {
                Wake();
                return(null);
            });

            mockJobWatcher.Dispose();
            LastCall.Repeat.AtLeastOnce();

            Expect.Call(mockJobStore.CreateJobWatcher(scheduler.Guid)).Return(mockJobWatcher);

            Mocks.ReplayAll();

            scheduler.ErrorRecoveryDelayInSeconds = 2;

            Stopwatch stopWatch = Stopwatch.StartNew();

            RunSchedulerUntilWake();
            Assert.Less(stopWatch.ElapsedMilliseconds, 2000);
        }
Beispiel #5
0
        public void SchedulerHandlesJobStoreExceptionByInsertingAnErrorRecoveryDelay()
        {
            // The mock job watcher will return job detail on the first GetNextJobToProcess
            // but the mock job store will throw Expection during SaveJobDetails.
            // On the second call to GetNextJobToProcess the mock job watcher will cause us to wake up.
            // There should be a total delay at least as big as the error recovery delay.
            JobDetails jobDetails = new JobDetails(dummyJobSpec, DateTime.UtcNow);

            jobDetails.JobState = JobState.Pending;

            Expect.Call(mockTrigger.Schedule(TriggerScheduleCondition.Latch, DateTime.UtcNow, null))
            .Constraints(Rhino.Mocks.Constraints.Is.Equal(TriggerScheduleCondition.Latch), Rhino.Mocks.Constraints.Is.Anything(), Rhino.Mocks.Constraints.Is.Null())
            .Return(TriggerScheduleAction.Stop);
            Expect.Call(mockTrigger.NextFireTimeUtc).Return(new DateTime(1970, 1, 5, 0, 0, 0, DateTimeKind.Utc));
            Expect.Call(mockTrigger.NextMisfireThreshold).Return(new TimeSpan(0, 1, 0));

            mockJobStore.SaveJobDetails(jobDetails);
            LastCall.Throw(new Exception("Uh oh!"));

            IJobWatcher mockJobWatcher = Mocks.CreateMock <IJobWatcher>();

            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Return(jobDetails);
            Expect.Call(mockJobWatcher.GetNextJobToProcess()).Do((GetNextJobToProcessDelegate) delegate
            {
                Wake();
                return(null);
            });

            mockJobWatcher.Dispose();
            LastCall.Repeat.AtLeastOnce();

            Expect.Call(mockJobStore.CreateJobWatcher(scheduler.Guid)).Return(mockJobWatcher);

            Mocks.ReplayAll();

            scheduler.ErrorRecoveryDelayInSeconds = 2;

            Stopwatch stopWatch = Stopwatch.StartNew();

            RunSchedulerUntilWake();
            Assert.That(stopWatch.ElapsedMilliseconds, NUnit.Framework.Is.GreaterThanOrEqualTo(2000).And.LessThanOrEqualTo(10000));
        }
        public void JobWatcher_IgnoresExceptionIfDbConnectionFailureOccurs()
        {
            Mocks.ReplayAll();

            SetBrokenConnectionMocking(JobStore, true);

            IJobWatcher jobWatcher = JobStore.CreateJobWatcher(SchedulerGuid);

            // This could throw an exception but instead we catch and log it then keep going
            // until we are disposed.
            Stopwatch stopwatch = Stopwatch.StartNew();

            ThreadPool.QueueUserWorkItem(delegate
            {
                Thread.Sleep(2000);
                jobWatcher.Dispose();
            });

            Assert.IsNull(jobWatcher.GetNextJobToProcess());
            Assert.That(stopwatch.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(2000).And.LessThanOrEqualTo(4000), "Check that the thread was blocked the whole time.");
        }
        public void JobWatcher_UnblocksWhenScheduledJobBecomesTriggered()
        {
            IJobWatcher watcher = jobStore.CreateJobWatcher(SchedulerGuid);

            DateTime fireTime = DateTime.UtcNow.AddSeconds(3);

            CreateScheduledJob("scheduled", fireTime);

            // Wait for job to become ready.
            Assert.Less(DateTime.UtcNow, fireTime);
            JobDetails triggered = watcher.GetNextJobToProcess();

            Assert.GreaterOrEqual(DateTime.UtcNow, fireTime.Subtract(new TimeSpan(0, 0, 0, 0, 500)));
            // allow a little imprecision

            // Job should come back triggered.
            Assert.AreEqual("scheduled", triggered.JobSpec.Name);
            Assert.AreEqual(JobState.Triggered, triggered.JobState);

            watcher.Dispose();
        }
        private void WatchTriggeredJobs(object arg)
        {
            IJobWatcher jobWatcher = (IJobWatcher)arg;

            try
            {
                for (;;)
                {
                    JobDetails jobDetails;
                    try
                    {
                        jobDetails = jobWatcher.GetNextJobToProcess();
                        if (jobDetails == null)
                        {
                            return;                             // watcher was disposed
                        }
                    }
                    catch (ObjectDisposedException ex)
                    {
                        // watcher was disposed not quite so nicely
                        logger.FatalFormat(ex, "The job store was disposed prematurely.  Stopping the scheduler.");
                        return;
                    }
                    catch (Exception ex)
                    {
                        logger.ErrorFormat(ex,
                                           "The scheduled job watcher threw an exception.  Pausing for '{0}' seconds before resuming job processing.",
                                           errorRecoveryDelayInSeconds);
                        Thread.Sleep(errorRecoveryDelayInSeconds * 1000);
                        continue;
                    }

                    try
                    {
                        ScheduleJob(jobDetails);
                    }
                    catch (Exception ex)
                    {
                        logger.ErrorFormat(ex,
                                           "The scheduled job processor threw an exception.  Pausing for '{0}' seconds before resuming job processing.",
                                           errorRecoveryDelayInSeconds);
                        Thread.Sleep(errorRecoveryDelayInSeconds * 1000);
                    }
                }
            }
            finally
            {
                // Put the scheduler back in the stopped state.
                lock (syncRoot)
                {
                    if (currentJobWatcher == jobWatcher)
                    {
                        currentJobWatcher       = null;
                        currentJobWatcherThread = null;
                    }
                }

                try
                {
                    jobWatcher.Dispose();
                }
                catch (Exception ex)
                {
                    logger.WarnFormat(ex, "The job watcher threw an exception when disposed.  The exception will be ignored.");
                }
            }
        }
        public void JobWatcher_YieldsJobsInExpectedSequence()
        {
            IJobWatcher watcher = jobStore.CreateJobWatcher(SchedulerGuid);

            JobDetails orphaned  = CreateOrphanedJob("orphaned", new DateTime(1970, 1, 3));
            JobDetails pending   = CreatePendingJob("pending", new DateTime(1970, 1, 2));
            JobDetails triggered = CreateTriggeredJob("triggered", new DateTime(1970, 1, 6));
            JobDetails completed = CreateCompletedJob("completed", new DateTime(1970, 1, 1));
            JobDetails scheduled = CreateScheduledJob("scheduled", new DateTime(1970, 1, 4));

            // Ensure we tolerate a few odd cases where data may not be available like it should.
            JobDetails scheduled2 = CreateScheduledJob("scheduled2", new DateTime(1970, 1, 2));

            scheduled2.NextTriggerFireTimeUtc = null;
            jobStore.SaveJobDetails(scheduled2);

            JobDetails completed2 = CreateCompletedJob("completed2", new DateTime(1970, 1, 1));

            completed2.LastJobExecutionDetails = null;
            jobStore.SaveJobDetails(completed2);

            JobDetails orphaned2 = CreateOrphanedJob("orphaned2", new DateTime(1970, 1, 3));

            orphaned2.LastJobExecutionDetails.EndTimeUtc = null;
            jobStore.SaveJobDetails(orphaned2);

            // Populate a table of expected jobs.
            List <JobDetails> expectedJobs = new List <JobDetails>(new JobDetails[]
            {
                orphaned, pending, triggered, completed, scheduled, scheduled2
                , completed2, orphaned2
            });

            // Add in some extra jobs in other states that will not be returned.
            CreateRunningJob("running1");
            CreateStoppedJob("stopped1");
            CreateScheduledJob("scheduled-in-the-future", DateTime.MaxValue);

            // Ensure expected jobs are retrieved.
            while (expectedJobs.Count != 0)
            {
                JobDetails actualJob   = watcher.GetNextJobToProcess();
                JobDetails expectedJob =
                    expectedJobs.Find(delegate(JobDetails candidate) { return(candidate.JobSpec.Name == actualJob.JobSpec.Name); });
                Assert.IsNotNull(expectedJob, "Did expect job {0}", actualJob.JobSpec.Name);

                // All expected scheduled jobs will have been triggered.
                if (expectedJob.JobState == JobState.Scheduled)
                {
                    expectedJob.JobState = JobState.Triggered;
                }

                JobAssert.AreEqual(expectedJob, actualJob);

                if (expectedJobs.Count == 1)
                {
                    // Ensure same job is returned a second time until its status is changed.
                    // We wait for Count == 1 because that's the easiest case for which to verify
                    // this behavior.
                    JobDetails actualJob2 = watcher.GetNextJobToProcess();
                    JobAssert.AreEqual(expectedJob, actualJob2);
                }

                // Change the status to progress.
                actualJob.JobState = JobState.Stopped;
                jobStore.SaveJobDetails(actualJob);

                expectedJobs.Remove(expectedJob);
            }

            // Ensure next request blocks but is released by the call to dispose.
            ThreadPool.QueueUserWorkItem(delegate
            {
                Thread.Sleep(2);
                watcher.Dispose();
            });

            // This call blocks until the dispose runs.
            Assert.IsNull(watcher.GetNextJobToProcess());
        }