Skip to content
Snippets Groups Projects
Commit 1bdb01eb authored by Reiter, Christoph's avatar Reiter, Christoph :snake:
Browse files

cron: Move the cron job handling into its own service

splitting it out of the cron command.
Makes it a bit clearer
parent 405e0a1f
No related branches found
No related tags found
No related merge requests found
...@@ -4,9 +4,7 @@ declare(strict_types=1); ...@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Cron; namespace Dbp\Relay\CoreBundle\Cron;
use Cron\CronExpression;
use Dbp\Relay\CoreBundle\Cron\CronJobs\CachePrune; use Dbp\Relay\CoreBundle\Cron\CronJobs\CachePrune;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
...@@ -21,29 +19,16 @@ final class CronCommand extends Command implements LoggerAwareInterface ...@@ -21,29 +19,16 @@ final class CronCommand extends Command implements LoggerAwareInterface
// dbp:cron only for backwards compat // dbp:cron only for backwards compat
protected static $defaultName = 'dbp:relay:core:cron|dbp:cron'; protected static $defaultName = 'dbp:relay:core:cron|dbp:cron';
/** @var CacheItemPoolInterface */
private $cachePool;
/** /**
* @var CronJobInterface[] * @var CronManager
*/ */
private $jobs; private $manager;
public function __construct() public function __construct(CronManager $manager)
{ {
parent::__construct(); parent::__construct();
$this->jobs = [];
$this->logger = new NullLogger(); $this->logger = new NullLogger();
} $this->manager = $manager;
public function setCache(CacheItemPoolInterface $cachePool)
{
$this->cachePool = $cachePool;
}
public function addJob(CronJobInterface $job)
{
$this->jobs[] = $job;
} }
protected function configure() protected function configure()
...@@ -51,78 +36,6 @@ final class CronCommand extends Command implements LoggerAwareInterface ...@@ -51,78 +36,6 @@ final class CronCommand extends Command implements LoggerAwareInterface
$this->setDescription('Runs various tasks which need to be executed periodically'); $this->setDescription('Runs various tasks which need to be executed periodically');
} }
/**
* Returns if a job should run or not. Note that there is no feedback channel, so if you skip
* this run you will only be notified the next time the cron job should run.
*
* @param string $cronExpression A cron expression
*
* @return bool If the job should run
*/
public static function isDue(?\DateTimeInterface $previousRun, \DateTimeInterface $currentRun, string $cronExpression): bool
{
$cron = new CronExpression($cronExpression);
$previousExpectedRun = $cron->getPreviousRunDate($currentRun, 0, true);
$previousExpectedRun->setTimezone(new \DateTimeZone('UTC'));
$shouldRun = false;
// If we were scheduled to run between now and the previous run (or just before of no previous run exists)
// then we should run
if ($previousExpectedRun <= $currentRun && ($previousRun === null || $previousExpectedRun > $previousRun)) {
$shouldRun = true;
}
return $shouldRun;
}
public function getPreviousRun(\DateTimeInterface $currentTime): ?\DateTimeInterface
{
$cachePool = $this->cachePool;
// Store the previous run time in the cache and fetch from there
assert($cachePool instanceof CacheItemPoolInterface);
$item = $cachePool->getItem('cron-previous-run');
$value = $item->get();
$previousRun = null;
if ($value !== null) {
$previousRun = (new \DateTimeImmutable())->setTimezone(new \DateTimeZone('UTC'))->setTimestamp($value);
if ($previousRun > $currentTime) {
// Something is wrong, cap at the current time
$previousRun = $currentTime;
}
}
$item->set($currentTime->getTimestamp());
if ($cachePool->save($item) === false) {
throw new \RuntimeException('Saving cron timestamp failed');
}
return $previousRun;
}
/**
* @return CronJobInterface[]
*/
protected function getDueJobs(): array
{
// Get all jobs that should have been run between the last time we were called and now
$currentTime = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
// round to full seconds, so we have the same resolution for both date times
$currentTime = $currentTime->setTimestamp($currentTime->getTimestamp());
$previousRunTime = $this->getPreviousRun($currentTime);
$toRun = [];
foreach ($this->jobs as $job) {
$interval = $job->getInterval();
$name = $job->getName();
$this->logger->info("cron: Checking '$name' ($interval)");
$isDue = self::isDue($previousRunTime, $currentTime, $interval);
if ($isDue) {
$toRun[] = $job;
}
}
return $toRun;
}
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
// We need to pass the prune command to CachePrune since I didn't find an alternative // We need to pass the prune command to CachePrune since I didn't find an alternative
...@@ -132,7 +45,7 @@ final class CronCommand extends Command implements LoggerAwareInterface ...@@ -132,7 +45,7 @@ final class CronCommand extends Command implements LoggerAwareInterface
CachePrune::setPruneCommand($command); CachePrune::setPruneCommand($command);
// Now run all jobs // Now run all jobs
$dueJobs = $this->getDueJobs(); $dueJobs = $this->manager->getDueJobs();
foreach ($dueJobs as $job) { foreach ($dueJobs as $job) {
$name = $job->getName(); $name = $job->getName();
$this->logger->info("cron: Running '$name'"); $this->logger->info("cron: Running '$name'");
......
...@@ -20,10 +20,10 @@ class CronCompilerPass implements CompilerPassInterface ...@@ -20,10 +20,10 @@ class CronCompilerPass implements CompilerPassInterface
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
if (!$container->has(CronCommand::class)) { if (!$container->has(CronManager::class)) {
return; return;
} }
$definition = $container->findDefinition(CronCommand::class); $definition = $container->findDefinition(CronManager::class);
$taggedServices = $container->findTaggedServiceIds(self::TAG); $taggedServices = $container->findTaggedServiceIds(self::TAG);
foreach ($taggedServices as $id => $tags) { foreach ($taggedServices as $id => $tags) {
$definition->addMethodCall('addJob', [new Reference($id)]); $definition->addMethodCall('addJob', [new Reference($id)]);
......
<?php
declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Cron;
use Cron\CronExpression;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
final class CronManager implements LoggerAwareInterface
{
use LoggerAwareTrait;
/** @var CacheItemPoolInterface */
private $cachePool;
/**
* @var CronJobInterface[]
*/
private $jobs;
public function __construct()
{
$this->jobs = [];
$this->logger = new NullLogger();
}
public function setCache(CacheItemPoolInterface $cachePool)
{
$this->cachePool = $cachePool;
}
public function addJob(CronJobInterface $job)
{
$this->jobs[] = $job;
}
/**
* Returns if a job should run or not. Note that there is no feedback channel, so if you skip
* this run you will only be notified the next time the cron job should run.
*
* @param string $cronExpression A cron expression
*
* @return bool If the job should run
*/
public static function isDue(?\DateTimeInterface $previousRun, \DateTimeInterface $currentRun, string $cronExpression): bool
{
$cron = new CronExpression($cronExpression);
$previousExpectedRun = $cron->getPreviousRunDate($currentRun, 0, true);
$previousExpectedRun->setTimezone(new \DateTimeZone('UTC'));
$shouldRun = false;
// If we were scheduled to run between now and the previous run (or just before of no previous run exists)
// then we should run
if ($previousExpectedRun <= $currentRun && ($previousRun === null || $previousExpectedRun > $previousRun)) {
$shouldRun = true;
}
return $shouldRun;
}
public function getPreviousRun(\DateTimeInterface $currentTime): ?\DateTimeInterface
{
$cachePool = $this->cachePool;
// Store the previous run time in the cache and fetch from there
assert($cachePool instanceof CacheItemPoolInterface);
$item = $cachePool->getItem('cron-previous-run');
$value = $item->get();
$previousRun = null;
if ($value !== null) {
$previousRun = (new \DateTimeImmutable())->setTimezone(new \DateTimeZone('UTC'))->setTimestamp($value);
if ($previousRun > $currentTime) {
// Something is wrong, cap at the current time
$previousRun = $currentTime;
}
}
$item->set($currentTime->getTimestamp());
if ($cachePool->save($item) === false) {
throw new \RuntimeException('Saving cron timestamp failed');
}
return $previousRun;
}
/**
* @return CronJobInterface[]
*/
public function getDueJobs(): array
{
// Get all jobs that should have been run between the last time we were called and now
$currentTime = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
// round to full seconds, so we have the same resolution for both date times
$currentTime = $currentTime->setTimestamp($currentTime->getTimestamp());
$previousRunTime = $this->getPreviousRun($currentTime);
$toRun = [];
foreach ($this->jobs as $job) {
$interval = $job->getInterval();
$name = $job->getName();
$this->logger->info("cron: Checking '$name' ($interval)");
$isDue = self::isDue($previousRunTime, $currentTime, $interval);
if ($isDue) {
$toRun[] = $job;
}
}
return $toRun;
}
}
...@@ -5,6 +5,7 @@ declare(strict_types=1); ...@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\DependencyInjection; namespace Dbp\Relay\CoreBundle\DependencyInjection;
use Dbp\Relay\CoreBundle\Auth\ProxyAuthenticator; use Dbp\Relay\CoreBundle\Auth\ProxyAuthenticator;
use Dbp\Relay\CoreBundle\Cron\CronManager;
use Dbp\Relay\CoreBundle\DB\MigrateCommand; use Dbp\Relay\CoreBundle\DB\MigrateCommand;
use Dbp\Relay\CoreBundle\Queue\TestMessage; use Dbp\Relay\CoreBundle\Queue\TestMessage;
use Dbp\Relay\CoreBundle\Queue\Utils as QueueUtils; use Dbp\Relay\CoreBundle\Queue\Utils as QueueUtils;
...@@ -36,7 +37,7 @@ class DbpRelayCoreExtension extends ConfigurableExtension implements PrependExte ...@@ -36,7 +37,7 @@ class DbpRelayCoreExtension extends ConfigurableExtension implements PrependExte
$cronCacheDef->setArguments(['core-cron', 0, '%kernel.cache_dir%/dbp/relay/core-cron']); $cronCacheDef->setArguments(['core-cron', 0, '%kernel.cache_dir%/dbp/relay/core-cron']);
$cronCacheDef->addTag('cache.pool'); $cronCacheDef->addTag('cache.pool');
$definition = $container->getDefinition('Dbp\Relay\CoreBundle\Cron\CronCommand'); $definition = $container->getDefinition(CronManager::class);
$definition->addMethodCall('setCache', [$cronCacheDef]); $definition->addMethodCall('setCache', [$cronCacheDef]);
$definition = $container->getDefinition(MigrateCommand::class); $definition = $container->getDefinition(MigrateCommand::class);
......
...@@ -14,6 +14,10 @@ services: ...@@ -14,6 +14,10 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Dbp\Relay\CoreBundle\Cron\CronManager:
autowire: true
autoconfigure: true
Dbp\Relay\CoreBundle\Cron\CronJobs\: Dbp\Relay\CoreBundle\Cron\CronJobs\:
resource: '../../Cron/CronJobs' resource: '../../Cron/CronJobs'
autowire: true autowire: true
......
...@@ -4,20 +4,20 @@ declare(strict_types=1); ...@@ -4,20 +4,20 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Tests\Cron; namespace Dbp\Relay\CoreBundle\Tests\Cron;
use Dbp\Relay\CoreBundle\Cron\CronCommand; use Dbp\Relay\CoreBundle\Cron\CronManager;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class CronTest extends TestCase class CronTest extends TestCase
{ {
public function testCronisDue() public function testCronisDue()
{ {
$isDue = CronCommand::isDue(new \DateTimeImmutable('2021-09-07T09:36:26Z'), new \DateTimeImmutable('2021-09-07T09:36:26Z'), '* * * * *'); $isDue = CronManager::isDue(new \DateTimeImmutable('2021-09-07T09:36:26Z'), new \DateTimeImmutable('2021-09-07T09:36:26Z'), '* * * * *');
$this->assertFalse($isDue); $this->assertFalse($isDue);
$isDue = CronCommand::isDue(new \DateTimeImmutable('2021-09-07T09:35:59Z'), new \DateTimeImmutable('2021-09-07T09:36:00Z'), '* * * * *'); $isDue = CronManager::isDue(new \DateTimeImmutable('2021-09-07T09:35:59Z'), new \DateTimeImmutable('2021-09-07T09:36:00Z'), '* * * * *');
$this->assertTrue($isDue); $this->assertTrue($isDue);
$isDue = CronCommand::isDue(null, new \DateTimeImmutable('2021-09-07T09:36:00Z'), '0 0 1 1 *'); $isDue = CronManager::isDue(null, new \DateTimeImmutable('2021-09-07T09:36:00Z'), '0 0 1 1 *');
$this->assertTrue($isDue); $this->assertTrue($isDue);
$isDue = CronCommand::isDue(null, new \DateTimeImmutable('2021-09-07T09:36:00Z'), '* * * * *'); $isDue = CronManager::isDue(null, new \DateTimeImmutable('2021-09-07T09:36:00Z'), '* * * * *');
$this->assertTrue($isDue); $this->assertTrue($isDue);
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment