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);
namespace Dbp\Relay\CoreBundle\Cron;
use Cron\CronExpression;
use Dbp\Relay\CoreBundle\Cron\CronJobs\CachePrune;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
......@@ -21,29 +19,16 @@ final class CronCommand extends Command implements LoggerAwareInterface
// dbp:cron only for backwards compat
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();
$this->jobs = [];
$this->logger = new NullLogger();
}
public function setCache(CacheItemPoolInterface $cachePool)
{
$this->cachePool = $cachePool;
}
public function addJob(CronJobInterface $job)
{
$this->jobs[] = $job;
$this->manager = $manager;
}
protected function configure()
......@@ -51,78 +36,6 @@ final class CronCommand extends Command implements LoggerAwareInterface
$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
{
// 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
CachePrune::setPruneCommand($command);
// Now run all jobs
$dueJobs = $this->getDueJobs();
$dueJobs = $this->manager->getDueJobs();
foreach ($dueJobs as $job) {
$name = $job->getName();
$this->logger->info("cron: Running '$name'");
......
......@@ -20,10 +20,10 @@ class CronCompilerPass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
if (!$container->has(CronCommand::class)) {
if (!$container->has(CronManager::class)) {
return;
}
$definition = $container->findDefinition(CronCommand::class);
$definition = $container->findDefinition(CronManager::class);
$taggedServices = $container->findTaggedServiceIds(self::TAG);
foreach ($taggedServices as $id => $tags) {
$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);
namespace Dbp\Relay\CoreBundle\DependencyInjection;
use Dbp\Relay\CoreBundle\Auth\ProxyAuthenticator;
use Dbp\Relay\CoreBundle\Cron\CronManager;
use Dbp\Relay\CoreBundle\DB\MigrateCommand;
use Dbp\Relay\CoreBundle\Queue\TestMessage;
use Dbp\Relay\CoreBundle\Queue\Utils as QueueUtils;
......@@ -36,7 +37,7 @@ class DbpRelayCoreExtension extends ConfigurableExtension implements PrependExte
$cronCacheDef->setArguments(['core-cron', 0, '%kernel.cache_dir%/dbp/relay/core-cron']);
$cronCacheDef->addTag('cache.pool');
$definition = $container->getDefinition('Dbp\Relay\CoreBundle\Cron\CronCommand');
$definition = $container->getDefinition(CronManager::class);
$definition->addMethodCall('setCache', [$cronCacheDef]);
$definition = $container->getDefinition(MigrateCommand::class);
......
......@@ -14,6 +14,10 @@ services:
autowire: true
autoconfigure: true
Dbp\Relay\CoreBundle\Cron\CronManager:
autowire: true
autoconfigure: true
Dbp\Relay\CoreBundle\Cron\CronJobs\:
resource: '../../Cron/CronJobs'
autowire: true
......
......@@ -4,20 +4,20 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\Tests\Cron;
use Dbp\Relay\CoreBundle\Cron\CronCommand;
use Dbp\Relay\CoreBundle\Cron\CronManager;
use PHPUnit\Framework\TestCase;
class CronTest extends TestCase
{
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);
$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);
$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);
$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);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment