Skip to content

Commit 5bd3f59

Browse files
committed
Fix schedules running twice, closes pterodactyl#1288
1 parent 413a22a commit 5bd3f59

File tree

6 files changed

+18
-72
lines changed

6 files changed

+18
-72
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ This file is a running track of new features and fixes to each version of the pa
33

44
This project follows [Semantic Versioning](http://semver.org) guidelines.
55

6+
## v0.7.10 (Derelict Dermodactylus)
7+
### Fixed
8+
* Scheduled tasks triggered manually no longer improperly change the `next_run_at` time and do not run twice in a row anymore.
9+
610
## v0.7.9 (Derelict Dermodactylus)
711
### Fixed
812
* Fixes a two-factor authentication bypass present in the password reset process for an account.

app/Console/Commands/Schedule/ProcessRunnableCommand.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public function __construct(ProcessScheduleService $processScheduleService, Sche
5757
public function handle()
5858
{
5959
$schedules = $this->repository->getSchedulesToProcess(Chronos::now()->toAtomString());
60+
if ($schedules->count() < 1) {
61+
$this->line('There are no scheduled tasks for servers that need to be run.');
62+
63+
return;
64+
}
6065

6166
$bar = $this->output->createProgressBar(count($schedules));
6267
$schedules->each(function ($schedule) use ($bar) {

app/Http/Controllers/Server/Tasks/ActionController.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Pterodactyl\Http\Controllers\Server\Tasks;
44

5-
use Cake\Chronos\Chronos;
65
use Illuminate\Http\Request;
76
use Illuminate\Http\Response;
87
use Pterodactyl\Http\Controllers\Controller;
@@ -71,7 +70,7 @@ public function trigger(Request $request): Response
7170
$server = $request->attributes->get('server');
7271
$this->authorize('toggle-schedule', $server);
7372

74-
$this->processScheduleService->setRunTimeOverride(Chronos::now())->handle(
73+
$this->processScheduleService->handle(
7574
$request->attributes->get('schedule')
7675
);
7776

app/Repositories/Eloquent/ScheduleRepository.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public function getSchedulesToProcess(string $timestamp): Collection
7575
{
7676
return $this->getBuilder()->with('tasks')
7777
->where('is_active', true)
78+
->where('is_processing', false)
7879
->where('next_run_at', '<=', $timestamp)
7980
->get($this->getColumns());
8081
}

app/Services/Schedules/ProcessScheduleService.php

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
namespace Pterodactyl\Services\Schedules;
44

5-
use DateTimeInterface;
65
use Cron\CronExpression;
7-
use Cake\Chronos\Chronos;
86
use Pterodactyl\Models\Schedule;
9-
use Cake\Chronos\ChronosInterface;
107
use Illuminate\Contracts\Bus\Dispatcher;
118
use Pterodactyl\Jobs\Schedule\RunTaskJob;
129
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
@@ -19,11 +16,6 @@ class ProcessScheduleService
1916
*/
2017
private $dispatcher;
2118

22-
/**
23-
* @var \DateTimeInterface|null
24-
*/
25-
private $runTimeOverride;
26-
2719
/**
2820
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
2921
*/
@@ -51,20 +43,6 @@ public function __construct(
5143
$this->taskRepository = $taskRepository;
5244
}
5345

54-
/**
55-
* Set the time that this schedule should be run at. This will override the time
56-
* defined on the schedule itself. Useful for triggering one-off task runs.
57-
*
58-
* @param \DateTimeInterface $time
59-
* @return $this
60-
*/
61-
public function setRunTimeOverride(DateTimeInterface $time)
62-
{
63-
$this->runTimeOverride = $time;
64-
65-
return $this;
66-
}
67-
6846
/**
6947
* Process a schedule and push the first task onto the queue worker.
7048
*
@@ -89,7 +67,7 @@ public function handle(Schedule $schedule)
8967

9068
$this->scheduleRepository->update($schedule->id, [
9169
'is_processing' => true,
92-
'next_run_at' => $this->getRunAtTime($formattedCron),
70+
'next_run_at' => CronExpression::factory($formattedCron)->getNextRunDate(),
9371
]);
9472

9573
$this->taskRepository->update($task->id, ['is_queued' => true]);
@@ -98,19 +76,4 @@ public function handle(Schedule $schedule)
9876
(new RunTaskJob($task->id, $schedule->id))->delay($task->time_offset)
9977
);
10078
}
101-
102-
/**
103-
* Get the timestamp to store in the database as the next_run time for a schedule.
104-
*
105-
* @param string $formatted
106-
* @return \Cake\Chronos\ChronosInterface
107-
*/
108-
private function getRunAtTime(string $formatted): ChronosInterface
109-
{
110-
if (! is_null($this->runTimeOverride)) {
111-
return $this->runTimeOverride instanceof ChronosInterface ? $this->runTimeOverride : Chronos::instance($this->runTimeOverride);
112-
}
113-
114-
return Chronos::instance(CronExpression::factory($formatted)->getNextRunDate());
115-
}
11679
}

tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Mockery as m;
66
use Tests\TestCase;
77
use Cron\CronExpression;
8-
use Cake\Chronos\Chronos;
98
use Pterodactyl\Models\Task;
109
use Pterodactyl\Models\Schedule;
1110
use Illuminate\Contracts\Bus\Dispatcher;
@@ -38,7 +37,6 @@ public function setUp()
3837
{
3938
parent::setUp();
4039

41-
Chronos::setTestNow(Chronos::now());
4240
$this->dispatcher = m::mock(Dispatcher::class);
4341
$this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class);
4442
$this->taskRepository = m::mock(TaskRepositoryInterface::class);
@@ -59,7 +57,7 @@ public function testScheduleIsUpdatedAndRun()
5957
$formatted = sprintf('%s %s %s * %s', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week);
6058
$this->scheduleRepository->shouldReceive('update')->with($model->id, [
6159
'is_processing' => true,
62-
'next_run_at' => Chronos::parse(CronExpression::factory($formatted)->getNextRunDate()->format(Chronos::ATOM)),
60+
'next_run_at' => CronExpression::factory($formatted)->getNextRunDate(),
6361
]);
6462

6563
$this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once();
@@ -77,35 +75,11 @@ public function testScheduleIsUpdatedAndRun()
7775
$this->assertTrue(true);
7876
}
7977

80-
public function testScheduleRunTimeCanBeOverridden()
81-
{
82-
$model = factory(Schedule::class)->make();
83-
$model->setRelation('tasks', collect([$task = factory(Task::class)->make([
84-
'sequence_id' => 1,
85-
])]));
86-
87-
$this->scheduleRepository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model);
88-
89-
$this->scheduleRepository->shouldReceive('update')->with($model->id, [
90-
'is_processing' => true,
91-
'next_run_at' => Chronos::now()->addSeconds(15),
92-
]);
93-
94-
$this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once();
95-
96-
$this->dispatcher->shouldReceive('dispatch')->with(m::on(function ($class) use ($model, $task) {
97-
$this->assertInstanceOf(RunTaskJob::class, $class);
98-
$this->assertSame($task->time_offset, $class->delay);
99-
$this->assertSame($task->id, $class->task);
100-
$this->assertSame($model->id, $class->schedule);
101-
102-
return true;
103-
}))->once();
104-
105-
$this->getService()->setRunTimeOverride(Chronos::now()->addSeconds(15))->handle($model);
106-
$this->assertTrue(true);
107-
}
108-
78+
/**
79+
* Return an instance of the service for testing purposes.
80+
*
81+
* @return \Pterodactyl\Services\Schedules\ProcessScheduleService
82+
*/
10983
private function getService(): ProcessScheduleService
11084
{
11185
return new ProcessScheduleService($this->dispatcher, $this->scheduleRepository, $this->taskRepository);

0 commit comments

Comments
 (0)