symfony: Using nested cache contracts can cause deadlock between processes
Symfony version(s) affected: 5.2.*
Description Using nested cache contracts can cause deadlock between processes
How to reproduce test-deadlock.php:
#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Contracts\Cache\ItemInterface;
class TestCommand extends Command {
protected static $defaultName = 'test' ;
private $keys = [
'a' => ["abc_5", "def_4"],
'b' => ["fgh_17", "ijk_21"],
];
public function __construct() {
$this->cache = new MemcachedAdapter(MemcachedAdapter::createConnection('memcached://localhost'));
parent::__construct();
}
protected function configure() {
$this->addArgument('key_set', InputArgument::REQUIRED);
}
public function execute(InputInterface $input, OutputInterface $output) {
$key_set = $input->getArgument('key_set');
$value = $this->cache->get($this->keys[$key_set][0], function (ItemInterface $item) use ($key_set) {
sleep(10);
$value2 = $this->cache->get($this->keys[$key_set][1], function (ItemInterface $item) {
sleep(10);
$item->expiresAfter(1);
return 1;
});
$item->expiresAfter(1);
return 1;
});
$output->writeln("All done");
return 0;
}
}
$app = new Application();
$app->add(new TestCommand());
$app->run();
composer.json:
{
"require": {
"symfony/console": "5.2.*",
"symfony/framework-bundle": "5.2.*"
}
}
memcached daemon should be running on localhost
run
composer install
if you then run the scripts sequentially,
php test-deadlock.php test a
php test-deadlock.php test b
then they exit correctly, after 20 seconds each
But if run them in parallel in different terminals, they will never finish.
If we look at the locks when the scripts are running, we will see something like this:
$ lslocks | grep php
php 32014 FLOCK 21.6K READ* 0 0 0 /.../vendor/symfony/cache/Adapter/PdoAdapter.php
php 32018 FLOCK 21.6K WRITE 0 0 0 /.../vendor/symfony/cache/Adapter/PdoAdapter.php
php 32018 FLOCK 1.9K READ* 0 0 0 /.../vendor/symfony/cache/Adapter/Psr16Adapter.php
php 32014 FLOCK 1.9K WRITE 0 0 0 /.../vendor/symfony/cache/Adapter/Psr16Adapter.php
Possible Solution Please make using flock() in cache contract optional, or add to the documentation that using nested cache contracts is dangerous. Additional context
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 21 (19 by maintainers)
Commits related to this issue
- bug #44669 [Cache] disable lock on CLI (nicolas-grekas) This PR was merged into the 4.4 branch. Discussion ---------- [Cache] disable lock on CLI | Q | A | ------------- | --- | Branch... — committed to symfony/symfony by nicolas-grekas 3 years ago
- Fix #948 Oke, een korte uiteenzetting van waardoor het komt dat de stek af en toe niet meer wil werken en de op dit moment gevonden oplossing(en). Zoals ik het nu zie gaat het mis als er twee proces... — committed to csrdelft/csrdelft.nl by qurben 3 years ago
- bug #44820 [Cache] Don't lock when doing nested computations (nicolas-grekas) This PR was merged into the 4.4 branch. Discussion ---------- [Cache] Don't lock when doing nested computations | Q ... — committed to symfony/symfony by nicolas-grekas 3 years ago
This is not supposed to happen, because a process cannot dead-lock itself. Please share a reproducer if you can!
I’m not sure we can fix this in Symfony itself. What you need to do is to call
LockRegistry::setFiles([])
from within your subprocesses to disable locking in them.