Instantly share code, notes, and snippets.

Embed
What would you like to do?
Yii2 console controller behavior. Prevents double run console command
<?php
namespace console\components;
use Throwable;
use yii\base\Action;
class CronException extends \RuntimeException
{
/**
* @var string
*/
public $actionId;
/**
* @var string
*/
public $since;
public function __construct(
string $failType,
Action $action,
string $lockFileName,
int $code = 0,
Throwable $previous = null
)
{
$this->actionId = $action->uniqueId;
if (file_exists($lockFileName)) {
$this->since = date('r', filemtime($lockFileName));
}
parent::__construct($this->actionId . ' ' . $failType, $code, $previous);
}
}
<?php
namespace console\components;
use Yii;
use yii\base\ActionEvent;
use yii\base\Behavior;
use yii\base\Exception;
use yii\console\Controller;
/**
* yii\console\Controller behavior
* Prevents double run console command
*/
class OneInstance extends Behavior
{
/**
* @var string
*/
protected $lockFileName;
/**
* If process executes more than $longRunningTimeout seconds,
* it will be considered as long-running
* and warning message will be logged
* @var int seconds
*/
public $longRunningTimeout = 3600; // 1h
/**
* Start command if there isn't another active instance. Even last run was unsuccessfully completed
* @var bool
*/
public $nonFailureShutdown = false;
public function events()
{
return [
Controller::EVENT_BEFORE_ACTION => 'checkInstance',
Controller::EVENT_AFTER_ACTION => 'cleanUp',
];
}
/**
* @param ActionEvent $event
* @throws Exception
*/
public function checkInstance(ActionEvent $event): void
{
$this->lockFileName = Yii::$app->runtimePath . '/' . str_replace('/', '-', $event->action->uniqueId) . '.lock';
if (file_exists($this->lockFileName)) {
$processExists = file_exists('/proc/' . trim(file_get_contents($this->lockFileName)));
if ($processExists) {
$event->isValid = false;
if (filemtime($this->lockFileName) + $this->longRunningTimeout < time()) {
throw new CronException('fall asleep', $event->action, $this->lockFileName);
}
return;
}
if ($this->nonFailureShutdown) {
@unlink($this->lockFileName);
} else {
$event->isValid = false;
throw new CronException('failed', $event->action, $this->lockFileName);
}
}
if (!file_put_contents($this->lockFileName, posix_getpid())) {
throw new CronException('Can\'t write lock file', $event->action, $this->lockFileName);
}
}
/**
* @param ActionEvent $event
*/
public function cleanUp($event)
{
if ($event->result == 0) {
@unlink($this->lockFileName);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment