Initial worker definition. Needs a jobs table to be created

This commit is contained in:
2022-05-16 15:22:37 +01:00
parent ae36ccb23b
commit fb24b2ee72
9 changed files with 412 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker\Bakery;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use UserFrosting\System\Bakery\BaseCommand;
/**
* worker Bakery Command
* List potential jobs.
*
* @author Craig Williams (https://avsdev.uk)
*/
class WorkerListCommand extends BaseCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('worker:list')
->setDescription('List all potential jobs')
->setHelp('This command returns a list of potential worker jobs that can be queued to be run by the worker.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->io->title('Available Jobs List');
$jobs = $this->ci->worker->getAvailableJobDefinitions();
$this->io->table(['Sprinkle', 'Name', 'Namespace'], $jobs);
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker\Bakery;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use UserFrosting\System\Bakery\BaseCommand;
/**
* worker:run Bakery Command
* Run a worker thread.
*
* @author Craig Williams (https://avsdev.uk)
*/
class WorkerRunCommand extends BaseCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('worker:run')
->setDescription('Run a worker loop')
->setHelp('This command runs a the workers job loop. The worker will not return until SIGTERM is sent.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$worker = $this->ci->worker;
$worker->run($this->io);
return self::SUCCESS;
}
}

35
src/Jobs/JobBase.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker\Jobs;
use Psr\Container\ContainerInterface;
/**
* Job Base Class
*
* @author Craig Williams (https://avsdev.uk)
*/
abstract class JobBase implements JobInterface
{
/**
* @var ContainerInterface
*/
protected $ci;
/**
* Constructor.
*
* @param ContainerInterface $ci
*/
public function __construct(ContainerInterface $ci)
{
$this->ci = $ci;
}
}

31
src/Jobs/JobInterface.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker\Jobs;
/**
* All jobs must implement this interface.
*
* @author Craig Williams (https://avsdev.uk)
*/
interface JobInterface
{
/**
* Function used to specify what the job does.
*
* Add parameters with default values if needed.
*/
public function run();
/**
* Terminate the current job
*/
public function terminate();
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker\ServicesProvider;
use Illuminate\Container\Container;
use Psr\Container\ContainerInterface;
use UserFrosting\Sprinkle\Worker\Worker\Worker;
use UserFrosting\Sprinkle\Core\ServicesProvider\BaseServicesProvider;
/**
* Worker services provider.
*
* Registers core services for the Worker sprinkle.
*
* @author Craig Williams (https://avsdev.uk)
*/
class ServicesProvider
{
/**
* Register Worker's services.
*
* @param ContainerInterface $container A DI container implementing ArrayAccess and psr-container.
*/
public function register(ContainerInterface $container)
{
/*
* Return an instance of the worker
*
* @return \UserFrosting\Sprinkle\Worker\Worker\Worker
*/
$container['worker'] = function ($c) {
return new Worker($c);
};
}
}

40
src/Worker.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker;
use UserFrosting\System\Sprinkle\Sprinkle;
/**
* Bootstrapper class for the 'worker' sprinkle.
*
* @author Craig Williams (https://avsdev.uk)
*/
class Worker extends Sprinkle
{
/**
* Set static references to DI container in necessary classes.
*/
public function onSprinklesInitialized()
{
$this->registerStreams();
}
/**
* Register worker sprinkle locator streams.
*/
protected function registerStreams()
{
/** @var \UserFrosting\UniformResourceLocator\ResourceLocator $locator */
$locator = $this->ci->locator;
// Register jobs sprinkle class streams
$locator->registerStream('jobs', '', \UserFrosting\JOBS_DIR);
}
}

147
src/Worker/Worker.php Normal file
View File

@@ -0,0 +1,147 @@
<?php
/*
* AVSDev UF Worker (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-worker
* @license https://git.avsdev.uk/avsdev/sprinkle-worker/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Worker\Worker;
use Illuminate\Support\Str;
use Psr\Container\ContainerInterface;
use UserFrosting\UniformResourceLocator\Resource as ResourceInstance;
/**
* Worker Class.
*
* Finds all job classes across sprinkles
*
* @author Craig Williams (https://avsdev.uk)
*/
class Worker
{
/**
* @var ContainerInterface
*/
protected $ci;
/**
* @var string The resource locator scheme
*/
protected $scheme = 'jobs://';
/**
* @var object The currently processing job
*/
protected $currentJob = null;
/**
* Class Constructor.
*
* @param ContainerInterface $ci
*/
public function __construct(ContainerInterface $ci)
{
$this->ci = $ci;
}
/**
* Loop all the available sprinkles and return a list of their jobs.
*
* @return array An array of all the task classes found for every sprinkle
*/
public function getAvailableJobDefinitions()
{
$tasks = $this->ci->locator->listResources($this->scheme, false, false);
return $this->loadAvailableJobDefinitions($tasks);
}
/**
* Process jobs Resource into info.
*
* @param array $taskFiles List of jobs files
*
* @return array
*/
protected function loadAvailableJobDefinitions(array $jobFiles)
{
$jobs = [];
foreach ($jobFiles as $jobFile) {
$job = $this->getJobDefinition($jobFile);
if ($job) {
$jobs[] = $job;
}
}
return $jobs;
}
/**
* Return an array of job details including the class name and the sprinkle name.
*
* @param ResourceInstance $file The job file
*
* @return array The details about a job file [name, class, sprinkle]
*/
protected function getJobDefinition(ResourceInstance $file)
{
// Format the sprinkle name for the namespace
$sprinkleName = $file->getLocation()->getName();
$sprinkleName = Str::studly($sprinkleName);
// Getting base path, name and class name
$basePath = str_replace($file->getBasename(), '', $file->getBasePath());
$name = $basePath . $file->getFilename();
$className = str_replace('/', '\\', $basePath) . $file->getFilename();
$classPath = "\\UserFrosting\\Sprinkle\\$sprinkleName\\Worker\\Jobs\\$className";
if ($className == "JobInterface" || $className == "JobBase") {
return null;
}
// Build the class name and namespace
return [
'sprinkle' => $sprinkleName,
'name' => $name,
'class' => $classPath,
];
}
/**
* Runs the worker loop, procssing queued jobs.
*/
public function run($io)
{
pcntl_async_signals(TRUE);
pcntl_signal(SIGINT, array($this, "handleTerm"));
pcntl_signal(SIGQUIT, array($this, "handleTerm"));
pcntl_signal(SIGTERM, array($this, "handleTerm"));
while(true) {
// TODO: find jobs in the database
try {
// Run the job using call_user_func_array
} catch(Exception $ex) {
$io->error('An exception occurred attempting to run a job');
var_dump($ex);
} finally {
$this->currentJob = null;
}
}
}
public function handleTerm($signo, $siginfo) {
if ($this->currentJob) {
$this->currentJob->terminate();
$this->currentJob = null;
}
exit;
}
}