-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Lock] Include lock component in framework bundle #22113
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
Conversation
|
||
<service id="lock.store.combined.abstract" class="Symfony\Component\Lock\Store\CombinedStore" abstract="true" public="false"> | ||
<argument /> <!-- List of stores --> | ||
<argument type="service" id="lock.quorum.majority" /> <!-- Quorum --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be rename into strategy
8c0a97b
to
c696f0a
Compare
What about adding a |
Maybe I'm late, but I'd like to propose a simpler way to use and configure this component inside the Symfony Framework. The idea, as usual, is to optimize/simplify the common case and don't complicate much the complex case. Common caseApplication defines/needs just 1 lock type. ConfigurationThis is the default config, where Symfony picks the best lock type for you: semaphore if the extension is installed; flock otherwise: framework:
lock: ~ This is the explicit config where you set the lock type (string or array and some smart conversion by the Config component, as we do everywhere else): framework:
lock: 'flock'
lock: 'semaphore'
lock: 'memcached://m1.docker'
lock: ['memcached://m1.docker', 'memcached://m2.docker']
lock: 'redis://r1.docker'
lock: ['redis://r1.docker', 'redis://r2.docker'] Usage// non-blocking, non-expiring
$lock = $this->get('lock')->acquire();
// $lock = $container->get('lock')->acquire();
// blocking, non-expiring
$lock = $this->get('lock')->acquire(true);
// non-blocking, expiring in 60 seconds
$lock = $this->get('lock')->acquire(false, 60);
// normal operations on the lock
$lock->isAcquired();
$lock->refresh();
$lock->release(); Complex caseApplication defines and uses several lock types. Configurationframework:
lock:
users: ['memcached://m1.docker', 'memcached://m2.docker']
admin: 'flock'
partners: 'redis://r1.docker'
invoices: 'semaphore' Usage$adminLock = $this->get('lock.admin')->acquire();
$invoicesLock = $this->get('lock.invoices')->acquire(true);
$partnersLock = $this->get('lock.partners')->acquire(false, 30);
// ... |
|
||
if (count($hostsDefinitions) > 1) { | ||
$definition = new ChildDefinition('lock.store.combined.abstract'); | ||
$definition->replaceArgument(0, $hostsDefinitions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to lazy load stores using an iterator? It seems the combined store can break in the middle of the store chain. Btw, why not ChainStore
instead of CombinedStore
for consistency?
|
||
if (null !== $username || null !== $password) { | ||
if (!method_exists($client, 'setSaslAuthData')) { | ||
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about a E_USER_WARNING
? As we're clearly going to call an undefined method.
Edit: i see it's a copy paste of the cache component.. understandable. Probably not worth it then. Im not sure this is desirable maintenance-wise though (vs. soft-depending on symfony/cache).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I choose to use the same error/format than src/Symfony/Component/Cache/Traits/MemcachedTrait.php
for more consistency
c696f0a
to
8417f2d
Compare
@javiereguiluz Today I start to implement your suggestion (much simpler, I like it).
the service I've got some other question: When you says Last point you are moving the
|
9d24eb1
to
b513ba6
Compare
Moving to milestone 3.4 as this is not a blocker for 3.3. If you don't agree, please tell :) |
ef4c5de
to
68954c3
Compare
PR rebased |
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<defaults public="false" />
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch
if ('default' === $resourceName) { | ||
$container->setAlias('lock.store', new Alias('lock.'.$resourceName.'.store')); | ||
$container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory')); | ||
$container->setAlias('lock', new Alias('lock.'.$resourceName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be private unless strongly needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved .store
services to private. But keep lock
and lock.factory
public. To let user retrieve the lock from a ContainerAware object (controller, command, ...)
$this->get('lock.foo')->acquire();
$this->get('lock.bar.factory')->createLock('my resource')->acquire();
$this->get('lock')->acquire();
$this->get('lock.factory')->createLock('my resource')->acquire();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id I missed something? Or am I using it wrongly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ContainerAware objects are still supported, but I think we all agree that they should not be promoted (for end applications esp.) and features should not be designed with that in mind.
Better inject @lock.factory
(controllers/commands/everything as services, should be autoinjected quite easily also) rather than locate it using a DIC (here is the bad habit: a DIC is not meant to be used as a service locator but to wire services, see #21623 for reference).
If no internal need for get()
this service, we should keep it private (regarding BC we can make it public afterwards if legit use case, not the inverse).
// Generate factories for each resource | ||
$factoryDefinition = new ChildDefinition('lock.factory.abstract'); | ||
$factoryDefinition->replaceArgument(0, new Reference('lock.'.$resourceName.'.store')); | ||
$factoryDefinition->setPublic(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless strongly needed it should remain private
$combinedDefinition->replaceArgument(0, $storeDefinitions); | ||
$container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition); | ||
} else { | ||
$container->setAlias('lock.'.$resourceName.'.store', new Alias((string) $storeDefinitions[0])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private?
$container->setDefinition($connectionDefinitionId, $connectionDefinition); | ||
} | ||
|
||
$storeDefinition = new Definition(\stdClass::class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StoreInterface::class
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed
Cool! thanks |
ping @symfony/deciders |
{ | ||
$loader->load('lock.xml'); | ||
|
||
$container->getDefinition('lock.store.flock')->replaceArgument(0, sys_get_temp_dir()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This inlines the tempdir used at compile time for reuse at build time, making the dumped container not-independent from the build env.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch
$storeDefinition = new Reference('lock.store.semaphore'); | ||
break; | ||
case $usedEnvs || preg_match('#^[a-z]++://#', $storeDsn): | ||
if (!$container->hasDefinition($connectionDefinitionId = md5($storeDsn))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$container->hash() instead of md5
$container->setDefinition($connectionDefinitionId, $connectionDefinition); | ||
} | ||
|
||
$storeDefinition = new Definition(StoreInterface::class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the definition should me made private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
$storeDefinition->setFactory(array(StoreFactory::class, 'createStore')); | ||
$storeDefinition->setArguments(array(new Reference($connectionDefinitionId))); | ||
|
||
$container->setDefinition($storeDefinitionId = 'lock.'.$resourceName.'.store.'.md5($storeDsn), $storeDefinition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$container->hash also here
|
||
$storeDefinition = new Reference($storeDefinitionId); | ||
break; | ||
case $usedEnvs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dead code because of the previous case
$container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition); | ||
|
||
// Generate services for lock instances | ||
$lockDefinition = new Definition(Lock::class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
if ('default' === $resourceName) { | ||
$container->setAlias('lock.store', new Alias('lock.'.$resourceName.'.store', false)); | ||
$container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false)); | ||
$container->setAlias('lock', new Alias('lock.'.$resourceName, false)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing FQCN aliases that could be use by autowiring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* | ||
* @throws \ErrorEception When invalid options or dsn are provided | ||
*/ | ||
public static function createConnection($dsn, array $options = array()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be updated to match #23978
d65375a
to
34a22f9
Compare
|
||
<service id="lock.store.flock" class="Symfony\Component\Lock\Store\FlockStore"> | ||
<argument type="service"> | ||
<service class="string"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've some doublt about the value of class here
<service id="lock.store.flock" class="Symfony\Component\Lock\Store\FlockStore"> | ||
<argument type="service"> | ||
<service class="string"> | ||
<factory function="sys_get_temp_dir"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of this, what about making the $lockPath arg of FlockStore optional, and make it use sys_get_temp_dir
then?
</argument> | ||
</service> | ||
|
||
<service id="lock.store.semaphore" class="Symfony\Component\Lock\Store\SemaphoreStore"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bad consistency about space before />
in the file
* | ||
* @return \Redis|\Predis\Client According to the "class" option | ||
*/ | ||
public static function createConnection($dsn, array $options = array()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could maybe consider a new component, named Dsn
, with factories for any kind of DSN we want to support?
not sure we can make it for 3.4, but what do others think about the idea?
52420e9
to
06342f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -32,12 +32,15 @@ class FlockStore implements StoreInterface | |||
private $lockPath; | |||
|
|||
/** | |||
* @param string $lockPath the directory to store the lock | |||
* @param string|null $lockPath the directory to store the lock. Default values will use temporary directory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the directory to store the lock, defaults to the system's temporary directory
06342f4
to
518e44b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine tuning
public static function createConnection($dsn, array $options = array()) | ||
{ | ||
if (!is_string($dsn)) { | ||
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, gettype($dsn))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expects
*/ | ||
class StoreFactory | ||
{ | ||
public static function createConnection($dsn, array $options = array()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add a docblock listing supported dsn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be removed replaced by the new Dns component
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn)); | ||
} | ||
|
||
public static function createStore($connection) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
@param \Redis|\RedisArray|\RedisCluster|\Predis\Client|\Memcached $connection
@return RedisStore|MemcachedStore
518e44b
to
0b9c2e6
Compare
Thank you @jderusse. |
…russe) This PR was squashed before being merged into the 3.4 branch (closes #22113). Discussion ---------- [Lock] Include lock component in framework bundle | 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 | symfony/symfony-docs#8066 ## Usage use the best available "out of box" store (semaphore if available, filesyste otherwise) ```yml framework: lock: ~ # lock: true ``` ```php $this->get('lock')->acquire(); $this->get('lock.factory')->createLock('my resource')->acquire(); ``` use a specific store ```yml framework: lock: flock # lock: semaphore # lock: redis://localhost # lock: "%env(MEMCACHED_DSN)%" # lock: ["%env(REDIS_DSN_1)%", "%env(REDIS_DSN_2)%"] ``` ```php $this->get('lock')->acquire(); $this->get('lock.factory')->createLock('my resource')->acquire(); ``` use a named lock ```yml framework: lock: foo: flock bar: redis://localhost ``` ```php $this->get('lock.foo')->acquire(); $this->get('lock.bar.factory')->createLock('my resource')->acquire(); ``` factory usage ```xml <service id="acme" class="AppBundle\Acme"> <argument type="service"> <service class="Symfony\Component\Lock\Lock"> <factory service="lock.foo.factory" method="createLock" /> <argument>my resource</argument> <argument>30</argument> <!-- optional TTL --> </service> </argument> </service> ``` * [x] Tests Commits ------- b4b00c9 [Lock] Include lock component in framework bundle
This PR was merged into the 3.4 branch. Discussion ---------- [Lock] Integrate framework Should probably be merged after #7866 Documentation for symfony/symfony#22113 Commits ------- 142e703 Fixed minor issues 7f1e087 Add lock configuration in framework bundle
Usage
use the best available "out of box" store (semaphore if available, filesyste otherwise)
use a specific store
use a named lock
factory usage