Merge pull request #51 from ss-gxp/fix-builder

Fix build execute - in some cases one build executed with many process
This commit is contained in:
Dmitry Khomutov 2017-04-05 22:56:46 +07:00 committed by GitHub
commit 0109e49f8e
6 changed files with 120 additions and 27 deletions

View file

@ -175,8 +175,16 @@ class Builder implements LoggerAwareInterface
*/
public function execute()
{
// check current status
if ($this->build->getStatus() != Build::STATUS_PENDING) {
throw new BuilderException('Can`t build - status is not pending', BuilderException::FAIL_START);
}
// set status only if current status pending
if (!$this->build->setStatusSync(Build::STATUS_RUNNING)) {
throw new BuilderException('Can`t build - unable change status to running', BuilderException::FAIL_START);
}
// Update the build in the database, ping any external services.
$this->build->setStatus(Build::STATUS_RUNNING);
$this->build->setStarted(new \DateTime());
$this->store->save($this->build);
$this->build->sendStatusPostback();

View file

@ -0,0 +1,8 @@
<?php
namespace PHPCensor;
class BuilderException extends \Exception {
/** Fail start build - non fatal */
const FAIL_START = 1;
}

View file

@ -13,6 +13,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use b8\Store\Factory;
use PHPCensor\Builder;
use PHPCensor\BuilderException;
use PHPCensor\BuildFactory;
use PHPCensor\Model\Build;
@ -95,31 +96,36 @@ class RunCommand extends Command
$build = BuildFactory::getBuild($build);
// Skip build (for now) if there's already a build running in that project:
if (in_array($build->getProjectId(), $running)) {
if (!empty($running[$build->getProjectId()])) {
$this->logger->addInfo(sprintf('Skipping Build %d - Project build already in progress.', $build->getId()));
$result['items'][] = $build;
// Re-run build validator:
$running = $this->validateRunningBuilds();
continue;
}
$builds++;
try {
// Logging relevant to this build should be stored
// against the build itself.
$buildDbLog = new BuildDBLogHandler($build, Logger::INFO);
$this->logger->pushHandler($buildDbLog);
// Logging relevant to this build should be stored
// against the build itself.
$buildDbLog = new BuildDBLogHandler($build, Logger::INFO);
$this->logger->pushHandler($buildDbLog);
try {
$builder = new Builder($build, $this->logger);
$builder->execute();
// After execution we no longer want to record the information
// back to this specific build so the handler should be removed.
$this->logger->popHandler();
// destructor implicitly call flush
unset($buildDbLog);
} catch (BuilderException $ex) {
$this->logger->addError($ex->getMessage());
switch($ex->getCode()) {
case BuilderException::FAIL_START:
// non fatal
break;
default:
$build->setStatus(Build::STATUS_FAILED);
$build->setFinished(new \DateTime());
$build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage());
$store->save($build);
break;
}
} catch (\Exception $ex) {
$build->setStatus(Build::STATUS_FAILED);
$build->setFinished(new \DateTime());
@ -127,6 +133,14 @@ class RunCommand extends Command
$store->save($build);
}
// After execution we no longer want to record the information
// back to this specific build so the handler should be removed.
$this->logger->popHandler();
// destructor implicitly call flush
unset($buildDbLog);
// Re-run build validator:
$running = $this->validateRunningBuilds();
}
$this->logger->addInfo('Finished processing builds.');

View file

@ -410,6 +410,27 @@ class Build extends Model
$this->setModified('status');
}
/**
* Set the value of Status / status only if it synced with db. Must not be null.
*
* @param $value int
* @return bool
*/
public function setStatusSync($value)
{
$this->validateNotNull('Status', $value);
$this->validateInt('Status', $value);
if ($this->data['status'] !== $value) {
$store = Factory::getStore('Build');
if ($store->updateStatusSync($this, $value)) {
$this->data['status'] = $value;
return true;
}
}
return false;
}
/**
* Set the value of Log / log.
*

View file

@ -310,4 +310,24 @@ class BuildStore extends Store
return false;
}
}
/**
* Update status only if it synced with db
* @param Build $build
* @param int $status
* @return bool
*/
public function updateStatusSync($build, $status)
{
try {
$query = 'UPDATE {{build}} SET status = :status_new WHERE {{id}} = :id AND {{status}} = :status_current';
$stmt = Database::getConnection('write')->prepareCommon($query);
$stmt->bindValue(':id', $build->getId(), \PDO::PARAM_INT);
$stmt->bindValue(':status_current', $build->getStatus(), \PDO::PARAM_INT);
$stmt->bindValue(':status_new', $status, \PDO::PARAM_INT);
return ($stmt->execute() and ($stmt->rowCount() == 1));
} catch (\Exception $e) {
return false;
}
}
}

View file

@ -9,6 +9,7 @@ use Monolog\Logger;
use Pheanstalk\Job;
use Pheanstalk\Pheanstalk;
use PHPCensor\Builder;
use PHPCensor\BuilderException;
use PHPCensor\BuildFactory;
use PHPCensor\Logging\BuildDBLogHandler;
use PHPCensor\Model\Build;
@ -107,27 +108,40 @@ class BuildWorker
} catch (\Exception $ex) {
$this->logger->addWarning('Build #' . $jobData['build_id'] . ' does not exist in the database.');
$this->pheanstalk->delete($job);
continue;
}
try {
// Logging relevant to this build should be stored
// against the build itself.
$buildDbLog = new BuildDBLogHandler($build, Logger::INFO);
$this->logger->pushHandler($buildDbLog);
// Logging relevant to this build should be stored
// against the build itself.
$buildDbLog = new BuildDBLogHandler($build, Logger::INFO);
$this->logger->pushHandler($buildDbLog);
try {
$builder = new Builder($build, $this->logger);
$builder->execute();
// After execution we no longer want to record the information
// back to this specific build so the handler should be removed.
$this->logger->popHandler();
// destructor implicitly call flush
unset($buildDbLog);
} catch (BuilderException $ex) {
$this->logger->addError($ex->getMessage());
switch($ex->getCode()) {
case BuilderException::FAIL_START:
// non fatal
$this->pheanstalk->release($job);
unset($job);
break;
default:
$build->setStatus(Build::STATUS_FAILED);
$build->setFinished(new \DateTime());
$build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage());
$buildStore->save($build);
$build->sendStatusPostback();
break;
}
} catch (\PDOException $ex) {
// If we've caught a PDO Exception, it is probably not the fault of the build, but of a failed
// connection or similar. Release the job and kill the worker.
$this->run = false;
$this->pheanstalk->release($job);
unset($job);
} catch (\Exception $ex) {
$build->setStatus(Build::STATUS_FAILED);
$build->setFinished(new \DateTime());
@ -136,13 +150,21 @@ class BuildWorker
$build->sendStatusPostback();
}
// After execution we no longer want to record the information
// back to this specific build so the handler should be removed.
$this->logger->popHandler();
// destructor implicitly call flush
unset($buildDbLog);
// Reset the config back to how it was prior to running this job:
if (!empty($currentConfig)) {
Database::reset();
}
// Delete the job when we're done:
$this->pheanstalk->delete($job);
if (!empty($job)) {
$this->pheanstalk->delete($job);
}
}
}