doctrine / DoctrineMongoDBBundle

Integrates Doctrine MongoDB ODM with Symfony
http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
MIT License
379 stars 229 forks source link

mkdir() race condition in cache warmers #744

Closed andrey-tech closed 1 year ago

andrey-tech commented 2 years ago

DoctrineMongoDBBundle version(s) affected

3.5.3, up to 4.6.x

Description

The race condition is appears when several processes are attempting to create a same Doctrine Proxy/Hydrator directory which does not yet exist and \RuntimeException is thrown:

  1. \Doctrine\Bundle\MongoDBBundle\CacheWarmer\ProxyCacheWarmer

    50        if (!file_exists($proxyCacheDir)) {
    51            if (false === @mkdir($proxyCacheDir, 0775, true)) {
    52                throw new \RuntimeException(sprintf('Unable to create the Doctrine Proxy directory (%s)', dirname($proxyCacheDir)));
    53            }
  2. \Doctrine\Bundle\MongoDBBundle\CacheWarmer\PersistentCollectionCacheWarmer

    47        if (! file_exists($collCacheDir)) {
    48           if (false === @mkdir($collCacheDir, 0775, true)) {
    49                throw new \RuntimeException(sprintf('Unable to create the Doctrine persistent collection directory (%s)', dirname($collCacheDir)));
    50            }
  3. \Doctrine\Bundle\MongoDBBundle\CacheWarmer\HydratorCacheWarmer

    48        if (!file_exists($hydratorCacheDir)) {
    49            if (false === @mkdir($hydratorCacheDir, 0775, true)) {
    50                throw new \RuntimeException(sprintf('Unable to create the Doctrine Hydrator directory (%s)', dirname($hydratorCacheDir)));
    51            }
  4. \Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\CreateProxyDirectoryPass

    22        if (!is_dir($proxyCacheDir)) {
    23            if (false === @mkdir($proxyCacheDir, 0775, true)) {
    24               exit(sprintf('Unable to create the Doctrine Proxy directory (%s)', dirname($proxyCacheDir)));
    25            }
  5. \Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\CreateHydratorDirectoryPass

    22        if (!is_dir($hydratorCacheDir)) {
    23            if (false === @mkdir($hydratorCacheDir, 0775, true)) {
    24                exit(sprintf('Unable to create the Doctrine Hydrator directory (%s)', dirname($hydratorCacheDir)));
    25            }

How to reproduce

This issue is difficult to reproduce, as any concurrency-related issues are. It appears when several processes are attempting to create a directory which does not yet exist. Specifically, when one process is between !file_exists() and @mkdir() after another process has already managed to create the directory.

Possible Solution

Add an extra check !file_exists(). Example:

  1. \Doctrine\Bundle\MongoDBBundle\CacheWarmer\ProxyCacheWarmer
    50        if (!file_exists($proxyCacheDir)) {
    51            if (false === @mkdir($proxyCacheDir, 0775, true) && !file_exists($proxyCacheDir)) {
    52                throw new \RuntimeException(sprintf('Unable to create the Doctrine Proxy directory (%s)', dirname($proxyCacheDir)));
    53            }

Additional Context

Same problem in symfony/symfony already was fixed - see issue #47489.

malarzm commented 1 year ago

Closing as #747 was merged