Exemple #1
0
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $jobId = $input->getArgument('jobId');
     $this->init($jobId);
     // Ensure that job status is 'terminating'
     if ($this->job->getStatus() != Job::STATUS_TERMINATING) {
         $this->job->setStatus(Job::STATUS_TERMINATING);
         $this->jobMapper->update($this->job);
     }
     /** @var ExecutorInterface $jobExecutor */
     $jobExecutor = $this->getContainer()->get('syrup.job_executor_factory')->create();
     // run cleanup
     $jobExecutor->cleanup($this->job);
     // Update job
     $endTime = time();
     $duration = $endTime - strtotime($this->job->getStartTime());
     $this->job->setStatus(Job::STATUS_TERMINATED);
     $this->job->setResult(['message' => 'Job has been terminated']);
     $this->job->setEndTime(date('c', $endTime));
     $this->job->setDurationSeconds($duration);
     $this->jobMapper->update($this->job);
     // run post-cleanup
     $jobExecutor->postCleanup($this->job);
     // DB unlock
     $this->lock->unlock();
 }
 public static function create(Lock $lock, callable $retryCallback, callable $killCallback, callable $successCallback, callable $errorCallback)
 {
     return function ($command, QueueMessage $message, Job $job) use($lock, $retryCallback, $killCallback, $successCallback, $errorCallback) {
         if (!$lock->lock()) {
             $retryCallback($message, "DB is locked");
         }
         $process = new Process($command);
         $process->setTimeout(86400);
         $process->setIdleTimeout(null);
         try {
             $process->run();
         } catch (ProcessTimedOutException $e) {
             $errorCallback($job, "Process timed out");
         } catch (RuntimeException $e) {
             // process has been signaled or some other error occurred, handled down in code
         }
         switch ($process->getExitCode()) {
             case JobCommand::STATUS_RETRY:
                 $message->incrementRetries();
                 if ($message->getRetryCount() > self::MAX_EXECUTION_RETRIES) {
                     $errorCallback($job, "Retries exceeded");
                     break;
                 }
                 $retryCallback($message);
                 break;
             case 137:
             case 143:
                 // process has been signaled
                 $killCallback($job);
                 break;
             case JobCommand::STATUS_SUCCESS:
                 $successCallback();
                 break;
             case JobCommand::STATUS_ERROR:
             default:
                 $errorCallback($job, $process->getOutput());
                 break;
         }
         return $process;
     };
 }
Exemple #3
0
 /**
  * @covers \Keboola\Syrup\Service\Db\Lock::lock
  * @covers \Keboola\Syrup\Service\Db\Lock::isFree
  * @covers \Keboola\Syrup\Service\Db\Lock::unlock
  */
 public function testLocks()
 {
     $connectionParams = ['host' => SYRUP_DATABASE_HOST, 'dbname' => SYRUP_DATABASE_NAME, 'user' => SYRUP_DATABASE_USER, 'password' => SYRUP_DATABASE_PASSWORD, 'port' => SYRUP_DATABASE_PORT, 'driver' => 'pdo_mysql', 'charset' => 'utf8'];
     $db1 = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
     $db1->exec('SET wait_timeout = 31536000;');
     $db2 = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
     $db2->exec('SET wait_timeout = 31536000;');
     $lockName = uniqid();
     $lock1 = new Lock($db1, $lockName);
     $lock2 = new Lock($db2, $lockName);
     $this->assertTrue($lock1->lock(), 'Should successfully lock');
     $this->assertFalse($lock1->isFree(), 'Should tell lock not free');
     $this->assertFalse($lock2->isFree(), 'Should tell lock not free');
     $this->assertFalse($lock2->lock(), 'Should fail locking');
     $this->assertTrue($lock1->unlock(), 'Should successfully unlock');
     $this->assertTrue($lock2->lock(), 'Should successfully lock');
     $this->assertFalse($lock2->isFree(), 'Should tell lock not free');
     $this->assertFalse($lock1->isFree(), 'Should tell lock not free');
     $this->assertFalse($lock1->lock(), 'Should fail locking');
     $this->assertTrue($lock2->unlock(), 'Should successfully unlock');
 }
Exemple #4
0
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $jobId = $input->getArgument('jobId');
     $this->logger = $this->getContainer()->get('logger');
     try {
         $this->init($jobId);
     } catch (\Exception $e) {
         // Initialization error -> job will be requeued
         $this->logException('error', $e);
         // Don't update job status or result -> error could be related to ES
         // Don't unlock DB, error happened either before lock creation or when creating the lock,
         // so the DB isn't locked
         return self::STATUS_RETRY;
     }
     if (is_null($jobId)) {
         throw new UserException("Missing jobId argument.");
     }
     if (!$this->lock->lock()) {
         return self::STATUS_LOCK;
     }
     /** @var Connection $checkConn */
     $checkConn = null;
     /** @var Lock $validationLock */
     $validationLock = null;
     if (StorageApiLimits::hasParallelLimit($this->storageApiService->getTokenData())) {
         try {
             $checkConn = $this->getContainer()->get('doctrine.dbal.limit_lock_connection');
             $checkConn->exec('SET wait_timeout = 31536000;');
             $validationLock = new Lock($checkConn, sprintf('syrup-%s-job-limit-check', $this->job->getProject()['id']));
             if (!$validationLock->lock(self::PARALLEL_LIMIT_LOCK_TIMEOUT)) {
                 throw new \RuntimeException('Could not lock for parallel validation');
             }
             if ($this->isParallelLimitExceeded()) {
                 throw new \RuntimeException('Exceeded parallel processing limit');
             }
         } catch (\RuntimeException $e) {
             return self::STATUS_LOCK;
         }
     }
     // check job status
     $this->job = $this->jobMapper->get($jobId);
     if (!in_array($this->job->getStatus(), [Job::STATUS_WAITING, Job::STATUS_PROCESSING])) {
         // job is not waiting or processing
         return self::STATUS_LOCK;
     }
     $startTime = time();
     // Update job status to 'processing'
     $this->job->setStatus(Job::STATUS_PROCESSING);
     $this->job->setStartTime(date('c', $startTime));
     $this->job->setEndTime(null);
     $this->job->setDurationSeconds(null);
     $this->job->setResult(null);
     $this->job->setProcess(['host' => gethostname(), 'pid' => getmypid()]);
     /** @var ExecutorInterface $jobExecutor */
     $jobExecutor = $this->getContainer()->get('syrup.job_executor_factory')->create();
     // update the job status after jobExecutor was created, so the signal handler is properly registered
     $this->jobMapper->update($this->job);
     // Execute job
     try {
         if (StorageApiLimits::hasParallelLimit($this->storageApiService->getTokenData())) {
             $validationLock->unlock();
             $checkConn->close();
         }
         $jobResult = $jobExecutor->execute($this->job);
         $jobStatus = Job::STATUS_SUCCESS;
         $status = self::STATUS_SUCCESS;
     } catch (InitializationException $e) {
         // job will be requeued
         $exceptionId = $this->logException('error', $e);
         $jobResult = ['message' => $e->getMessage(), 'exceptionId' => $exceptionId];
         $jobStatus = Job::STATUS_PROCESSING;
         $status = self::STATUS_RETRY;
     } catch (MaintenanceException $e) {
         $jobResult = [];
         $jobStatus = Job::STATUS_WAITING;
         $status = self::STATUS_LOCK;
     } catch (UserException $e) {
         $exceptionId = $this->logException('error', $e);
         $jobResult = ['message' => $e->getMessage(), 'exceptionId' => $exceptionId];
         $jobStatus = Job::STATUS_ERROR;
         $this->job->setError(Job::ERROR_USER);
         $status = self::STATUS_SUCCESS;
     } catch (JobException $e) {
         $logLevel = 'error';
         if ($e->getStatus() === Job::STATUS_WARNING) {
             $logLevel = Job::STATUS_WARNING;
         }
         $exceptionId = $this->logException($logLevel, $e);
         $jobResult = ['message' => $e->getMessage(), 'exceptionId' => $exceptionId];
         if ($e->getResult()) {
             $jobResult += $e->getResult();
         }
         $jobStatus = $e->getStatus();
         $status = self::STATUS_SUCCESS;
     } catch (\Exception $e) {
         // make sure that the job is recorded as failed
         $jobStatus = Job::STATUS_ERROR;
         $jobResult = ['message' => 'Internal error occurred, evaluating details'];
         $this->job->setStatus($jobStatus);
         $this->job->setResult($jobResult);
         $this->job->setError(Job::ERROR_APPLICATION);
         // try to log the exception
         $exceptionId = $this->logException('critical', $e);
         $jobResult = ['message' => 'Internal error occurred, please contact support@keboola.com', 'exceptionId' => $exceptionId];
         $status = self::STATUS_ERROR;
     }
     // Update job with results
     $endTime = time();
     $createdTime = $this->job->getCreatedTime();
     $waitSeconds = is_null($createdTime) ?: $startTime - strtotime($createdTime);
     $this->job->setStatus($jobStatus);
     $this->job->setResult($jobResult);
     $this->job->setEndTime(date('c', $endTime));
     $this->job->setDurationSeconds($endTime - $startTime);
     $this->job->setWaitSeconds($waitSeconds);
     $this->jobMapper->update($this->job);
     // postExecution action
     try {
         $jobExecutor->postExecute($this->job);
         //@todo: remove call to this deprecated interface method
         if ($jobExecutor instanceof HookExecutorInterface) {
             /** @var HookExecutorInterface $jobExecutor */
             $jobExecutor->postExecution($this->job);
         }
     } catch (\Exception $e) {
         $this->logException('critical', $e);
     }
     // DB unlock
     $this->lock->unlock();
     return $status;
 }