From 6ef7ea359bb3dd91e023a9c2df32612361936637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Barto=C5=A1?= Date: Wed, 20 Dec 2023 00:30:27 +0100 Subject: [PATCH] ManagedScheduler: locked job event --- CHANGELOG.md | 2 ++ docs/README.md | 36 ++++++++++++++++++++-- src/ManagedScheduler.php | 34 +++++++++++++++------ tests/Unit/SimpleSchedulerTest.php | 49 +++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04fc758..2d667d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `addJob()` accepts parameter `repeatAfterSeconds` - `addJob()` accepts parameter `timeZone` - `addLazyJob()` replaces `CallbackJobManager` +- `ManagedScheduler` + - `addLockedJobCallback()` - executes given callback when job is locked - `JobInfo` - `getRepeatAfterSeconds()`- returns the seconds part of expression - `getExtendedExpression()` - returns cron expression including seconds diff --git a/docs/README.md b/docs/README.md index a56d826..b60ec52 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,9 @@ Cron job scheduler - with locks, parallelism and more - [Seconds](#seconds) - [Timezones](#timezones) - [Events](#events) + - [Before job event](#before-job-event) + - [After job event](#after-job-event) + - [Locked job event](#locked-job-event) - [Handling errors](#handling-errors) - [Locks and job overlapping](#locks-and-job-overlapping) - [Parallelization and process isolation](#parallelization-and-process-isolation) @@ -211,17 +214,31 @@ Myanmar Standard Time is UTC+06:30. ## Events -Run callbacks before and after job to collect statistics, etc. +Run callbacks to collect statistics, etc. + +Check [job info and result](#job-info-and-result) for available status info + +### Before job event + +Executes before job start ```php use Orisai\Scheduler\Status\JobInfo; -use Orisai\Scheduler\Status\JobResult; $scheduler->addBeforeJobCallback( function(JobInfo $info): void { // Executes before job start }, ); +``` + +### After job event + +Executes after job finish + +```php +use Orisai\Scheduler\Status\JobInfo; +use Orisai\Scheduler\Status\JobResult; $scheduler->addAfterJobCallback( function(JobInfo $info, JobResult $result): void { @@ -230,7 +247,20 @@ $scheduler->addAfterJobCallback( ); ``` -Check [job info and result](#job-info-and-result) for available status info +### Locked job event + +Executes when [lock](#locks-and-job-overlapping) for given job is acquired by another process + +```php +use Orisai\Scheduler\Status\JobInfo; +use Orisai\Scheduler\Status\JobResult; + +$scheduler->addLockedJobCallback( + function(JobInfo $info, JobResult $result): void { + // Executes when lock for given job is acquired by another process + }, +); +``` ## Handling errors diff --git a/src/ManagedScheduler.php b/src/ManagedScheduler.php index 1742db7..5ada57c 100644 --- a/src/ManagedScheduler.php +++ b/src/ManagedScheduler.php @@ -42,11 +42,14 @@ class ManagedScheduler implements Scheduler private Clock $clock; + /** @var list */ + private array $lockedJobCallbacks = []; + /** @var list */ - private array $beforeJob = []; + private array $beforeJobCallbacks = []; /** @var list */ - private array $afterJob = []; + private array $afterJobCallbacks = []; /** * @param Closure(Throwable, JobInfo, JobResult): (void)|null $errorHandler @@ -192,18 +195,21 @@ private function runInternal($id, JobSchedule $jobSchedule, int $runSecond): arr $lock = $this->lockFactory->createLock("Orisai.Scheduler.Job/$id"); if (!$lock->acquire()) { + $result = new JobResult($expression, $info->getStart(), JobResultState::skip()); + + foreach ($this->lockedJobCallbacks as $cb) { + $cb($info, $result); + } + return [ - new JobSummary( - $info, - new JobResult($expression, $info->getStart(), JobResultState::skip()), - ), + new JobSummary($info, $result), null, ]; } $throwable = null; try { - foreach ($this->beforeJob as $cb) { + foreach ($this->beforeJobCallbacks as $cb) { $cb($info); } @@ -219,7 +225,7 @@ private function runInternal($id, JobSchedule $jobSchedule, int $runSecond): arr $throwable === null ? JobResultState::done() : JobResultState::fail(), ); - foreach ($this->afterJob as $cb) { + foreach ($this->afterJobCallbacks as $cb) { $cb($info, $result); } @@ -237,12 +243,20 @@ private function runInternal($id, JobSchedule $jobSchedule, int $runSecond): arr ]; } + /** + * @param Closure(JobInfo, JobResult): void $callback + */ + public function addLockedJobCallback(Closure $callback): void + { + $this->lockedJobCallbacks[] = $callback; + } + /** * @param Closure(JobInfo): void $callback */ public function addBeforeJobCallback(Closure $callback): void { - $this->beforeJob[] = $callback; + $this->beforeJobCallbacks[] = $callback; } /** @@ -250,7 +264,7 @@ public function addBeforeJobCallback(Closure $callback): void */ public function addAfterJobCallback(Closure $callback): void { - $this->afterJob[] = $callback; + $this->afterJobCallbacks[] = $callback; } } diff --git a/tests/Unit/SimpleSchedulerTest.php b/tests/Unit/SimpleSchedulerTest.php index fef6fef..5cc8b2c 100644 --- a/tests/Unit/SimpleSchedulerTest.php +++ b/tests/Unit/SimpleSchedulerTest.php @@ -210,7 +210,7 @@ static function () use (&$i): void { self::assertCount(3, $errors); } - public function testEvents(): void + public function testJobEvents(): void { $errorHandler = static function (): void { // Noop @@ -784,6 +784,53 @@ static function () use (&$i): void { self::assertSame(3, $i); } + public function testLockedJobEvent(): void + { + $lockFactory = new TestLockFactory(new InMemoryStore(), false); + $clock = new FrozenClock(1); + $scheduler = new SimpleScheduler(null, $lockFactory, null, $clock); + + $now = $clock->now(); + $cbs = new CallbackList(); + + $job = new CallbackJob(Closure::fromCallable([$cbs, 'job1'])); + $scheduler->addJob($job, new CronExpression('* * * * *')); + + $afterCollected = []; + $afterCb = static function (JobInfo $info, JobResult $result) use (&$afterCollected): void { + $afterCollected[] = [$info, $result]; + }; + $scheduler->addLockedJobCallback($afterCb); + + $scheduler->run(); + $scheduler->runJob(0); + self::assertCount(0, $afterCollected); + + $lock = $lockFactory->createLock('Orisai.Scheduler.Job/0'); + $lock->acquire(); + $scheduler->run(); + self::assertEquals( + [ + [ + new JobInfo( + 0, + 'Tests\Orisai\Scheduler\Doubles\CallbackList::job1()', + '* * * * *', + 0, + 0, + $now, + ), + new JobResult(new CronExpression('* * * * *'), $now, JobResultState::skip()), + ], + ], + $afterCollected, + ); + self::assertCount(1, $afterCollected); + + $scheduler->runJob(0); + self::assertCount(2, $afterCollected); + } + public function testRepeat(): void { $clock = new FrozenClock(1);