Correctly reset a schedule if there is an exception during the run stage; closes #2550
This commit is contained in:
parent
bffec5b3dc
commit
73b795faba
4 changed files with 71 additions and 15 deletions
|
@ -7,11 +7,12 @@ use Pterodactyl\Jobs\Job;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Pterodactyl\Models\Task;
|
use Pterodactyl\Models\Task;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use Pterodactyl\Models\Schedule;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
|
||||||
use Pterodactyl\Services\Backups\InitiateBackupService;
|
use Pterodactyl\Services\Backups\InitiateBackupService;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
|
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Schedules;
|
namespace Pterodactyl\Services\Schedules;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Pterodactyl\Models\Schedule;
|
use Pterodactyl\Models\Schedule;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Pterodactyl\Jobs\Schedule\RunTaskJob;
|
use Pterodactyl\Jobs\Schedule\RunTaskJob;
|
||||||
|
@ -60,8 +61,22 @@ class ProcessScheduleService
|
||||||
$task->update(['is_queued' => true]);
|
$task->update(['is_queued' => true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->dispatcher->{$now ? 'dispatchNow' : 'dispatch'}(
|
$job = new RunTaskJob($task);
|
||||||
(new RunTaskJob($task))->delay($task->time_offset)
|
|
||||||
);
|
if (! $now) {
|
||||||
|
$this->dispatcher->dispatch($job->delay($task->time_offset));
|
||||||
|
} else {
|
||||||
|
// When using dispatchNow the RunTaskJob::failed() function is not called automatically
|
||||||
|
// so we need to manually trigger it and then continue with the exception throw.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/2550
|
||||||
|
try {
|
||||||
|
$this->dispatcher->dispatchNow($job);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$job->failed($exception);
|
||||||
|
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ class ExecuteScheduleTest extends ClientApiIntegrationTestCase
|
||||||
$this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertStatus(Response::HTTP_ACCEPTED);
|
$this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertStatus(Response::HTTP_ACCEPTED);
|
||||||
|
|
||||||
Bus::assertDispatched(function (RunTaskJob $job) use ($task) {
|
Bus::assertDispatched(function (RunTaskJob $job) use ($task) {
|
||||||
$this->assertSame($task->time_offset, $job->delay);
|
// A task executed right now should not have any job delay associated with it.
|
||||||
|
$this->assertNull($job->delay);
|
||||||
$this->assertSame($task->id, $job->task->id);
|
$this->assertSame($task->id, $job->task->id);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
namespace Pterodactyl\Tests\Integration\Services\Schedules;
|
namespace Pterodactyl\Tests\Integration\Services\Schedules;
|
||||||
|
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
use Exception;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
use Pterodactyl\Models\Task;
|
use Pterodactyl\Models\Task;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Pterodactyl\Models\Schedule;
|
use Pterodactyl\Models\Schedule;
|
||||||
|
@ -61,7 +63,7 @@ class ProcessScheduleServiceTest extends IntegrationTestCase
|
||||||
*/
|
*/
|
||||||
public function testJobCanBeDispatchedWithExpectedInitialDelay($now)
|
public function testJobCanBeDispatchedWithExpectedInitialDelay($now)
|
||||||
{
|
{
|
||||||
$this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class));
|
Bus::fake();
|
||||||
|
|
||||||
$server = $this->createServerModel();
|
$server = $this->createServerModel();
|
||||||
|
|
||||||
|
@ -71,12 +73,17 @@ class ProcessScheduleServiceTest extends IntegrationTestCase
|
||||||
/** @var \Pterodactyl\Models\Task $task */
|
/** @var \Pterodactyl\Models\Task $task */
|
||||||
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'time_offset' => 10, 'sequence_id' => 1]);
|
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'time_offset' => 10, 'sequence_id' => 1]);
|
||||||
|
|
||||||
$dispatcher->expects($now ? 'dispatchNow' : 'dispatch')->with(Mockery::on(function (RunTaskJob $job) use ($task) {
|
|
||||||
return $task->id === $job->task->id && $job->delay === 10;
|
|
||||||
}));
|
|
||||||
|
|
||||||
$this->getService()->handle($schedule, $now);
|
$this->getService()->handle($schedule, $now);
|
||||||
|
|
||||||
|
Bus::assertDispatched(RunTaskJob::class, function ($job) use ($now, $task) {
|
||||||
|
$this->assertInstanceOf(RunTaskJob::class, $job);
|
||||||
|
$this->assertSame($task->id, $job->task->id);
|
||||||
|
// Jobs using dispatchNow should not have a delay associated with them.
|
||||||
|
$this->assertSame($now ? null : 10, $job->delay);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
$this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
|
$this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
|
||||||
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
|
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
|
||||||
}
|
}
|
||||||
|
@ -89,7 +96,7 @@ class ProcessScheduleServiceTest extends IntegrationTestCase
|
||||||
*/
|
*/
|
||||||
public function testFirstSequenceTaskIsFound()
|
public function testFirstSequenceTaskIsFound()
|
||||||
{
|
{
|
||||||
$this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class));
|
Bus::fake();
|
||||||
|
|
||||||
$server = $this->createServerModel();
|
$server = $this->createServerModel();
|
||||||
/** @var \Pterodactyl\Models\Schedule $schedule */
|
/** @var \Pterodactyl\Models\Schedule $schedule */
|
||||||
|
@ -100,18 +107,50 @@ class ProcessScheduleServiceTest extends IntegrationTestCase
|
||||||
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 2]);
|
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 2]);
|
||||||
$task3 = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 3]);
|
$task3 = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 3]);
|
||||||
|
|
||||||
$dispatcher->expects('dispatch')->with(Mockery::on(function (RunTaskJob $job) use ($task) {
|
|
||||||
return $task->id === $job->task->id;
|
|
||||||
}));
|
|
||||||
|
|
||||||
$this->getService()->handle($schedule);
|
$this->getService()->handle($schedule);
|
||||||
|
|
||||||
|
Bus::assertDispatched(RunTaskJob::class, function (RunTaskJob $job) use ($task) {
|
||||||
|
return $task->id === $job->task->id;
|
||||||
|
});
|
||||||
|
|
||||||
$this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
|
$this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
|
||||||
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
|
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
|
||||||
$this->assertDatabaseHas('tasks', ['id' => $task2->id, 'is_queued' => false]);
|
$this->assertDatabaseHas('tasks', ['id' => $task2->id, 'is_queued' => false]);
|
||||||
$this->assertDatabaseHas('tasks', ['id' => $task3->id, 'is_queued' => false]);
|
$this->assertDatabaseHas('tasks', ['id' => $task3->id, 'is_queued' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a task's processing state is reset correctly if using "dispatchNow" and there is
|
||||||
|
* an exception encountered while running it.
|
||||||
|
*
|
||||||
|
* @see https://github.com/pterodactyl/panel/issues/2550
|
||||||
|
*/
|
||||||
|
public function testTaskDispatchedNowIsResetProperlyIfErrorIsEncountered()
|
||||||
|
{
|
||||||
|
$this->swap(Dispatcher::class, $dispatcher = Mockery::mock(Dispatcher::class));
|
||||||
|
|
||||||
|
$server = $this->createServerModel();
|
||||||
|
/** @var \Pterodactyl\Models\Schedule $schedule */
|
||||||
|
$schedule = factory(Schedule::class)->create(['server_id' => $server->id, 'last_run_at' => null]);
|
||||||
|
/** @var \Pterodactyl\Models\Task $task */
|
||||||
|
$task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 1]);
|
||||||
|
|
||||||
|
$dispatcher->expects('dispatchNow')->andThrows(new Exception('Test thrown exception'));
|
||||||
|
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('Test thrown exception');
|
||||||
|
|
||||||
|
$this->getService()->handle($schedule, true);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('schedules', [
|
||||||
|
'id' => $schedule->id,
|
||||||
|
'is_processing' => false,
|
||||||
|
'last_run_at' => CarbonImmutable::now()->toAtomString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue