diff --git a/README.md b/README.md index e9b56c43..3ab5dbb9 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ set the notification email address in your config as below: Email: queued_job_admin_email: support@mycompany.com -## Memory limit configuration +## Performance configuration By default this task will run until either 128mb or the limit specified by php_ini('memory_limit') is reached. @@ -134,6 +134,16 @@ You can adjust this with the below config change memory_limit: 256m +You can also enforce a time limit for each queue, after which the task will attempt a restart to release all +resources. By default this is disabled, so you must specify this in your project as below: + + + :::yaml + # Force limit to 10 minutes + QueuedJobsService: + time_limit: 600 + + ## Indexes ALTER TABLE `QueuedJobDescriptor` ADD INDEX ( `JobStatus` , `JobType` ) diff --git a/code/services/QueuedJobService.php b/code/services/QueuedJobService.php index e9be6b65..a6399a4f 100644 --- a/code/services/QueuedJobService.php +++ b/code/services/QueuedJobService.php @@ -37,6 +37,23 @@ class QueuedJobService { * @config */ private static $memory_limit = 134217728; + + /** + * Optional time limit (in seconds) to run the service before restarting to release resources. + * + * Defaults to no limit. + * + * @var int + * @config + */ + private static $time_limit = 0; + + /** + * Timestamp (in seconds) when the queue was started + * + * @var int + */ + protected $startedAt = 0; /** * Should "immediate" jobs be managed using the shutdown function? @@ -529,6 +546,16 @@ public function runJob($jobId) { $jobDescriptor->JobStatus = QueuedJob::STATUS_WAIT; $broken = true; } + + // Also check if we are running too long + if($this->hasPassedTimeLimit()) { + $job->addMessage(_t( + 'QueuedJobs.TIME_LIMIT', + 'Queue has passed time limit and will restart before continuing' + )); + $jobDescriptor->JobStatus = QueuedJob::STATUS_WAIT; + $broken = true; + } } if ($jobDescriptor) { @@ -579,6 +606,35 @@ public function runJob($jobId) { return !$broken; } + /** + * Start timer + */ + protected function markStarted() { + if($this->startedAt) { + $this->startedAt = SS_Datetime::now()->Format('U'); + } + } + + /** + * Is execution time too long? + * + * @return bool True if the script has passed the configured time_limit + */ + protected function hasPassedTimeLimit() { + // Ensure a limit exists + $limit = Config::inst()->get(__CLASS__, 'time_limit'); + if(!$limit) { + return false; + } + + // Ensure started date is set + $this->markStarted(); + + // Check duration + $now = SS_Datetime::now()->Format('U'); + return $now > $this->startedAt + $limit; + } + /** * Is memory usage too high? * @@ -700,10 +756,13 @@ public function getJobListFilter($type = null, $includeUpUntil = 0) { /** * Process all jobs from a given queue * - * @param string $name - * The job queue to completely process + * @param string $name The job queue to completely process */ public function processJobQueue($name) { + // Start timer to measure lifetime + $this->markStarted(); + + // Begin main loop do { if (class_exists('Subsite')) { // clear subsite back to default to prevent any subsite changes from leaking to