Fix schedules running twice, closes #1288
This commit is contained in:
		
							parent
							
								
									413a22a3d5
								
							
						
					
					
						commit
						5bd3f59455
					
				| @ -3,6 +3,10 @@ This file is a running track of new features and fixes to each version of the pa | |||||||
| 
 | 
 | ||||||
| This project follows [Semantic Versioning](http://semver.org) guidelines. | This project follows [Semantic Versioning](http://semver.org) guidelines. | ||||||
| 
 | 
 | ||||||
|  | ## v0.7.10 (Derelict Dermodactylus) | ||||||
|  | ### Fixed | ||||||
|  | * Scheduled tasks triggered manually no longer improperly change the `next_run_at` time and do not run twice in a row anymore. | ||||||
|  | 
 | ||||||
| ## v0.7.9 (Derelict Dermodactylus) | ## v0.7.9 (Derelict Dermodactylus) | ||||||
| ### Fixed | ### Fixed | ||||||
| * Fixes a two-factor authentication bypass present in the password reset process for an account. | * Fixes a two-factor authentication bypass present in the password reset process for an account. | ||||||
|  | |||||||
| @ -57,6 +57,11 @@ class ProcessRunnableCommand extends Command | |||||||
|     public function handle() |     public function handle() | ||||||
|     { |     { | ||||||
|         $schedules = $this->repository->getSchedulesToProcess(Chronos::now()->toAtomString()); |         $schedules = $this->repository->getSchedulesToProcess(Chronos::now()->toAtomString()); | ||||||
|  |         if ($schedules->count() < 1) { | ||||||
|  |             $this->line('There are no scheduled tasks for servers that need to be run.'); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         $bar = $this->output->createProgressBar(count($schedules)); |         $bar = $this->output->createProgressBar(count($schedules)); | ||||||
|         $schedules->each(function ($schedule) use ($bar) { |         $schedules->each(function ($schedule) use ($bar) { | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| namespace Pterodactyl\Http\Controllers\Server\Tasks; | namespace Pterodactyl\Http\Controllers\Server\Tasks; | ||||||
| 
 | 
 | ||||||
| use Cake\Chronos\Chronos; |  | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Illuminate\Http\Response; | use Illuminate\Http\Response; | ||||||
| use Pterodactyl\Http\Controllers\Controller; | use Pterodactyl\Http\Controllers\Controller; | ||||||
| @ -71,7 +70,7 @@ class ActionController extends Controller | |||||||
|         $server = $request->attributes->get('server'); |         $server = $request->attributes->get('server'); | ||||||
|         $this->authorize('toggle-schedule', $server); |         $this->authorize('toggle-schedule', $server); | ||||||
| 
 | 
 | ||||||
|         $this->processScheduleService->setRunTimeOverride(Chronos::now())->handle( |         $this->processScheduleService->handle( | ||||||
|             $request->attributes->get('schedule') |             $request->attributes->get('schedule') | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -75,6 +75,7 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor | |||||||
|     { |     { | ||||||
|         return $this->getBuilder()->with('tasks') |         return $this->getBuilder()->with('tasks') | ||||||
|             ->where('is_active', true) |             ->where('is_active', true) | ||||||
|  |             ->where('is_processing', false) | ||||||
|             ->where('next_run_at', '<=', $timestamp) |             ->where('next_run_at', '<=', $timestamp) | ||||||
|             ->get($this->getColumns()); |             ->get($this->getColumns()); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,11 +2,8 @@ | |||||||
| 
 | 
 | ||||||
| namespace Pterodactyl\Services\Schedules; | namespace Pterodactyl\Services\Schedules; | ||||||
| 
 | 
 | ||||||
| use DateTimeInterface; |  | ||||||
| use Cron\CronExpression; | use Cron\CronExpression; | ||||||
| use Cake\Chronos\Chronos; |  | ||||||
| use Pterodactyl\Models\Schedule; | use Pterodactyl\Models\Schedule; | ||||||
| use Cake\Chronos\ChronosInterface; |  | ||||||
| use Illuminate\Contracts\Bus\Dispatcher; | use Illuminate\Contracts\Bus\Dispatcher; | ||||||
| use Pterodactyl\Jobs\Schedule\RunTaskJob; | use Pterodactyl\Jobs\Schedule\RunTaskJob; | ||||||
| use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; | use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; | ||||||
| @ -19,11 +16,6 @@ class ProcessScheduleService | |||||||
|      */ |      */ | ||||||
|     private $dispatcher; |     private $dispatcher; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @var \DateTimeInterface|null |  | ||||||
|      */ |  | ||||||
|     private $runTimeOverride; |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface |      * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface | ||||||
|      */ |      */ | ||||||
| @ -51,20 +43,6 @@ class ProcessScheduleService | |||||||
|         $this->taskRepository = $taskRepository; |         $this->taskRepository = $taskRepository; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Set the time that this schedule should be run at. This will override the time |  | ||||||
|      * defined on the schedule itself. Useful for triggering one-off task runs. |  | ||||||
|      * |  | ||||||
|      * @param \DateTimeInterface $time |  | ||||||
|      * @return $this |  | ||||||
|      */ |  | ||||||
|     public function setRunTimeOverride(DateTimeInterface $time) |  | ||||||
|     { |  | ||||||
|         $this->runTimeOverride = $time; |  | ||||||
| 
 |  | ||||||
|         return $this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Process a schedule and push the first task onto the queue worker. |      * Process a schedule and push the first task onto the queue worker. | ||||||
|      * |      * | ||||||
| @ -89,7 +67,7 @@ class ProcessScheduleService | |||||||
| 
 | 
 | ||||||
|         $this->scheduleRepository->update($schedule->id, [ |         $this->scheduleRepository->update($schedule->id, [ | ||||||
|             'is_processing' => true, |             'is_processing' => true, | ||||||
|             'next_run_at' => $this->getRunAtTime($formattedCron), |             'next_run_at' => CronExpression::factory($formattedCron)->getNextRunDate(), | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         $this->taskRepository->update($task->id, ['is_queued' => true]); |         $this->taskRepository->update($task->id, ['is_queued' => true]); | ||||||
| @ -98,19 +76,4 @@ class ProcessScheduleService | |||||||
|             (new RunTaskJob($task->id, $schedule->id))->delay($task->time_offset) |             (new RunTaskJob($task->id, $schedule->id))->delay($task->time_offset) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the timestamp to store in the database as the next_run time for a schedule. |  | ||||||
|      * |  | ||||||
|      * @param string $formatted |  | ||||||
|      * @return \Cake\Chronos\ChronosInterface |  | ||||||
|      */ |  | ||||||
|     private function getRunAtTime(string $formatted): ChronosInterface |  | ||||||
|     { |  | ||||||
|         if (! is_null($this->runTimeOverride)) { |  | ||||||
|             return $this->runTimeOverride instanceof ChronosInterface ? $this->runTimeOverride : Chronos::instance($this->runTimeOverride); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return Chronos::instance(CronExpression::factory($formatted)->getNextRunDate()); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,6 @@ namespace Tests\Unit\Services\Schedules; | |||||||
| use Mockery as m; | use Mockery as m; | ||||||
| use Tests\TestCase; | use Tests\TestCase; | ||||||
| use Cron\CronExpression; | use Cron\CronExpression; | ||||||
| use Cake\Chronos\Chronos; |  | ||||||
| use Pterodactyl\Models\Task; | use Pterodactyl\Models\Task; | ||||||
| use Pterodactyl\Models\Schedule; | use Pterodactyl\Models\Schedule; | ||||||
| use Illuminate\Contracts\Bus\Dispatcher; | use Illuminate\Contracts\Bus\Dispatcher; | ||||||
| @ -38,7 +37,6 @@ class ProcessScheduleServiceTest extends TestCase | |||||||
|     { |     { | ||||||
|         parent::setUp(); |         parent::setUp(); | ||||||
| 
 | 
 | ||||||
|         Chronos::setTestNow(Chronos::now()); |  | ||||||
|         $this->dispatcher = m::mock(Dispatcher::class); |         $this->dispatcher = m::mock(Dispatcher::class); | ||||||
|         $this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class); |         $this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class); | ||||||
|         $this->taskRepository = m::mock(TaskRepositoryInterface::class); |         $this->taskRepository = m::mock(TaskRepositoryInterface::class); | ||||||
| @ -59,7 +57,7 @@ class ProcessScheduleServiceTest extends TestCase | |||||||
|         $formatted = sprintf('%s %s %s * %s', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week); |         $formatted = sprintf('%s %s %s * %s', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week); | ||||||
|         $this->scheduleRepository->shouldReceive('update')->with($model->id, [ |         $this->scheduleRepository->shouldReceive('update')->with($model->id, [ | ||||||
|             'is_processing' => true, |             'is_processing' => true, | ||||||
|             'next_run_at' => Chronos::parse(CronExpression::factory($formatted)->getNextRunDate()->format(Chronos::ATOM)), |             'next_run_at' => CronExpression::factory($formatted)->getNextRunDate(), | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once(); |         $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once(); | ||||||
| @ -77,35 +75,11 @@ class ProcessScheduleServiceTest extends TestCase | |||||||
|         $this->assertTrue(true); |         $this->assertTrue(true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function testScheduleRunTimeCanBeOverridden() |     /** | ||||||
|     { |      * Return an instance of the service for testing purposes. | ||||||
|         $model = factory(Schedule::class)->make(); |      * | ||||||
|         $model->setRelation('tasks', collect([$task = factory(Task::class)->make([ |      * @return \Pterodactyl\Services\Schedules\ProcessScheduleService | ||||||
|             'sequence_id' => 1, |      */ | ||||||
|         ])])); |  | ||||||
| 
 |  | ||||||
|         $this->scheduleRepository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model); |  | ||||||
| 
 |  | ||||||
|         $this->scheduleRepository->shouldReceive('update')->with($model->id, [ |  | ||||||
|             'is_processing' => true, |  | ||||||
|             'next_run_at' => Chronos::now()->addSeconds(15), |  | ||||||
|         ]); |  | ||||||
| 
 |  | ||||||
|         $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once(); |  | ||||||
| 
 |  | ||||||
|         $this->dispatcher->shouldReceive('dispatch')->with(m::on(function ($class) use ($model, $task) { |  | ||||||
|             $this->assertInstanceOf(RunTaskJob::class, $class); |  | ||||||
|             $this->assertSame($task->time_offset, $class->delay); |  | ||||||
|             $this->assertSame($task->id, $class->task); |  | ||||||
|             $this->assertSame($model->id, $class->schedule); |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }))->once(); |  | ||||||
| 
 |  | ||||||
|         $this->getService()->setRunTimeOverride(Chronos::now()->addSeconds(15))->handle($model); |  | ||||||
|         $this->assertTrue(true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function getService(): ProcessScheduleService |     private function getService(): ProcessScheduleService | ||||||
|     { |     { | ||||||
|         return new ProcessScheduleService($this->dispatcher, $this->scheduleRepository, $this->taskRepository); |         return new ProcessScheduleService($this->dispatcher, $this->scheduleRepository, $this->taskRepository); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dane Everitt
						Dane Everitt