/**
  * @inheritdoc
  *
  * @param int $startedAt
  * @return bool
  */
 public function shouldExpire($startedAt)
 {
     if ($this->descriptor->hasMethod('shouldExpire')) {
         return $this->descriptor->shouldExpire($startedAt);
     }
     return true;
 }
 /**
  * @param string $queue
  * @return null|QueuedJobDescriptor
  */
 protected function getNextJobDescriptorWithoutMutex($queue)
 {
     $list = QueuedJobDescriptor::get()->filter('JobType', $queue)->sort('ID', 'ASC');
     $descriptor = $list->filter('JobStatus', QueuedJob::STATUS_WAIT)->first();
     if ($descriptor) {
         return $descriptor;
     }
     return $list->filter('JobStatus', QueuedJob::STATUS_NEW)->where(sprintf('"StartAfter" < \'%s\' OR "StartAfter" IS NULL', SS_DateTime::now()->getValue()))->first();
 }
 /**
  *
  * Create a new {@link GenerateGoogleSitemapJob} after each CMS write manipulation.
  *
  * @param {@inheritdoc}
  * @return mixed void | null
  */
 public function onAfterPublish(&$original)
 {
     if (!class_exists('AbstractQueuedJob')) {
         return;
     }
     // Get all "running" GenerateGoogleSitemapJob's
     $list = QueuedJobDescriptor::get()->filter(array('Implementation' => 'GenerateGoogleSitemapJob', 'JobStatus' => array(QueuedJob::STATUS_INIT, QueuedJob::STATUS_RUN)));
     $existingJob = $list ? $list->first() : null;
     if ($existingJob && $existingJob->exists()) {
         // Do nothing. There's a job for generating the sitemap already running
     } else {
         $where = '"StartAfter" > \'' . date('Y-m-d H:i:s') . '\'';
         $list = QueuedJobDescriptor::get()->where($where);
         $list = $list->filter(array('Implementation' => 'GenerateGoogleSitemapJob', 'JobStatus' => array(QueuedJob::STATUS_NEW)));
         $list = $list->sort('ID', 'ASC');
         if ($list && $list->count()) {
             // Execute immediately
             $existingJob = $list->first();
             $existingJob->StartAfter = date('Y-m-d H:i:s');
             $existingJob->write();
             return;
         }
         // If no such a job existing, create a new one for the first time, and run immediately
         // But first remove all legacy jobs which might be of the following statuses:
         /**
          * New (but Start data somehow is less than now)
          * Waiting
          * Completed
          * Paused
          * Cancelled
          * Broken
          */
         $list = QueuedJobDescriptor::get()->filter(array('Implementation' => 'GenerateGoogleSitemapJob'));
         if ($list && $list->count()) {
             $list->removeAll();
         }
         $job = new GenerateGoogleSitemapJob();
         singleton('QueuedJobService')->queueJob($job);
     }
 }
 /**
  * Prepares the given jobDescriptor for execution. Returns the job that
  * will actually be run in a state ready for executing.
  *
  * Note that this is called each time a job is picked up to be executed from the cron
  * job - meaning that jobs that are paused and restarted will have 'setup()' called on them again,
  * so your job MUST detect that and act accordingly. 
  *
  * @param QueuedJobDescriptor $jobDescriptor
  *			The Job descriptor of a job to prepare for execution
  *
  * @return QueuedJob
  */
 protected function initialiseJob(QueuedJobDescriptor $jobDescriptor)
 {
     // create the job class
     $impl = $jobDescriptor->Implementation;
     $job = new $impl();
     /* @var $job QueuedJob */
     if (!$job) {
         throw new Exception("Implementation {$impl} no longer exists");
     }
     // start the init process
     $jobDescriptor->JobStatus = QueuedJob::STATUS_INIT;
     $jobDescriptor->write();
     // make sure the data is there
     $this->copyDescriptorToJob($jobDescriptor, $job);
     // see if it needs 'setup' or 'restart' called
     if (!$jobDescriptor->StepsProcessed) {
         $job->setup();
     } else {
         $job->prepareForRestart();
     }
     // make sure the descriptor is up to date with anything changed
     $this->copyJobToDescriptor($job, $jobDescriptor);
     return $job;
 }
 /**
  * Verify that broken jobs are correctly verified for health and restarted as necessary
  *
  * Order of checkJobHealth() and getNextPendingJob() is important
  *
  * Execution of this job is broken into several "loops", each of which represents one invocation
  * of ProcessJobQueueTask
  */
 public function testJobHealthCheck()
 {
     // Create a job and add it to the queue
     $svc = $this->getService();
     $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
     $job->firstJob = true;
     $id = $svc->queueJob($job);
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     // Verify initial state is new and LastProcessedCount is not marked yet
     $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
     $this->assertEquals(0, $descriptor->StepsProcessed);
     $this->assertEquals(-1, $descriptor->LastProcessedCount);
     $this->assertEquals(0, $descriptor->ResumeCounts);
     // Loop 1 - Pick up new job and attempt to run it
     // Job health should not attempt to cleanup unstarted jobs
     $svc->checkJobHealth();
     $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
     // Ensure that this is the next job ready to go
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     $this->assertEquals($nextJob->ID, $descriptor->ID);
     $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
     $this->assertEquals(0, $descriptor->StepsProcessed);
     $this->assertEquals(-1, $descriptor->LastProcessedCount);
     $this->assertEquals(0, $descriptor->ResumeCounts);
     // Run 1 - Start the job (no work is done)
     $descriptor->JobStatus = QueuedJob::STATUS_INIT;
     $descriptor->write();
     // Assume that something bad happens at this point, the process dies during execution, and
     // the task is re-initiated somewhere down the track
     // Loop 2 - Detect broken job, and mark it for future checking.
     $svc->checkJobHealth();
     $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
     // Note that we don't immediately try to restart it until StepsProcessed = LastProcessedCount
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     $this->assertFalse($nextJob);
     // Don't run it this round please!
     $this->assertEquals(QueuedJob::STATUS_INIT, $descriptor->JobStatus);
     $this->assertEquals(0, $descriptor->StepsProcessed);
     $this->assertEquals(0, $descriptor->LastProcessedCount);
     $this->assertEquals(0, $descriptor->ResumeCounts);
     // Loop 3 - We've previously marked this job as broken, so restart it this round
     // If no more work has been done on the job at this point, assume that we are able to
     // restart it
     $svc->checkJobHealth();
     $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
     // This job is resumed and exeuction is attempted this round
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     $this->assertEquals($nextJob->ID, $descriptor->ID);
     $this->assertEquals(QueuedJob::STATUS_WAIT, $descriptor->JobStatus);
     $this->assertEquals(0, $descriptor->StepsProcessed);
     $this->assertEquals(0, $descriptor->LastProcessedCount);
     $this->assertEquals(1, $descriptor->ResumeCounts);
     // Run 2 - First restart (work is done)
     $descriptor->JobStatus = QueuedJob::STATUS_RUN;
     $descriptor->StepsProcessed++;
     // Essentially delays the next restart by 1 loop
     $descriptor->write();
     // Once again, at this point, assume the job fails and crashes
     // Loop 4 - Assuming a job has LastProcessedCount < StepsProcessed we are in the same
     // situation as step 2.
     // Because the last time the loop ran, StepsProcessed was incremented,
     // this indicates that it's likely that another task could be working on this job, so
     // don't run this.
     $svc->checkJobHealth();
     $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     $this->assertFalse($nextJob);
     // Don't run jobs we aren't sure should be restarted
     $this->assertEquals(QueuedJob::STATUS_RUN, $descriptor->JobStatus);
     $this->assertEquals(1, $descriptor->StepsProcessed);
     $this->assertEquals(1, $descriptor->LastProcessedCount);
     $this->assertEquals(1, $descriptor->ResumeCounts);
     // Loop 5 - Job is again found to not have been restarted since last iteration, so perform second
     // restart. The job should be attempted to run this loop
     $svc->checkJobHealth();
     $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
     // This job is resumed and exeuction is attempted this round
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     $this->assertEquals($nextJob->ID, $descriptor->ID);
     $this->assertEquals(QueuedJob::STATUS_WAIT, $descriptor->JobStatus);
     $this->assertEquals(1, $descriptor->StepsProcessed);
     $this->assertEquals(1, $descriptor->LastProcessedCount);
     $this->assertEquals(2, $descriptor->ResumeCounts);
     // Run 3 - Second and last restart (no work is done)
     $descriptor->JobStatus = QueuedJob::STATUS_RUN;
     $descriptor->write();
     // Loop 6 - As no progress has been made since loop 3, we can mark this as dead
     $svc->checkJobHealth();
     $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
     // Since no StepsProcessed has been done, don't wait another loop to mark this as dead
     $descriptor = QueuedJobDescriptor::get()->byID($id);
     $this->assertEquals(QueuedJob::STATUS_PAUSED, $descriptor->JobStatus);
     $this->assertEmpty($nextJob);
 }
 /**
  * If the queued jobs module is installed, queue up the first job for 9am tomorrow morning
  * (by default).
  */
 public function requireDefaultRecords()
 {
     if (class_exists("ContentReviewNotificationJob")) {
         // Ensure there is not already a job queued
         if (QueuedJobDescriptor::get()->filter("Implementation", "ContentReviewNotificationJob")->first()) {
             return;
         }
         $nextRun = new ContentReviewNotificationJob();
         $runHour = Config::inst()->get("ContentReviewNotificationJob", "first_run_hour");
         $firstRunTime = date("Y-m-d H:i:s", mktime($runHour, 0, 0, date("m"), date("d") + 1, date("y")));
         singleton("QueuedJobService")->queueJob($nextRun, $firstRunTime);
         DB::alteration_message(sprintf("Added ContentReviewNotificationJob to run at %s", $firstRunTime));
     }
 }
 public function startJobOnQueue(QueuedJobDescriptor $job)
 {
     $job->activateOnQueue();
 }