Issue #3211936 by alexpott, daffie: Race condition when generating sub directories for image styles
(cherry picked from commit 2690c63dcb
)
merge-requests/674/head
parent
2d95b87cb2
commit
2f5eb02527
|
@ -214,7 +214,10 @@ class FileSystem implements FileSystemInterface {
|
|||
$recursive_path .= $component;
|
||||
|
||||
if (!file_exists($recursive_path)) {
|
||||
if (!$this->mkdirCall($recursive_path, $mode, FALSE, $context)) {
|
||||
$success = $this->mkdirCall($recursive_path, $mode, FALSE, $context);
|
||||
// If the operation failed, check again if the directory was created
|
||||
// by another process/server, only report a failure if not.
|
||||
if (!$success && !file_exists($recursive_path)) {
|
||||
return FALSE;
|
||||
}
|
||||
// Not necessary to use self::chmod() as there is no scheme.
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\File;
|
|||
use Drupal\Component\FileSecurity\FileSecurity;
|
||||
use Drupal\Component\FileSystem\FileSystem;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\File\Exception\FileException;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
|
||||
|
@ -196,4 +197,51 @@ class DirectoryTest extends FileTestBase {
|
|||
$this->assertTrue($file_system->mkdir($dir . '/foo/baz/', 0775, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests asynchronous directory creation.
|
||||
*
|
||||
* Image style generation can result in many calls to create similar directory
|
||||
* paths. This test forks the process to create the same situation.
|
||||
*/
|
||||
public function testMultiplePrepareDirectory() {
|
||||
if (!function_exists('pcntl_fork')) {
|
||||
$this->markTestSkipped('Requires the pcntl_fork() function');
|
||||
}
|
||||
$directories = [];
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$directories[] = 'public://a/b/c/d/e/f/g/h/' . $i;
|
||||
}
|
||||
|
||||
$file_system = $this->container->get('file_system');
|
||||
|
||||
$time_to_start = microtime(TRUE) + 0.1;
|
||||
// This loop creates a new fork to create each directory.
|
||||
foreach ($directories as $directory) {
|
||||
$pid = pcntl_fork();
|
||||
if ($pid == -1) {
|
||||
$this->fail("Error forking");
|
||||
}
|
||||
elseif ($pid == 0) {
|
||||
// Sleep so that all the forks start preparing the directory at the same
|
||||
// time.
|
||||
usleep(($time_to_start - microtime(TRUE)) * 1000000);
|
||||
$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// This while loop holds the parent process until all the child threads
|
||||
// are complete - at which point the script continues to execute.
|
||||
while (pcntl_waitpid(0, $status) != -1);
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$this->assertDirectoryExists($directory);
|
||||
}
|
||||
|
||||
// Remove the database connection because it will have been destroyed when
|
||||
// the forks exited. This allows
|
||||
// \Drupal\KernelTests\KernelTestBase::tearDown() to reopen it.
|
||||
Database::removeConnection('default');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue