Skip to content

Commit

Permalink
Add test for signal handling during cron archive scheduled tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
mneudert committed Aug 26, 2024
1 parent e0b5bfc commit 138b7e8
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 0 deletions.
8 changes: 8 additions & 0 deletions tests/PHPUnit/Fixtures/CronArchiveProcessSignal.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ public function provideContainerConfig(): array
'CronArchive.init.finish',
DI::value(Closure::fromCallable([$this->stepControl, 'handleCronArchiveStart'])),
],
[
'ScheduledTasks.execute',
DI::value(Closure::fromCallable([$this->stepControl, 'handleScheduledTasksExecute'])),
],
[
'ScheduledTasks.shouldExecuteTask',
DI::value(Closure::fromCallable([$this->stepControl, 'handleScheduledTasksShouldExecute'])),
]
]),
];
}
Expand Down
53 changes: 53 additions & 0 deletions tests/PHPUnit/Fixtures/CronArchiveProcessSignal/StepControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class StepControl

private const OPTION_ARCHIVE_REPORTS_BLOCKED = self::OPTION_PREFIX . 'ArchiveReportsBlocked';
private const OPTION_CRON_ARCHIVE_BLOCKED = self::OPTION_PREFIX . 'CronArchiveBlocked';
private const OPTION_SCHEDULED_TASKS_BLOCKED = self::OPTION_PREFIX . 'ScheduledTasksBlocked';
private const OPTION_SCHEDULED_TASKS_EXECUTE = self::OPTION_PREFIX . 'ScheduledTasksExecute';

/**
* Block proceeding from the "API.CoreAdminHome.archiveReports" event.
Expand All @@ -31,6 +33,14 @@ public function blockAPIArchiveReports(array $blockSpec): void
Option::set(self::OPTION_ARCHIVE_REPORTS_BLOCKED, json_encode($blockSpec));
}

/**
* Block proceeding from the "ScheduledTasks.execute" event.
*/
public function blockScheduledTasks(): void
{
Option::set(self::OPTION_SCHEDULED_TASKS_BLOCKED, true);
}

/**
* Block proceeding from the "CronArchive.init.finish" event.
*/
Expand All @@ -39,6 +49,14 @@ public function blockCronArchiveStart(): void
Option::set(self::OPTION_CRON_ARCHIVE_BLOCKED, true);
}

/**
* Force scheduled tasks to execute.
*/
public function executeScheduledTasks(): void
{
Option::set(self::OPTION_SCHEDULED_TASKS_EXECUTE, true);
}

/**
* DI hook intercepting the "API.CoreAdminHome.archiveReports" event.
*/
Expand Down Expand Up @@ -84,6 +102,33 @@ public function handleCronArchiveStart(): void
}
}

/**
* DI hook intercepting the "ScheduledTasks.execute" event.
*/
public function handleScheduledTasksExecute(): void
{
$continue = $this->waitForSuccess(static function (): bool {
// force reading from database
Option::clearCachedOption(self::OPTION_SCHEDULED_TASKS_BLOCKED);

return false === Option::get(self::OPTION_SCHEDULED_TASKS_BLOCKED);
});

if (!$continue) {
throw new RuntimeException('Waiting for ScheduledTask option took too long!');
}
}

/**
* DI hook intercepting the "ScheduledTasks.shouldExecuteTask" event.
*/
public function handleScheduledTasksShouldExecute(bool &$shouldExecuteTask): void
{
if (Option::get(self::OPTION_SCHEDULED_TASKS_EXECUTE)) {
$shouldExecuteTask = true;
}
}

/**
* Remove all internal blocks.
*/
Expand All @@ -108,6 +153,14 @@ public function unblockCronArchiveStart(): void
Option::delete(self::OPTION_CRON_ARCHIVE_BLOCKED);
}

/**
* Allow proceeding past the "ScheduledTasks.execute" event.
*/
public function unblockScheduledTasks(): void
{
Option::delete(self::OPTION_SCHEDULED_TASKS_BLOCKED);
}

/**
* Wait until a callable returns true or a timeout is reached.
*/
Expand Down
46 changes: 46 additions & 0 deletions tests/PHPUnit/Integration/CronArchiveProcessSignalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
use Piwik\DataAccess\Model;
use Piwik\Db;
use Piwik\Piwik;
use Piwik\Plugins\CoreAdminHome\Tasks;
use Piwik\Plugins\CoreConsole\FeatureFlags\CliMultiProcessSymfony;
use Piwik\Scheduler\Task;
use Piwik\Segment;
use Piwik\Tests\Fixtures\CronArchiveProcessSignal as CronArchiveProcessSignalFixture;
use Piwik\Tests\Framework\Fixture;
Expand Down Expand Up @@ -280,6 +282,50 @@ public function getSigtermDuringArchivingData(): iterable
];
}

/**
* @dataProvider getScheduledTasksStoppedData
*/
public function testScheduledTasksStopped(int $signal): void
{
self::$fixture->stepControl->blockScheduledTasks();
self::$fixture->stepControl->executeScheduledTasks();

// we don't care for the exact method, pick one with low setup complexity
$this->setUpArchivingMethod(self::METHOD_ASYNC_CLI);

$process = $this->startArchivingProcess(self::METHOD_ASYNC_CLI);

// wait until scheduled tasks are running
$result = self::$fixture->stepControl->waitForSuccess(static function () use ($process): bool {
return false !== strpos($process->getOutput(), 'Scheduler: executing task');
}, $timeoutInSeconds = 60);

self::assertTrue($result, 'Scheduled tasks did not start');

$this->sendSignalToProcess($process, $signal, self::METHOD_ASYNC_CLI);

self::$fixture->stepControl->unblockScheduledTasks();

$this->waitForArchivingProcessToStop($process);

$processOutput = $process->getOutput();
$expectedExecutedTask = Task::getTaskName(Tasks::class, 'invalidateOutdatedArchives', null);
$expectedSkippedTask = Task::getTaskName(Tasks::class, 'purgeOutdatedArchives', null);

self::assertStringContainsString('executing task ' . $expectedExecutedTask, $processOutput);
self::assertStringNotContainsString('executing task ' . $expectedSkippedTask, $processOutput);

self::assertStringContainsString('Received system signal: ' . $signal, $processOutput);
self::assertStringContainsString('Trying to stop running tasks...', $processOutput);
self::assertStringContainsString('Scheduler: Aborting due to received signal', $processOutput);
}

public function getScheduledTasksStoppedData(): iterable
{
yield 'stop using sigint' => [\SIGINT];
yield 'stop using sigterm' => [\SIGTERM];
}

/**
* @param array{inProgress: int, total: int} $expectedCounts
*/
Expand Down

0 comments on commit 138b7e8

Please sign in to comment.