From 8d52dd0ba0bdb8a6199de0d59c4f71bddd047792 Mon Sep 17 00:00:00 2001 From: Arif Hoque Date: Sat, 13 Dec 2025 11:56:20 +0600 Subject: [PATCH] timeout option added for per job execution --- src/Attributes/Queueable.php | 4 ++- src/Contracts/JobInterface.php | 7 ++++ src/Exceptions/JobTimeoutException.php | 8 +++++ src/InteractsWithQueueableAttributes.php | 4 +++ src/Job.php | 17 +++++++++ src/QueueWorker.php | 46 ++++++++++++++++++++++-- 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/Exceptions/JobTimeoutException.php diff --git a/src/Attributes/Queueable.php b/src/Attributes/Queueable.php index 056d6be..505a6a3 100644 --- a/src/Attributes/Queueable.php +++ b/src/Attributes/Queueable.php @@ -12,11 +12,13 @@ class Queueable * @param int|null $retryAfter * @param int|null $delayFor * @param string|null $onQueue + * @param int|null $timeout */ public function __construct( public ?int $tries = null, public ?int $retryAfter = null, public ?int $delayFor = null, - public ?string $onQueue = null + public ?string $onQueue = null, + public ?int $timeout = null ) {} } diff --git a/src/Contracts/JobInterface.php b/src/Contracts/JobInterface.php index be89ace..4715419 100644 --- a/src/Contracts/JobInterface.php +++ b/src/Contracts/JobInterface.php @@ -61,4 +61,11 @@ public function getJobId(): ?string; * @return void */ public function setJobId(string $id): void; + + /** + * Get the timeout in seconds. + * + * @return int|null + */ + public function getTimeout(): ?int; } diff --git a/src/Exceptions/JobTimeoutException.php b/src/Exceptions/JobTimeoutException.php new file mode 100644 index 0000000..e4bfd3e --- /dev/null +++ b/src/Exceptions/JobTimeoutException.php @@ -0,0 +1,8 @@ +onQueue !== null) { $this->queueName = $attribute->onQueue; } + + if ($attribute->timeout !== null) { + $this->timeout = $attribute->timeout; + } } } } \ No newline at end of file diff --git a/src/Job.php b/src/Job.php index a5b7f23..06431da 100644 --- a/src/Job.php +++ b/src/Job.php @@ -52,6 +52,13 @@ abstract class Job implements JobInterface */ public $attempts = 0; + /** + * Maximum execution time in seconds. + * + * @var int|null + */ + public $timeout = null; + /** * Get the number of times the job may be attempted. * @@ -150,6 +157,16 @@ public function delayFor(int $delay): self return $this; } + /** + * Get the timeout in seconds. + * + * @return int|null + */ + public function getTimeout(): ?int + { + return $this->timeout; + } + /** * Dispatch the job to the queue. * diff --git a/src/QueueWorker.php b/src/QueueWorker.php index 7e54b84..b58d92b 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -3,6 +3,7 @@ namespace Doppar\Queue; use Doppar\Queue\Models\QueueJob; +use Doppar\Queue\Exceptions\JobTimeoutException; use Doppar\Queue\Contracts\JobInterface; class QueueWorker @@ -187,8 +188,8 @@ protected function processJob(QueueJob $queueJob): void ($this->onJobProcessing)($job); } - // Execute the job - $this->executeJob($job); + // Execute the job with timeout + $this->executeJobWithTimeout($job); // Delete the job from queue if successful $this->manager->delete($queueJob); @@ -202,6 +203,47 @@ protected function processJob(QueueJob $queueJob): void } } + /** + * Execute a job with timeout protection. + * + * @param JobInterface $job + * @return void + * @throws \Throwable + */ + protected function executeJobWithTimeout(JobInterface $job): void + { + $timeout = $job->getTimeout(); + + if ($timeout === null || !extension_loaded('pcntl')) { + // No timeout or pcntl not available, execute normally + $this->executeJob($job); + return; + } + + // Set up timeout handler + $timedOut = false; + + pcntl_signal(SIGALRM, function () use (&$timedOut) { + $timedOut = true; + }); + + pcntl_alarm($timeout); + + try { + $this->executeJob($job); + pcntl_alarm(0); + } catch (\Throwable $e) { + pcntl_alarm(0); + throw $e; + } + + if ($timedOut) { + throw new JobTimeoutException( + "Job exceeded maximum execution time of {$timeout} seconds" + ); + } + } + /** * Execute a job. *