From cc35d395c6607f958c2a0de39650a709aa682de6 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 6 Sep 2024 15:04:53 +1200 Subject: [PATCH] API Update API to reflect changes to CLI interaction --- README.md | 4 +- _config/cli.yml | 6 + _config/taskrunner.yml | 7 +- docs/en/defining-jobs.md | 2 +- docs/en/immediate-jobs.md | 10 +- docs/en/index.md | 16 +-- src/Cli/ProcessJobQueueChildCommand.php | 38 ++++++ src/Controllers/QueuedTaskRunner.php | 42 ++---- src/Jobs/RunBuildTaskJob.php | 10 +- src/Services/AbstractQueuedJob.php | 26 ++++ src/Tasks/CheckJobHealthTask.php | 61 +++++---- src/Tasks/CreateQueuedJobTask.php | 78 ++++++++--- src/Tasks/DeleteAllJobsTask.php | 50 ++++---- src/Tasks/Engines/DoormanRunner.php | 8 +- src/Tasks/ProcessJobQueueChildTask.php | 43 ------- src/Tasks/ProcessJobQueueTask.php | 121 +++++++----------- src/Tasks/PublishItemsTask.php | 33 +++-- .../Controllers/QueuedTaskRunner.ss | 14 +- 18 files changed, 314 insertions(+), 255 deletions(-) create mode 100644 _config/cli.yml create mode 100644 src/Cli/ProcessJobQueueChildCommand.php delete mode 100644 src/Tasks/ProcessJobQueueChildTask.php diff --git a/README.md b/README.md index b295bd0f..01730fb2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ composer require symbiote/silverstripe-queuedjobs Setup a cron job: ```sh -*/1 * * * * /path/to/silverstripe/vendor/bin/sake dev/tasks/ProcessJobQueueTask +*/1 * * * * /path/to/silverstripe/vendor/bin/sake tasks:ProcessJobQueueTask ``` * To schedule a job to be executed at some point in the future, pass a date through with the call to queueJob @@ -252,7 +252,7 @@ In addition to the config setting there is a task that can be used with a cron t detected: ``` -*/5 * * * * /path/to/silverstripe/vendor/bin/sake dev/tasks/CheckJobHealthTask +*/5 * * * * /path/to/silverstripe/vendor/bin/sake tasks:CheckJobHealthTask ``` ## Special job variables diff --git a/_config/cli.yml b/_config/cli.yml new file mode 100644 index 00000000..932891f0 --- /dev/null +++ b/_config/cli.yml @@ -0,0 +1,6 @@ +--- +Name: queuedjobs-cli +--- +SilverStripe\Cli\Sake: + commands: + - 'Symbiote\QueuedJobs\Cli\ProcessJobQueueChildCommand' diff --git a/_config/taskrunner.yml b/_config/taskrunner.yml index 7cdfa26b..c4469503 100644 --- a/_config/taskrunner.yml +++ b/_config/taskrunner.yml @@ -4,8 +4,7 @@ After: - DevelopmentAdmin --- SilverStripe\Dev\DevelopmentAdmin: - registered_controllers: + controllers: tasks: - controller: Symbiote\QueuedJobs\Controllers\QueuedTaskRunner - links: - tasks: 'See a list of build tasks to run (QueuedJobs version)' + class: Symbiote\QueuedJobs\Controllers\QueuedTaskRunner + description: 'See a list of build tasks to run (QueuedJobs version)' diff --git a/docs/en/defining-jobs.md b/docs/en/defining-jobs.md index 87d276f5..32acff11 100644 --- a/docs/en/defining-jobs.md +++ b/docs/en/defining-jobs.md @@ -3,7 +3,7 @@ The best way to learn about defining your own jobs is by checking the examples * `PublishItemsJob` - A job used to publish all the children of a particular node. To create this job, run the PublishItemsTask passing in the parent as a request var (eg ?parent=1) -* `GenerateGoogleSitemapJob` - A job used to create a google sitemap. If the googlesitemaps module is installed it will include priority settings as defined there, otherwise just produces a generic structure. To create an initial instance of this job, call dev/tasks/CreateDummyJob?name=GenerateGoogleSitemapJob. This will create the initial job and queue it; once the job has been run once, it will automatically schedule itself to be run again 24 hours later. +* `GenerateGoogleSitemapJob` - A job used to create a google sitemap. If the googlesitemaps module is installed it will include priority settings as defined there, otherwise just produces a generic structure. To create an initial instance of this job, run `sake tasks:CreateDummyJob --name=Symbiote\QueuedJobs\Jobs\GenerateGoogleSitemapJob`. This will create the initial job and queue it; once the job has been run once, it will automatically schedule itself to be run again 24 hours later. * `CreateDummyJob` - A very simple skeleton job. ## API Overview diff --git a/docs/en/immediate-jobs.md b/docs/en/immediate-jobs.md index 8fb249bc..7921ea3b 100644 --- a/docs/en/immediate-jobs.md +++ b/docs/en/immediate-jobs.md @@ -31,12 +31,12 @@ inotifywait and then call the task when a new job is ready to run. SILVERSTRIPE_ROOT=/path/to/silverstripe SILVERSTRIPE_CACHE=/path/to/silverstripe-cache -inotifywait --monitor --event attrib --format "php $SILVERSTRIPE_ROOT/vendor/bin/sake dev/tasks/ProcessJobQueueTask job=%f" $SILVERSTRIPE_CACHE/queuedjobs | sh +inotifywait --monitor --event attrib --format "php $SILVERSTRIPE_ROOT/vendor/bin/sake tasks:ProcessJobQueueTask --job=%f" $SILVERSTRIPE_CACHE/queuedjobs | sh ``` You can also turn this into an `init.d` service: -``` +```sh #!/bin/bash # # /etc/init.d/queue_processor @@ -73,7 +73,7 @@ start() { for PATH in ${SILVERSTRIPE_ROOT[@]}; do INOTIFY_OPTS="--monitor --event attrib -q" - INOTIFY_ARGS="--format 'php ${PATH}/vendor/bin/sake dev/tasks/ProcessJobQueueTask job=%f' ${PATH}/silverstripe-cache/queuedjobs | /bin/sh" + INOTIFY_ARGS="--format 'php ${PATH}/vendor/bin/sake tasks:ProcessJobQueueTask job=%f' ${PATH}/silverstripe-cache/queuedjobs | /bin/sh" daemon --user apache /usr/bin/inotifywait ${INOTIFY_OPTS} ${INOTIFY_ARGS} & /bin/touch /var/lock/subsys/queue_processor done @@ -113,7 +113,7 @@ Similar concept to `inotifywait`, but with the `lsyncd` system utility. The following is an example config `/etc/lsyncd.conf` -``` +```sh -- Queue Processor configuration, typically placed in /etc/lsyncd.conf settings = { @@ -124,7 +124,7 @@ settings = { -- Define the command and path for the each system being monitored here, where webuser is the user your webserver -- runs as -runcmd = "/sbin/runuser webuser -c \"/usr/bin/php /var/www/sitepath/framework/cli-script.php dev/tasks/ProcessJobQueueTask job=\"$1\" /var/www/sitepath/framework/silverstripe-cache/queuedjobs\"" +runcmd = "/sbin/runuser webuser -c \"/usr/bin/php /var/www/sitepath/vendor/bin/sake tasks:ProcessJobQueueTask job=\"$1\" /var/www/sitepath/framework/silverstripe-cache/queuedjobs\"" site_processor = { onCreate = function(event) diff --git a/docs/en/index.md b/docs/en/index.md index e4218f86..cdcf21d8 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -15,14 +15,14 @@ Install the cronjob needed to manage all the jobs within the system. It is best to have this execute as the same user as your webserver - this prevents any problems with file permissions. -``` -*/1 * * * * /path/to/silverstripe/vendor/bin/sake dev/tasks/ProcessJobQueueTask +```sh +*/1 * * * * /path/to/silverstripe/vendor/bin/sake tasks:ProcessJobQueueTask ``` To test things are working, run the following command to create a dummy task -``` -vendor/bin/sake dev/tasks/CreateQueuedJobTask +```sh +vendor/bin/sake tasks:CreateQueuedJobTask ``` Every job is tracked as a database record, through `QueuedJobDescriptor` objects. @@ -31,8 +31,8 @@ Open up `/admin/queuedjobs` in a browser. You should see the new job with `Status=New`. Now either wait for your cron to execute, or trigger execution manually. -``` -vendor/bin/sake dev/tasks/ProcessJobQueueTask +```sh +vendor/bin/sake tasks:ProcessJobQueueTask ``` The job should now be marked with `Status=Completed`. @@ -116,8 +116,8 @@ Symbiote\QueuedJobs\Jobs\CleanupJob: If your code is to make use of the 'long' jobs, ie that could take days to process, also install another task that processes this queue. Its time of execution can be left a little longer. -``` -*/15 * * * * /path/to/silverstripe/vendor/bin/sake dev/tasks/ProcessJobQueueTask queue=large +```sh +*/15 * * * * /path/to/silverstripe/vendor/bin/sake tasks:ProcessJobQueueTask queue=large ``` From your code, add a new job for execution. diff --git a/src/Cli/ProcessJobQueueChildCommand.php b/src/Cli/ProcessJobQueueChildCommand.php new file mode 100644 index 00000000..f75b103e --- /dev/null +++ b/src/Cli/ProcessJobQueueChildCommand.php @@ -0,0 +1,38 @@ +getArgument('base64-task'))); + if ($task) { + $this->getService()->runJob($task->getDescriptor()->ID); + } + return Command::SUCCESS; + } + + /** + * Returns an instance of the QueuedJobService. + * + * @return QueuedJobService + */ + protected function getService() + { + return QueuedJobService::singleton(); + } + + protected function configure() + { + $this->addArgument('base64-task', InputArgument::REQUIRED); + } +} diff --git a/src/Controllers/QueuedTaskRunner.php b/src/Controllers/QueuedTaskRunner.php index 57996535..0660dd7a 100644 --- a/src/Controllers/QueuedTaskRunner.php +++ b/src/Controllers/QueuedTaskRunner.php @@ -19,7 +19,6 @@ use Symbiote\QueuedJobs\Services\QueuedJobService; use Symbiote\QueuedJobs\Tasks\CreateQueuedJobTask; use Symbiote\QueuedJobs\Tasks\DeleteAllJobsTask; -use Symbiote\QueuedJobs\Tasks\ProcessJobQueueChildTask; use Symbiote\QueuedJobs\Tasks\ProcessJobQueueTask; /** @@ -29,55 +28,34 @@ */ class QueuedTaskRunner extends TaskRunner { - /** - * @var array - */ - private static $url_handlers = [ + private static array $url_handlers = [ 'queue/$TaskName' => 'queueTask', ]; - /** - * @var array - */ - private static $allowed_actions = [ + private static array $allowed_actions = [ 'queueTask', ]; - /** - * @var array - */ - private static $css = [ + private static array $css = [ 'symbiote/silverstripe-queuedjobs:client/styles/task-runner.css', ]; /** - * Tasks on this list will be available to be run only via browser - * - * @config - * @var array + * Tasks on this list will not be available to run via the jobs queue */ - private static $task_blacklist = [ + private static array $task_blacklist = [ ProcessJobQueueTask::class, - ProcessJobQueueChildTask::class, CreateQueuedJobTask::class, DeleteAllJobsTask::class, ]; /** * Tasks on this list will be available to be run only via jobs queue - * - * @config - * @var array */ - private static $queued_only_tasks = []; + private static array $queued_only_tasks = []; public function index() { - if (Director::is_cli()) { - // CLI mode - revert to default behaviour - return parent::index(); - } - $baseUrl = Director::absoluteBaseURL(); $tasks = $this->getTasks(); @@ -108,6 +86,8 @@ public function index() 'Title' => $task['title'], 'Description' => $task['description'], 'Type' => 'universal', + 'Parameters' => $task['parameters'], + 'Help' => $task['help'], ])); } @@ -118,18 +98,20 @@ public function index() 'Title' => $task['title'], 'Description' => $task['description'], 'Type' => 'immediate', + 'Parameters' => $task['parameters'], + 'Help' => $task['help'], ])); } // Queue only tasks - $queueOnlyTaskList = ArrayList::create(); - foreach ($queuedOnlyTasks as $task) { $taskList->push(ArrayData::create([ 'QueueLink' => Controller::join_links($baseUrl, 'dev/tasks/queue', $task['segment']), 'Title' => $task['title'], 'Description' => $task['description'], 'Type' => 'queue-only', + 'Parameters' => $task['parameters'], + 'Help' => $task['help'], ])); } diff --git a/src/Jobs/RunBuildTaskJob.php b/src/Jobs/RunBuildTaskJob.php index 775fb478..60515761 100644 --- a/src/Jobs/RunBuildTaskJob.php +++ b/src/Jobs/RunBuildTaskJob.php @@ -5,14 +5,16 @@ use SilverStripe\Control\HTTPRequest; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\BuildTask; +use SilverStripe\HybridExecution\HybridOutput; use SilverStripe\ORM\DataObject; use Symbiote\QueuedJobs\Services\AbstractQueuedJob; use Symbiote\QueuedJobs\Services\QueuedJob; +use Symfony\Component\Console\Input\ArrayInput; /** * A convenience wrapper for running BuildTask implementations. * These are usually executed via synchronous web request - * or synchronous CLI execution (under dev/tasks/*). + * or synchronous CLI execution. * * Caution: This job can't increment steps. This is a signal * for job health checks that a job should be considered stale @@ -83,8 +85,10 @@ public function process() $getVars = []; parse_str($this->QueryString ?? '', $getVars); - $request = new HTTPRequest('GET', '/', $getVars); - $task->run($request); + $output = HybridOutput::create(HybridOutput::FORMAT_ANSI); + $input = new ArrayInput($getVars); + $input->setInteractive(false); + $task->run($input, $output); $this->currentStep = 1; $this->isComplete = true; diff --git a/src/Services/AbstractQueuedJob.php b/src/Services/AbstractQueuedJob.php index f47604bb..6716893a 100644 --- a/src/Services/AbstractQueuedJob.php +++ b/src/Services/AbstractQueuedJob.php @@ -271,4 +271,30 @@ public function __get($name) { return isset($this->jobData->$name) ? $this->jobData->$name : null; } + + /** + * Resolves a queue name to one of the queue constants. + * If $queue is already an int representing a queue, that int will be returned. + * If the queue is unknown, `null` will be returned. + */ + public static function getQueue(string|int $queue): ?int + { + switch (strtolower($queue)) { + case 'immediate': + $queue = QueuedJob::IMMEDIATE; + break; + case 'queued': + $queue = QueuedJob::QUEUED; + break; + case 'large': + $queue = QueuedJob::LARGE; + break; + default: + $queues = [QueuedJob::IMMEDIATE, QueuedJob::QUEUED, QueuedJob::LARGE]; + if (!ctype_digit($queue) || !in_array((int) $queue, $queues)) { + return null; + } + } + return $queue; + } } diff --git a/src/Tasks/CheckJobHealthTask.php b/src/Tasks/CheckJobHealthTask.php index d5544a2a..3717dea6 100644 --- a/src/Tasks/CheckJobHealthTask.php +++ b/src/Tasks/CheckJobHealthTask.php @@ -2,25 +2,21 @@ namespace Symbiote\QueuedJobs\Tasks; -use Exception; -use SilverStripe\Control\HTTPRequest; +use Composer\Console\Input\InputOption; +use Psr\Log\LoggerInterface; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\BuildTask; -use Symbiote\QueuedJobs\Services\QueuedJob; +use SilverStripe\HybridExecution\HybridOutput; +use Symbiote\QueuedJobs\Services\AbstractQueuedJob; use Symbiote\QueuedJobs\Services\QueuedJobService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; class CheckJobHealthTask extends BuildTask { - /** - * {@inheritDoc} - * @var string - */ - private static $segment = 'CheckJobHealthTask'; + protected static string $commandName = 'CheckJobHealthTask'; - /** - * {@inheritDoc} - * @return string - */ - public function getDescription() + public static function getDescription(): string { return _t( __CLASS__ . '.Description', @@ -32,30 +28,49 @@ public function getDescription() /** * Implement this method in the task subclass to * execute via the TaskRunner - * - * @param HTTPRequest $request - * @return - * - * @throws Exception */ - public function run($request) + protected function execute(InputInterface $input, HybridOutput $output): int { - $queue = $request->requestVar('queue') ?: QueuedJob::QUEUED; + $queue = AbstractQueuedJob::getQueue($input->getOption('queue')); + if ($queue === null) { + $output->writeln('queue must be one of "immediate", "queued", or "large"'); + return Command::INVALID; + } $jobHealth = $this->getService()->checkJobHealth($queue); $unhealthyJobCount = 0; foreach ($jobHealth as $type => $IDs) { $count = count($IDs ?? []); - echo 'Detected and attempted restart on ' . $count . ' ' . $type . ' jobs'; + $output->writeln('Detected and attempted restart on ' . $count . ' ' . $type . ' jobs'); $unhealthyJobCount = $unhealthyJobCount + $count; } if ($unhealthyJobCount > 0) { - throw new Exception("$unhealthyJobCount jobs are unhealthy"); + $msg = "$unhealthyJobCount jobs are unhealthy"; + /** @var LoggerInterface $logger */ + $Logger = Injector::inst()->get(LoggerInterface::class . '.errorhandler'); + $Logger->error($msg); + $output->writeln($msg); + return Command::FAILURE; } - echo 'All jobs are healthy'; + $output->writeln('All jobs are healthy'); + return Command::SUCCESS; + } + + public function getOptions(): array + { + return [ + new InputOption( + 'queue', + null, + InputOption::VALUE_REQUIRED, + 'The queue to check', + 'queued', + ['immediate', 'queued', 'large'] + ), + ]; } protected function getService() diff --git a/src/Tasks/CreateQueuedJobTask.php b/src/Tasks/CreateQueuedJobTask.php index fa73f882..3167a117 100644 --- a/src/Tasks/CreateQueuedJobTask.php +++ b/src/Tasks/CreateQueuedJobTask.php @@ -2,11 +2,17 @@ namespace Symbiote\QueuedJobs\Tasks; -use SilverStripe\Control\HTTPRequest; +use Closure; +use ReflectionClass; use SilverStripe\Core\ClassInfo; use SilverStripe\Dev\BuildTask; +use SilverStripe\HybridExecution\HybridOutput; use SilverStripe\ORM\FieldType\DBDatetime; +use Symbiote\QueuedJobs\Services\QueuedJob; use Symbiote\QueuedJobs\Services\QueuedJobService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; /** * A task that can be used to create a queued job. @@ -21,16 +27,9 @@ */ class CreateQueuedJobTask extends BuildTask { - /** - * {@inheritDoc} - * @var string - */ - private static $segment = 'CreateQueuedJobTask'; + protected static string $commandName = 'CreateQueuedJobTask'; - /** - * @return string - */ - public function getDescription() + public static function getDescription(): string { return _t( __CLASS__ . '.Description', @@ -39,31 +38,68 @@ public function getDescription() ); } - /** - * @param HTTPRequest $request - */ - public function run($request) + protected function execute(InputInterface $input, HybridOutput $output): int { - if (isset($request['name']) && ClassInfo::exists($request['name'])) { - $clz = $request['name']; + $name = $input->getOption('name'); + if ($name && ClassInfo::exists($name)) { + $clz = $name; $job = new $clz(); } else { $job = new DummyQueuedJob(mt_rand(10, 100)); } - if (isset($request['start'])) { - $start = strtotime($request['start'] ?? ''); + $start = $input->getOption('start'); + if ($start) { + $start = strtotime($start); $now = DBDatetime::now()->getTimestamp(); if ($start >= $now) { $friendlyStart = DBDatetime::create()->setValue($start)->Rfc2822(); - echo 'Job queued to start at: ' . $friendlyStart . ''; + $output->writeln('Job queued to start at: ' . $friendlyStart . ''); QueuedJobService::singleton()->queueJob($job, $start); } else { - echo "'start' parameter must be a date/time in the future, parseable with strtotime"; + $output->writeln("'start' parameter must be a date/time in the future, parseable with strtotime"); } } else { - echo "Job Queued"; + $output->writeln('Job Queued'); QueuedJobService::singleton()->queueJob($job); } + return Command::SUCCESS; + } + + public function getOptions(): array + { + return [ + new InputOption( + 'name', + null, + InputOption::VALUE_REQUIRED, + 'Fully qualified classname for the job to queue', + suggestedValues: Closure::fromCallable([static::class, 'getAllQueuedJobClasses']) + ), + new InputOption( + 'start', + null, + InputOption::VALUE_REQUIRED, + 'When to start the job. Must be parsable by ' + . 'strtotime' + ), + ]; + } + + public static function getAllQueuedJobClasses(): array + { + $implementors = ClassInfo::implementorsOf(QueuedJob::class); + $classes = []; + foreach ($implementors as $class) { + $subclasses = ClassInfo::subclassesFor($class); + foreach ($subclasses as $subclass) { + $reflectionClass = new ReflectionClass($subclass); + if ($reflectionClass->isAbstract()) { + continue; + } + $classes[] = $subclass; + } + } + return $classes; } } diff --git a/src/Tasks/DeleteAllJobsTask.php b/src/Tasks/DeleteAllJobsTask.php index 4befd0c9..f72a8f42 100644 --- a/src/Tasks/DeleteAllJobsTask.php +++ b/src/Tasks/DeleteAllJobsTask.php @@ -2,10 +2,14 @@ namespace Symbiote\QueuedJobs\Tasks; -use SilverStripe\Control\HTTPRequest; +use SilverStripe\Control\Director; use SilverStripe\Dev\BuildTask; +use SilverStripe\HybridExecution\HybridOutput; use SilverStripe\ORM\DataObject; use Symbiote\QueuedJobs\DataObjects\QueuedJobDescriptor; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; /** * An administrative task to delete all queued jobs records from the database. @@ -13,41 +17,41 @@ */ class DeleteAllJobsTask extends BuildTask { - /** - * @inheritdoc - * @return string - */ - public function getTitle() + protected static string $commandName = 'delete-queued-jobs'; + + public function getTitle(): string { return "Delete all queued jobs."; } - /** - * @inheritdoc - * @return string - */ - public function getDescription() + public static function getDescription(): string { return "Remove all queued jobs from the database. Use with caution!"; } - /** - * Run the task - * @param HTTPRequest $request - */ - public function run($request) + protected function execute(InputInterface $input, HybridOutput $output): int { - $confirm = $request->getVar('confirm'); - $jobs = DataObject::get(QueuedJobDescriptor::class); - if (!$confirm) { - echo "Really delete " . $jobs->count() . " jobs? Please add ?confirm=1 to the URL to confirm."; - return; + if (!$input->getOption('confirm')) { + if (Director::is_cli()) { + $confirmText = '?confirm=1 to the URL'; + } else { + $confirmText = '--confirm'; + } + $output->writeln('Really delete ' . $jobs->count() . " jobs? Please add $confirmText to confirm."); + return Command::INVALID; } - echo "Deleting " . $jobs->count() . " jobs...
\n"; + $output->writeln('Deleting ' . $jobs->count() . ' jobs...'); $jobs->removeAll(); - echo "Done."; + return Command::SUCCESS; + } + + public function getOptions(): array + { + return [ + new InputOption('confirm', null, InputOption::VALUE_NONE, 'Confirm you want to delete the jobs'), + ]; } } diff --git a/src/Tasks/Engines/DoormanRunner.php b/src/Tasks/Engines/DoormanRunner.php index 41981e44..6483d745 100644 --- a/src/Tasks/Engines/DoormanRunner.php +++ b/src/Tasks/Engines/DoormanRunner.php @@ -2,7 +2,6 @@ namespace Symbiote\QueuedJobs\Tasks\Engines; -use SilverStripe\Dev\Deprecation; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Environment; @@ -11,7 +10,6 @@ use Symbiote\QueuedJobs\Jobs\DoormanQueuedJobTask; use Symbiote\QueuedJobs\Services\ProcessManager; use Symbiote\QueuedJobs\Services\QueuedJob; -use Symbiote\QueuedJobs\Services\QueuedJobService; /** * Runs all jobs through the doorman engine @@ -38,12 +36,12 @@ class DoormanRunner extends BaseRunner implements TaskRunnerEngine private static $tick_interval = 1; /** - * Name of the dev task used to run the child process + * Name of the command used to run the child process * * @config * @var string */ - private static $child_runner = 'ProcessJobQueueChildTask'; + private static $child_runner = 'queuedjobs:process-queue-child'; /** * @var string[] @@ -92,7 +90,7 @@ public function runQueue($queue) $manager = Injector::inst()->create(ProcessManager::class); $manager->setWorker( sprintf( - '%s/vendor/silverstripe/framework/cli-script.php dev/tasks/%s', + '%s/vendor/bin/sake %s', BASE_PATH, $this->getChildRunner() ) diff --git a/src/Tasks/ProcessJobQueueChildTask.php b/src/Tasks/ProcessJobQueueChildTask.php deleted file mode 100644 index e6c5e51f..00000000 --- a/src/Tasks/ProcessJobQueueChildTask.php +++ /dev/null @@ -1,43 +0,0 @@ -getService()->runJob($task->getDescriptor()->ID); - } - } - - /** - * Returns an instance of the QueuedJobService. - * - * @return QueuedJobService - */ - protected function getService() - { - return QueuedJobService::singleton(); - } -} diff --git a/src/Tasks/ProcessJobQueueTask.php b/src/Tasks/ProcessJobQueueTask.php index 84df6ac8..5efe8945 100644 --- a/src/Tasks/ProcessJobQueueTask.php +++ b/src/Tasks/ProcessJobQueueTask.php @@ -2,14 +2,17 @@ namespace Symbiote\QueuedJobs\Tasks; -use Monolog\Handler\FilterHandler; -use Monolog\Handler\StreamHandler; use Monolog\Logger; -use SilverStripe\Control\HTTPRequest; -use SilverStripe\Core\Environment; +use SilverStripe\Control\Director; use SilverStripe\Dev\BuildTask; +use SilverStripe\HybridExecution\HybridOutput; +use SilverStripe\HybridExecution\HybridOutputLogHandler; +use Symbiote\QueuedJobs\Services\AbstractQueuedJob; use Symbiote\QueuedJobs\Services\QueuedJob; use Symbiote\QueuedJobs\Services\QueuedJobService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; /** * Task used to process the job queue @@ -19,16 +22,9 @@ */ class ProcessJobQueueTask extends BuildTask { - /** - * {@inheritDoc} - * @var string - */ - private static $segment = 'ProcessJobQueueTask'; + protected static string $commandName = 'ProcessJobQueueTask'; - /** - * @return string - */ - public function getDescription() + public static function getDescription(): string { return _t( __CLASS__ . '.Description', @@ -36,88 +32,49 @@ public function getDescription() ); } - /** - * @param HTTPRequest $request - */ - public function run($request) + protected function execute(InputInterface $input, HybridOutput $output): int { if (QueuedJobService::singleton()->isMaintenanceLockActive()) { - return; + return Command::FAILURE; + } + $queue = AbstractQueuedJob::getQueue($input->getOption('queue')); + if ($queue === null) { + $output->writeln('queue must be one of "immediate", "queued", or "large"'); + return Command::INVALID; } $service = $this->getService(); - // Ensure that log messages are visible when executing this task on CLI. - // Could be replaced with BuildTask logger: https://github.com/silverstripe/silverstripe-framework/issues/9183 - if (Environment::isCli()) { + // Ensure that log messages are visible when executing this task in CLI. + // Running the task via browser doesn't need this output because you can check the job in the CMS. + // Note that if we want to output this to the browser in the future, simply removing this condition + // isn't enough, because it'll end up double-logging in the job messages tab. + if (Director::is_cli()) { $logger = $service->getLogger(); - - // Assumes that general purpose logger usually doesn't already contain a stream handler. - $errorHandler = new StreamHandler('php://stderr', Logger::ERROR); - $standardHandler = new StreamHandler('php://stdout'); - - // Avoid double logging of errors - $standardFilterHandler = new FilterHandler( - $standardHandler, - Logger::DEBUG, - Logger::WARNING - ); - - $logger->pushHandler($standardFilterHandler); - $logger->pushHandler($errorHandler); + if ($logger instanceof Logger) { + $logger->pushHandler(HybridOutputLogHandler::create($output)); + } } - if ($request->getVar('list')) { + if ($input->getOption('list')) { // List helper $service->queueRunner->listJobs(); - return; + return Command::SUCCESS; } // Check if there is a job to run - if (($job = $request->getVar('job')) && strpos($job ?? '', '-')) { - // Run from a isngle job + $job = $input->getOption('job'); + if ($job && strpos($job, '-')) { + // Run from a single job $parts = explode('-', $job ?? ''); $id = $parts[1]; $service->runJob($id); - return; + return Command::SUCCESS; } // Run the queue - $queue = $this->getQueue($request); $service->runQueue($queue); - } - - /** - * Resolves the queue name to one of a few aliases. - * - * @todo Solve the "Queued"/"queued" mystery! - * - * @param HTTPRequest $request - * @return string - */ - protected function getQueue($request) - { - $queue = $request->getVar('queue'); - - if (!$queue) { - $queue = 'Queued'; - } - - switch (strtolower($queue ?? '')) { - case 'immediate': - $queue = QueuedJob::IMMEDIATE; - break; - case 'queued': - $queue = QueuedJob::QUEUED; - break; - case 'large': - $queue = QueuedJob::LARGE; - break; - default: - break; - } - - return $queue; + return Command::SUCCESS; } /** @@ -129,4 +86,20 @@ public function getService() { return QueuedJobService::singleton(); } + + public function getOptions(): array + { + return [ + new InputOption('list', null, InputOption::VALUE_NONE, 'List jobs instead of processing a queue'), + new InputOption('job', null, InputOption::VALUE_REQUIRED, 'A specific job to run'), + new InputOption( + 'queue', + null, + InputOption::VALUE_REQUIRED, + 'The queue to process', + 'queued', + ['immediate', 'queued', 'large'] + ), + ]; + } } diff --git a/src/Tasks/PublishItemsTask.php b/src/Tasks/PublishItemsTask.php index b486543e..5c4d9807 100644 --- a/src/Tasks/PublishItemsTask.php +++ b/src/Tasks/PublishItemsTask.php @@ -4,8 +4,12 @@ use Exception; use SilverStripe\Dev\BuildTask; +use SilverStripe\HybridExecution\HybridOutput; use SilverStripe\ORM\DataObject; use Symbiote\QueuedJobs\Jobs\PublishItemsJob; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; /** * An example build task that publishes a bunch of pages - this demonstrates a realworld example of how the @@ -16,21 +20,13 @@ */ class PublishItemsTask extends BuildTask { - /** - * {@inheritDoc} - * @var string - */ - private static $segment = 'PublishItemsTask'; + protected static string $commandName = 'PublishItemsTask'; - /** - * @throws Exception - * @param HTTPRequest $request - */ - public function run($request) + protected function execute(InputInterface $input, HybridOutput $output): int { - $root = $request->getVar('parent'); + $root = $input->getOption('parent'); if (!$root) { - throw new Exception("Sorry, you must provide a parent node to publish from"); + $output->writeln('Sorry, you must provide a parent node to publish from'); } $item = DataObject::get_by_id('Page', $root); @@ -39,5 +35,18 @@ public function run($request) $job = new PublishItemsJob($root); singleton('Symbiote\\QueuedJobs\\Services\\QueuedJobService')->queueJob($job); } + return Command::SUCCESS; + } + + public function getOptions(): array + { + return [ + new InputOption( + 'parent', + null, + InputOption::VALUE_REQUIRED, + 'The ID of the page you want to publish. This page and its children will be published' + ), + ]; } } diff --git a/templates/Symbiote/QueuedJobs/Controllers/QueuedTaskRunner.ss b/templates/Symbiote/QueuedJobs/Controllers/QueuedTaskRunner.ss index fb11b192..4532f7f2 100644 --- a/templates/Symbiote/QueuedJobs/Controllers/QueuedTaskRunner.ss +++ b/templates/Symbiote/QueuedJobs/Controllers/QueuedTaskRunner.ss @@ -29,7 +29,19 @@ $Info.RAW

$Title

-
$Description
+
+ $Description + <% if $Help %> +
+ Display additional information + $Help +
+ <% end_if %> +
+ <% if $Parameters %> + Parameters: + <% include SilverStripe/Dev/Parameters %> + <% end_if %>
<% if $TaskLink %>