/** * @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(); }