Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
120 views
in Technique[技术] by (71.8m points)

php - Implement locking for all commands in my Symfony app

I followed this guide: https://symfony.com/doc/current/console/lockable_trait.html and implemented the command lock feature for my one of my commands to see how it works. It worked as described and then I was going to implement it for all of my commands. But the issue is that I have about 50 commands and:

  • I do not want spent time adding the necessary code to each command
  • I want to have the centralized management of commands locking. I mean, adding extra option to regular commands so that they will be used by my future management center. For now I will need a pretty simple option protected function isLocked() for a regular command which will help me to manage if a command should have lockable feature.

So, I went to the source of SymfonyComponentConsoleCommandLockableTrait and after some time created the following listener to the event console.command:

use SymfonyComponentConsoleEventConsoleCommandEvent;
use SymfonyComponentConsoleExceptionLogicException;
use SymfonyComponentLockLock;
use SymfonyComponentLockLockFactory;
use SymfonyComponentLockLockInterface;
use SymfonyComponentLockStoreFlockStore;
use SymfonyComponentLockStoreSemaphoreStore;

class LockCommandsListener
{
    /**
     * @var array<string, Lock>
     */
    private $commandLocks = [];

    private static function init()
    {
        if (!class_exists(SemaphoreStore::class)) {
            throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
        }
    }

    public function onConsoleCommand(ConsoleCommandEvent $event)
    {
        static::init();

        $name = $event->getCommand()->getName();
        $this->ensureLockNotPlaced($name);
        $lock = $this->createLock($name);
        $this->commandLocks[$name] = $lock;

        if (!$lock->acquire()) {
            $this->disableCommand($event, $name);
        }
    }

    private function disableCommand(ConsoleCommandEvent $event, string $name)
    {
        unset($this->commandLocks[$name]);
        $event->getOutput()->writeln('The command ' . $name . ' is already running');
        $event->disableCommand();
        $event->getCommand()->setCode()
    }

    private function createLock(string $name): LockInterface
    {
        if (SemaphoreStore::isSupported()) {
            $store = new SemaphoreStore();
        } else {
            $store = new FlockStore();
        }

        return (new LockFactory($store))->createLock($name);
    }

    private function ensureLockNotPlaced(string $name)
    {
        if (isset($this->commandLocks[$name])) {
            throw new LogicException('A lock is already in place.');
        }
    }
}

I made some tests and it kind of worked. But I am not sure this is the right way of doing things.

Another problem is that I can not find the proper exit code when I disabled a command. Should I just disable it? But it seems that exit code would be a great feature here. Specially when it comes to this listener testing (PHPUnit testing).

And I also have with testing itself. How can I run commands in parallel in my test class. For now I have this:

class LockCommandTest extends CommandTest
{
    public function testOneCommandCanBeRun()
    {
        $commandTester = new ApplicationTester($this->application);
        $commandTester->run([
            'command' => 'app:dummy-command'
        ]);
        $output = $commandTester->getDisplay();
        dd($output);
    }
}

It will allow only to run my commands one by one. But I would like to run them both so after running the first one, the second will fail (with some exit code).

question from:https://stackoverflow.com/questions/65861993/implement-locking-for-all-commands-in-my-symfony-app

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As for me the best way to make background task is doing it via supervisor, create config file, like:

[program:your_service]
command=/usr/local/bin/php /srv/www/bin/console <your:app:command>
priority=1
numprocs=1
# Each 5 min.
startsecs=300
autostart=true
autorestart=true
process_name=%(program_name)s_%(process_num)02d
user=root

this is the best way to be sure that your command will be ran only in one process


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...