Skip to content

[Console] Add Lockable trait #18471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

geoffrey-brier
Copy link
Contributor

@geoffrey-brier geoffrey-brier commented Apr 7, 2016

Q A
Branch? "master"
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets none
License MIT
Doc PR none for the moment :)

Hi there,

Since the 2.6 the LockHandler class was added to ease concurrency problems. There was a nice post about using it in your commands.

From my humble experience, I find it a bit unpleasant/time consuming to always copy/paste the same code. So here is my proposal:

Before:

class UpdateContentsCommand extends Command
{
    protected function configure()
    {
        // ...
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // create the lock
        $lock = new LockHandler('update:contents');
        if (!$lock->lock()) {
            $output->writeln('The command is already running in another process.');

            return 0;
        }

      // Your code

        $lock->release();
    }
}

After:

class MyCommand extends Command
{
    use LockableTrait;

    protected function execute(InputInterface $input, OutputInterface $output)
    {
         if (!$this->lock()) {
             // Here you can handle locking errors
         }
        // Your code
        // The lock release is still optionnal
         $this->release();
    }
}

In addition, you can optionally pass two arguments:

  • a string argument to change the lock name
  • a boolean argument to indicate if you want to wait until the requested lock is released

@geoffrey-brier geoffrey-brier force-pushed the feature/auto-lockable-commands branch 2 times, most recently from f9d7d98 to 3f9fc8a Compare April 7, 2016 11:02
*/
public function setAutoLock($autoLock)
{
$this->autoLock = (boolean) $autoLock;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the standard is to use (bool), though usually the values are not cast like that afaik

@javiereguiluz
Copy link
Member

About the proposed method name: ->setAutoLock(true); I don't think it's bad, but maybe we can improve it.

My concern: the "auto-lock" feature is an "implementation detail". What you really want is to prevent concurrent command executions. You don't care about the lock at all.

If you agree, the hard part will be to find a good name for this.

@geoffrey-brier
Copy link
Contributor Author

@javiereguiluz I do agree with you, I wasn't really satisfied with the naming ... what about "safe lock", "thread safe" ... ? Or setParallelizable ?

@linaori
Copy link
Contributor

linaori commented Apr 7, 2016

The only problem with this locking strategy is that it only works if your command is limited to 1 run per application instance. If I would deploy 2 applications which both run the command, it could cause issues.

@geoffrey-brier
Copy link
Contributor Author

@iltar That's true (though the problem also occurs before this PR). What would you suggest ? Introduce some kind of interface with various strategies ? If that's the case that may be a good idea to create a new "locking" component.

@javiereguiluz
Copy link
Member

@iltar @geoffrey-brier I think that'd a reasonable limitation of this feature.

@linaori
Copy link
Contributor

linaori commented Apr 7, 2016

If it's extendable, it's really nice (I think I've suggested this in the original feature but not sure).

It's already a nice to have feature as is though.

@geoffrey-brier
Copy link
Contributor Author

@javiereguiluz I think it would be reasonable too (may be be nice to enhance it later though).

@fabpot
Copy link
Member

fabpot commented Apr 28, 2016

ping @lyrixx

@lyrixx
Copy link
Member

lyrixx commented Apr 28, 2016

  • I like the idea
  • I agree with @javiereguiluz comments
  • But I don't really like the implementation. I think we should stick to what is described in the blog post. The diff will be smaller, and the code easier to understand
  • Should we add a note about the limitation (1 server) ?

@fabpot
Copy link
Member

fabpot commented Jun 14, 2016

@geoffrey-brier Do you have some time to finish this PR by taking comments into account? Thanks.

@geoffrey-brier
Copy link
Contributor Author

@fabpot Sure no problem but I'm not sure to understand correctly.

Should I add another method ? What about the naming ?

@geoffrey-brier geoffrey-brier force-pushed the feature/auto-lockable-commands branch 2 times, most recently from f5e8f4c to ca06975 Compare June 14, 2016 09:58
@geoffrey-brier
Copy link
Contributor Author

geoffrey-brier commented Jun 14, 2016

@fabpot @javiereguiluz @iltar I've completely reworked the PR.

Now you would do something like this:

class MyCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
         // By default it would throw a RuntimeException if the command is locked
         $this->lock();
        // Your code
        // The lock release is still optionnal
         $this->release();
    }
}

You can also pass a boolean to the lock method so that it doesn't throw any exception.

class MyCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
         // Silently return
         if ($this->lock(true)) {
             return;
         }
    }
}

*
* @throws RuntimeException
*/
protected function lock($name, $blocking = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need the name here? As the trait is only able to deal with only one lock anyway, I'd suggest to remove the name and generate one automatically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have a name depending on some arguments. But a default value (the command name) is indeed better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fait enough, let's add a default value then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about making the name able to accept full paths as well? We usually put our locks into %projectdir%/var/run so doing something like

$lockPath = null;
$lockName = $name ?: $this->getName();

if ($name && $fs->isAbsolutePath($name)) {
    $lockPath = dirname($name);
    $lockName = basename($name);
}

would be great. Or is that look too much?

@geoffrey-brier geoffrey-brier force-pushed the feature/auto-lockable-commands branch 2 times, most recently from b545b81 to 8b63ed3 Compare June 23, 2016 09:32
@geoffrey-brier
Copy link
Contributor Author

Fixed everything

@fabpot
Copy link
Member

fabpot commented Jun 23, 2016

👍

@@ -21,6 +21,7 @@
},
"require-dev": {
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/filesystem": "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing version.

@lyrixx
Copy link
Member

lyrixx commented Jun 23, 2016

@geoffrey-brier Could you update the PR description to have something that match the new implementation? Thanks.

*/
protected function lock($name = null, $blocking = false)
{
if (!class_exists('Symfony\Component\Filesystem\LockHandler')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LockHandler::class

@geoffrey-brier geoffrey-brier force-pushed the feature/auto-lockable-commands branch from 8b63ed3 to dd523cb Compare June 23, 2016 11:26
@geoffrey-brier geoffrey-brier changed the title [Console] Add lock feature [Console] Add Lockable trait Jun 23, 2016
@geoffrey-brier
Copy link
Contributor Author

@lyrixx Done & tests are green :)

@fabpot
Copy link
Member

fabpot commented Jun 23, 2016

@geoffrey-brier Looks good. Can you add a new item in the component CHANGELOG to mention this new feature for 3.2?

@geoffrey-brier geoffrey-brier force-pushed the feature/auto-lockable-commands branch from dd523cb to b57a83f Compare June 23, 2016 11:42
@geoffrey-brier
Copy link
Contributor Author

@fabpot Done

@fabpot
Copy link
Member

fabpot commented Jun 23, 2016

Thank you @geoffrey-brier.

@fabpot fabpot merged commit b57a83f into symfony:master Jun 23, 2016
fabpot added a commit that referenced this pull request Jun 23, 2016
This PR was merged into the 3.2-dev branch.

Discussion
----------

[Console] Add Lockable trait

| Q             | A
| ------------- | ---
| Branch?       | "master"
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | none
| License       | MIT
| Doc PR        | none for the moment :)

Hi there,

Since the 2.6 the `LockHandler` class was added to ease concurrency problems. There was a nice post about [using it in your commands](http://symfony.com/blog/new-in-symfony-2-6-lockhandler).

From my humble experience, I find it a bit unpleasant/time consuming to always copy/paste the same code. So here is my proposal:

Before:

```php
class UpdateContentsCommand extends Command
{
    protected function configure()
    {
        // ...
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // create the lock
        $lock = new LockHandler('update:contents');
        if (!$lock->lock()) {
            $output->writeln('The command is already running in another process.');

            return 0;
        }

      // Your code

        $lock->release();
    }
}
```

After:

```php
class MyCommand extends Command
{
    use LockableTrait;

    protected function execute(InputInterface $input, OutputInterface $output)
    {
         if (!$this->lock()) {
             // Here you can handle locking errors
         }
        // Your code
        // The lock release is still optionnal
         $this->release();
    }
}
```
In addition, you can optionally pass two arguments:
- a string argument to change the lock name
- a boolean argument to indicate if you want to wait until the requested lock is released

Commits
-------

b57a83f [Console] Add Lockable trait
@geoffrey-brier geoffrey-brier deleted the feature/auto-lockable-commands branch June 23, 2016 11:48
*
* @return bool
*/
protected function lock($name = null, $blocking = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not declaring everything private by default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected looks fine to me, moving to private won't provide anything more

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, every class using this trait will now expose the used methods as an extension point which might not be what the user intended.

/**
* Basic lock feature for commands.
*
* @author Fabien Potencier <fabien@symfony.com>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geoffrey-brier You should have put your name here. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xabbuh I'm not that important ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, you're the author of this trait. :)

fabpot added a commit that referenced this pull request Jun 24, 2016
…abbuh)

This PR was merged into the 3.2-dev branch.

Discussion
----------

[Console] LockableTrait: change visibility to private

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #18471 (comment)
| License       | MIT
| Doc PR        |

Without this change we force users to expose unnecessary extension points they may not want to provide.

Commits
-------

eaa3bb0 LockableTrait: change visibility to private
fabpot added a commit that referenced this pull request Jun 24, 2016
This PR was merged into the 3.2-dev branch.

Discussion
----------

[Console] update the author of the LockableTrait

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #18471 (comment)
| License       | MIT
| Doc PR        |

Commits
-------

b15fec7 update the author of the LockableTrait
@fabpot fabpot mentioned this pull request Oct 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.