8000 [Console] Add 'auto lockable' behaviour · symfony/symfony@ca06975 · GitHub
[go: up one dir, main page]

Skip to content

Commit ca06975

Browse files
[Console] Add 'auto lockable' behaviour
1 parent 5c91f6e commit ca06975

File tree

6 files changed

+126
-0
lines changed

6 files changed

+126
-0
lines changed

src/Symfony/Component/Console/Command/Command.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Console\Command;
1313

1414
use Symfony\Component\Console\Exception\ExceptionInterface;
15+
use Symfony\Component\Console\Exception\RuntimeException;
1516
use Symfony\Component\Console\Input\InputDefinition;
1617
use Symfony\Component\Console\Input\InputOption;
1718
use Symfony\Component\Console\Input\InputArgument;
@@ -21,6 +22,7 @@
2122
use Symfony\Component\Console\Helper\HelperSet;
2223
use Symfony\Component\Console\Exception\InvalidArgumentException;
2324
use Symfony\Component\Console\Exception\LogicException;
25+
use Symfony\Component\Filesystem\LockHandler;
2426

2527
/**
2628
* Base class for all commands.
@@ -43,6 +45,7 @@ class Command
4345
private $synopsis = array();
4446
private $usages = array();
4547
private $helperSet;
48+
private $lock;
4649

4750
/**
4851
* Constructor.
@@ -256,6 +259,8 @@ public function run(InputInterface $input, OutputInterface $output)
256259
$statusCode = $this->execute($input, $output);
257260
}
258261

262+
$this->release();
263+
259264
return is_numeric($statusCode) ? (int) $statusCode : 0;
260265
}
261266

@@ -628,4 +633,46 @@ private function validateName($name)
628633
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
629634
}
630635
}
636+
637+
/**
638+
* Locks a command.
639+
*
640+
* @param bool $silent
641+
*
642+
* @return bool
643+
*
644+
* @throws RuntimeException
645+
*/
646+
protected function lock($silent = false)
647+
{
648+
if (!class_exists('Symfony\Component\Filesystem\LockHandler')) {
649+
throw new RuntimeException('To enable the locking feature you must install the symfony/filesystem component');
650+
}
651+
652+
$this->lock = new LockHandler($this->getName());
653+
654+
if (!$this->lock->lock()) {
655+
if ($silent) {
656+
return false;
657+
}
658+
659+
throw new RuntimeException(sprintf(
660+
'<error>The command "%s" is already running in another process.</error>',
661+
$this->getName()
662+
));
663+
}
664+
665+
return true;
666+
}
667+
668+
/**
669+
* Releases the command lock if there is one.
670+
*/
671+
protected function release()
672+
{
673+
if ($this->lock) {
674+
$this->lock->release();
675+
$this->lock = null;
676+
}
677+
}
631678
}

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public static function setUpBeforeClass()
4343
require_once self::$fixturesPath.'/Foo3Command.php';
4444
require_once self::$fixturesPath.'/Foo4Command.php';
4545
require_once self::$fixturesPath.'/Foo5Command.php';
46+
require_once self::$fixturesPath.'/FooLock1Command.php';
47+
require_once self::$fixturesPath.'/FooLock2Command.php';
4648
require_once self::$fixturesPath.'/FoobarCommand.php';
4749
require_once self::$fixturesPath.'/BarBucCommand.php';
4850
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';

src/Symfony/Component/Console/Tests/Command/CommandTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Console\Output\OutputInterface;
2323
use Symfony\Component\Console\Output\NullOutput;
2424
use Symfony\Component\Console\Tester\CommandTester;
25+
use Symfony\Component\Filesystem\LockHandler;
2526

2627
class CommandTest extends \PHPUnit_Framework_TestCase
2728
{
@@ -348,6 +349,45 @@ public function callableMethodCommand(InputInterface $input, OutputInterface $ou
348349
{
349350
$output->writeln('from the code...');
350351
}
352+
353+
public function testLockIsReleased()
354+
{
355+
$command1 = new \FooLock1Command();
356+
357+
$tester = new CommandTester($command1);
358+
$tester->execute(array());
359+
$tester->execute(array());
360+
}
361+
362+
/**
363+
* @expectedException Symfony\Component\Console\Exception\RuntimeException
364+
* @expectedExceptionMessage <error>The command "foo:lock1" is already running in another process.</error>
365+
*/
366+
public function testLockThrowsExceptionIfAlreadyLocked()
367+
{
368+
$command1 = new \FooLock1Command();
369+
370+
$lock = new LockHandler($command1->getName());
371+
$lock->lock();
372+
373+
$tester = new CommandTester($command1);
374+
$tester->execute(array());
375+
376+
$lock->release();
377+
}
378+
379+
public function testLockSilentModeDoesNotThrowAnyException()
380+
{
381+
$command1 = new \FooLock2Command();
382+
383+
$lock = new LockHandler($command1->getName());
384+
$lock->lock();
385+
386+
$tester = new CommandTester($command1);
387+
$tester->execute(array());
388+
389+
$lock->release();
390+
}
351391
}
352392

353393
// In order to get an unbound closure, we should create it outside a class
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Command\Command;
4+
use Symfony\Component\Console\Input\InputInterface;
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
class FooLock1Command extends Command
8+
{
9+
protected function configure()
10+
{
11+
$this->setName('foo:lock1');
12+
}
13+
14+
protected function execute(InputInterface $input, OutputInterface $output)
15+
{
16+
$this->lock();
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Command\Command;
4+
use Symfony\Component\Console\Input\InputInterface;
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
class FooLock2Command extends Command
8+
{
9+
protected function configure()
10+
{
11+
$this->setName('foo:lock2');
12+
}
13+
14+
protected function execute(InputInterface $input, OutputInterface $output)
15+
{
16+
$this->lock(true);
17+
}
18+
}

src/Symfony/Component/Console/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"suggest": {
2828
"symfony/event-dispatcher": "",
2929
"symfony/process": "",
30+
"symfony/filesystem": "",
3031
"psr/log": "For using the console logger"
3132
},
3233
"autoload": {

0 commit comments

Comments
 (0)
0