Skip to content

Commit 73b795f

Browse files
committed
Correctly reset a schedule if there is an exception during the run stage; closes pterodactyl#2550
1 parent bffec5b commit 73b795f

File tree

4 files changed

+71
-15
lines changed

4 files changed

+71
-15
lines changed

app/Jobs/Schedule/RunTaskJob.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
use Carbon\CarbonImmutable;
88
use Pterodactyl\Models\Task;
99
use InvalidArgumentException;
10+
use Pterodactyl\Models\Schedule;
11+
use Illuminate\Support\Facades\Log;
1012
use Illuminate\Queue\SerializesModels;
1113
use Illuminate\Queue\InteractsWithQueue;
1214
use Illuminate\Contracts\Queue\ShouldQueue;
1315
use Illuminate\Foundation\Bus\DispatchesJobs;
14-
use Pterodactyl\Repositories\Eloquent\TaskRepository;
1516
use Pterodactyl\Services\Backups\InitiateBackupService;
1617
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
1718
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;

app/Services/Schedules/ProcessScheduleService.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pterodactyl\Services\Schedules;
44

5+
use Exception;
56
use Pterodactyl\Models\Schedule;
67
use Illuminate\Contracts\Bus\Dispatcher;
78
use Pterodactyl\Jobs\Schedule\RunTaskJob;
@@ -60,8 +61,22 @@ public function handle(Schedule $schedule, bool $now = false)
6061
$task->update(['is_queued' => true]);
6162
});
6263

63-
$this->dispatcher->{$now ? 'dispatchNow' : 'dispatch'}(
64-
(new RunTaskJob($task))->delay($task->time_offset)
65-
);
64+
$job = new RunTaskJob($task);
65+
66+
if (! $now) {
67+
$this->dispatcher->dispatch($job->delay($task->time_offset));
68+
} else {
69+
// When using dispatchNow the RunTaskJob::failed() function is not called automatically
70+
// so we need to manually trigger it and then continue with the exception throw.
71+
//
72+
// @see https://github.com/pterodactyl/panel/issues/2550
73+
try {
74+
$this->dispatcher->dispatchNow($job);
75+
} catch (Exception $exception) {
76+
$job->failed($exception);
77+
78+
throw $exception;
79+
}
80+
}
6681
}
6782
}

tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public function testScheduleIsExecutedRightAway(array $permissions)
4444
$this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertStatus(Response::HTTP_ACCEPTED);
4545

4646
Bus::assertDispatched(function (RunTaskJob $job) use ($task) {
47-
$this->assertSame($task->time_offset, $job->delay);
47+
// A task executed right now should not have any job delay associated with it.
48+
$this->assertNull($job->delay);
4849
$this->assertSame($task->id, $job->task->id);
4950

5051
return true;

tests/Integration/Services/Schedules/ProcessScheduleServiceTest.php

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Pterodactyl\Tests\Integration\Services\Schedules;
44

55
use Mockery;
6+
use Exception;
7+
use Carbon\CarbonImmutable;
68
use Pterodactyl\Models\Task;
79
use InvalidArgumentException;
810
use Pterodactyl\Models\Schedule;
@@ -61,7 +63,7 @@ public function testErrorDuringScheduleDataUpdateDoesNotPersistChanges()
6163
*/
6264
public function testJobCanBeDispatchedWithExpectedInitialDelay($now)
6365
{
64-
$this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class));
66+
Bus::fake();
6567

6668
$server = $this->createServerModel();
6769

@@ -71,12 +73,17 @@ public function testJobCanBeDispatchedWithExpectedInitialDelay($now)
7173
/** @var \Pterodactyl\Models\Task $task */
7274
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'time_offset' => 10, 'sequence_id' => 1]);
7375

74-
$dispatcher->expects($now ? 'dispatchNow' : 'dispatch')->with(Mockery::on(function (RunTaskJob $job) use ($task) {
75-
return $task->id === $job->task->id && $job->delay === 10;
76-
}));
77-
7876
$this->getService()->handle($schedule, $now);
7977

78+
Bus::assertDispatched(RunTaskJob::class, function ($job) use ($now, $task) {
79+
$this->assertInstanceOf(RunTaskJob::class, $job);
80+
$this->assertSame($task->id, $job->task->id);
81+
// Jobs using dispatchNow should not have a delay associated with them.
82+
$this->assertSame($now ? null : 10, $job->delay);
83+
84+
return true;
85+
});
86+
8087
$this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
8188
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
8289
}
@@ -89,7 +96,7 @@ public function testJobCanBeDispatchedWithExpectedInitialDelay($now)
8996
*/
9097
public function testFirstSequenceTaskIsFound()
9198
{
92-
$this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class));
99+
Bus::fake();
93100

94101
$server = $this->createServerModel();
95102
/** @var \Pterodactyl\Models\Schedule $schedule */
@@ -100,18 +107,50 @@ public function testFirstSequenceTaskIsFound()
100107
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 2]);
101108
$task3 = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 3]);
102109

103-
$dispatcher->expects('dispatch')->with(Mockery::on(function (RunTaskJob $job) use ($task) {
104-
return $task->id === $job->task->id;
105-
}));
106-
107110
$this->getService()->handle($schedule);
108111

112+
Bus::assertDispatched(RunTaskJob::class, function (RunTaskJob $job) use ($task) {
113+
return $task->id === $job->task->id;
114+
});
115+
109116
$this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
110117
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
111118
$this->assertDatabaseHas('tasks', ['id' => $task2->id, 'is_queued' => false]);
112119
$this->assertDatabaseHas('tasks', ['id' => $task3->id, 'is_queued' => false]);
113120
}
114121

122+
/**
123+
* Tests that a task's processing state is reset correctly if using "dispatchNow" and there is
124+
* an exception encountered while running it.
125+
*
126+
* @see https://github.com/pterodactyl/panel/issues/2550
127+
*/
128+
public function testTaskDispatchedNowIsResetProperlyIfErrorIsEncountered()
129+
{
130+
$this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class));
131+
132+
$server = $this->createServerModel();
133+
/** @var \Pterodactyl\Models\Schedule $schedule */
134+
$schedule = factory(Schedule::class)->create(['server_id' => $server->id, 'last_run_at' => null]);
135+
/** @var \Pterodactyl\Models\Task $task */
136+
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 1]);
137+
138+
$dispatcher->expects('dispatchNow')->andThrows(new Exception('Test thrown exception'));
139+
140+
$this->expectException(Exception::class);
141+
$this->expectExceptionMessage('Test thrown exception');
142+
143+
$this->getService()->handle($schedule, true);
144+
145+
$this->assertDatabaseHas('schedules', [
146+
'id' => $schedule->id,
147+
'is_processing' => false,
148+
'last_run_at' => CarbonImmutable::now()->toAtomString(),
149+
]);
150+
151+
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => false]);
152+
}
153+
115154
/**
116155
* @return array
117156
*/

0 commit comments

Comments
 (0)