From 044e92bf4e093f04cb71f7995ce8db851119663f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 14 Oct 2025 11:01:39 +0200 Subject: [PATCH] feat(TaskProcessing): Introduce ITriggerableProvider Signed-off-by: Marcel Klehr # Conflicts: # lib/private/TaskProcessing/Db/TaskMapper.php --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 13 +-- lib/private/TaskProcessing/Db/TaskMapper.php | 16 ++++ lib/private/TaskProcessing/Manager.php | 11 +++ .../TaskProcessing/ITriggerableProvider.php | 27 ++++++ .../lib/TaskProcessing/TaskProcessingTest.php | 87 +++++++++++++++++++ 6 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 lib/public/TaskProcessing/ITriggerableProvider.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 76043b85952..f7a6585924e 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -872,6 +872,7 @@ return array( 'OCP\\TaskProcessing\\IProvider' => $baseDir . '/lib/public/TaskProcessing/IProvider.php', 'OCP\\TaskProcessing\\ISynchronousProvider' => $baseDir . '/lib/public/TaskProcessing/ISynchronousProvider.php', 'OCP\\TaskProcessing\\ITaskType' => $baseDir . '/lib/public/TaskProcessing/ITaskType.php', + 'OCP\\TaskProcessing\\ITriggerableProvider' => $baseDir . '/lib/public/TaskProcessing/ITriggerableProvider.php', 'OCP\\TaskProcessing\\ShapeDescriptor' => $baseDir . '/lib/public/TaskProcessing/ShapeDescriptor.php', 'OCP\\TaskProcessing\\ShapeEnumValue' => $baseDir . '/lib/public/TaskProcessing/ShapeEnumValue.php', 'OCP\\TaskProcessing\\Task' => $baseDir . '/lib/public/TaskProcessing/Task.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 5b2c871132e..2f8483fad80 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -11,32 +11,32 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 ); public static $prefixLengthsPsr4 = array ( - 'O' => + 'O' => array ( 'OC\\Core\\' => 8, 'OC\\' => 3, 'OCP\\' => 4, ), - 'N' => + 'N' => array ( 'NCU\\' => 4, ), ); public static $prefixDirsPsr4 = array ( - 'OC\\Core\\' => + 'OC\\Core\\' => array ( 0 => __DIR__ . '/../../..' . '/core', ), - 'OC\\' => + 'OC\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/private', ), - 'OCP\\' => + 'OCP\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/public', ), - 'NCU\\' => + 'NCU\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/unstable', ), @@ -913,6 +913,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\TaskProcessing\\IProvider' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/IProvider.php', 'OCP\\TaskProcessing\\ISynchronousProvider' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ISynchronousProvider.php', 'OCP\\TaskProcessing\\ITaskType' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ITaskType.php', + 'OCP\\TaskProcessing\\ITriggerableProvider' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ITriggerableProvider.php', 'OCP\\TaskProcessing\\ShapeDescriptor' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ShapeDescriptor.php', 'OCP\\TaskProcessing\\ShapeEnumValue' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ShapeEnumValue.php', 'OCP\\TaskProcessing\\Task' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Task.php', diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php index e235ff4ec0c..f9a33081a76 100644 --- a/lib/private/TaskProcessing/Db/TaskMapper.php +++ b/lib/private/TaskProcessing/Db/TaskMapper.php @@ -264,4 +264,20 @@ class TaskMapper extends QBMapper { return $this->findEntities($qb); } + + /** + * @throws Exception + */ + public function hasRunningTasksForTaskType(string $getTaskTypeId): bool { + $qb = $this->db->getQueryBuilder(); + $qb->select(Task::$columns) + ->from($this->tableName); + $qb->where($qb->expr()->eq('type', $qb->createNamedParameter($getTaskTypeId))); + $qb->andWhere($qb->expr()->eq('status', $qb->createNamedParameter(\OCP\TaskProcessing\Task::STATUS_RUNNING, IQueryBuilder::PARAM_INT))); + $qb->setMaxResults(1); + $result = $qb->executeQuery(); + $hasRunningTasks = $result->fetch() !== false; + $result->closeCursor(); + return $hasRunningTasks; + } } diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php index c2e272c76c3..e80935212d9 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -56,6 +56,7 @@ use OCP\TaskProcessing\IManager; use OCP\TaskProcessing\IProvider; use OCP\TaskProcessing\ISynchronousProvider; use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ITriggerableProvider; use OCP\TaskProcessing\ShapeDescriptor; use OCP\TaskProcessing\ShapeEnumValue; use OCP\TaskProcessing\Task; @@ -976,6 +977,16 @@ class Manager implements IManager { if ($provider instanceof ISynchronousProvider) { $this->jobList->add(SynchronousBackgroundJob::class, null); } + if ($provider instanceof ITriggerableProvider) { + try { + if (!$this->taskMapper->hasRunningTasksForTaskType($task->getTaskTypeId())) { + // If no tasks are currently running for this task type, nudge the provider to ask for tasks + $provider->trigger(); + } + } catch (Exception $e) { + $this->logger->error('Failed to check DB for running tasks after a task was scheduled for a triggerable provider. Not triggering the provider.', ['exception' => $e]); + } + } } public function runTask(Task $task): Task { diff --git a/lib/public/TaskProcessing/ITriggerableProvider.php b/lib/public/TaskProcessing/ITriggerableProvider.php new file mode 100644 index 00000000000..c837d953f0b --- /dev/null +++ b/lib/public/TaskProcessing/ITriggerableProvider.php @@ -0,0 +1,27 @@ + new SuccessfulTextToImageProvider(), FailingTextToImageProvider::class => new FailingTextToImageProvider(), ExternalProvider::class => new ExternalProvider(), + ExternalTriggerableProvider::class => new ExternalTriggerableProvider(), ConflictingExternalProvider::class => new ConflictingExternalProvider(), ExternalTaskType::class => new ExternalTaskType(), ConflictingExternalTaskType::class => new ConflictingExternalTaskType(), @@ -1227,6 +1276,44 @@ class TaskProcessingTest extends \Test\TestCase { self::assertCount(1, $providers); // Ensure no extra provider was added } + public function testTriggerableProviderWithNoOtherRunningTasks() { + // Arrange: Local provider registered, conflicting external provider via event + $this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([]); + $this->registrationContext->expects($this->any())->method('getTextProcessingProviders')->willReturn([]); + $this->registrationContext->expects($this->any())->method('getTextToImageProviders')->willReturn([]); + $this->registrationContext->expects($this->any())->method('getSpeechToTextProviders')->willReturn([]); + + $externalProvider = $this->createPartialMock(ExternalTriggerableProvider::class, ['trigger']); + $externalProvider->expects($this->once())->method('trigger'); + $this->configureEventDispatcherMock(providersToAdd: [$externalProvider]); + $this->manager = $this->createManagerInstance(); + + // Act + $task = new Task($externalProvider->getTaskTypeId(), ['input' => ''], 'tests', 'foobar'); + $this->manager->scheduleTask($task); + } + + public function testTriggerableProviderWithOtherRunningTasks() { + // Arrange: Local provider registered, conflicting external provider via event + $this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([]); + $this->registrationContext->expects($this->any())->method('getTextProcessingProviders')->willReturn([]); + $this->registrationContext->expects($this->any())->method('getTextToImageProviders')->willReturn([]); + $this->registrationContext->expects($this->any())->method('getSpeechToTextProviders')->willReturn([]); + + $externalProvider = $this->createPartialMock(ExternalTriggerableProvider::class, ['trigger']); + $externalProvider->expects($this->once())->method('trigger'); + $this->configureEventDispatcherMock(providersToAdd: [$externalProvider]); + $this->manager = $this->createManagerInstance(); + + $task = new Task($externalProvider->getTaskTypeId(), ['input' => ''], 'tests', 'foobar'); + $this->manager->scheduleTask($task); + $this->manager->lockTask($task); + + // Act + $task = new Task($externalProvider->getTaskTypeId(), ['input' => ''], 'tests', 'foobar'); + $this->manager->scheduleTask($task); + } + public function testMergeTaskTypesLocalAndEvent() { // Arrange: Local type registered, DIFFERENT external type via event $this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([