Reputation: 4820
At work, I've inherited a web application that has a file upload process. Part of this process occasionally (once every two weeks or so) triggers the following error:
PHP Warning: mkdir(): File exists in {{{file_path_name_redacted}}} on line 7
Looking at lines 6-8, gives us:
if(!is_dir($storeFolder)){
mkdir($storeFolder, 0644, TRUE);
}
Given this file can be hit by multiple PHP processes, I believe race conditions may be coming into play here. I've seen the same problem on other sites that I've managed in the past, similarly occurring only once in a blue moon.
What I believe is happening is that users are double-clicking on the upload button, which causes two PHP processes to execute at almost exactly the same time, like this:
Process 1 executes line 6 - dir does not exist
Process 2 executes line 6 - dir does not exist
Process 1 executes line 7 - directory is created
Process 2 executes line 7 - directory cannot be created as it already exists
Is this a case of race conditions, as I explained above (i.e. has anyone else noticed this), and/or is there some way of mitigating the error other turning off error reporting for warnings?
Upvotes: 7
Views: 603
Reputation: 352
Php inspections confirms that a race condition exists, and suggests that the safest way to write your code is:
if (!is_dir($dir) && !mkdir($dir) && !is_dir($dir)) {
throw new \RuntimeException(sprintf('Directory "%s" could not be created', $dir));
}
A bit more of explanation (Edit: link is dead see link above)
It feels strange, but it certainly works. Best of luck.
Upvotes: 8
Reputation: 4677
I have seen many projects use Hugues D's solution, which seems to work quite well. However, it can fail when using $recursive = true
, because of a bug in PHP reported in 2005, which they somehow refuse to fix (yes, it is a bug).
Here's a snipped that has served me well so far:
/**
* Safer version of mkdir(..., ..., true) without race condition issues
*
* @see https://bugs.php.net/bug.php?id=35326
*
* @param string $dirname A directory path to create
* @param int $mode Permission
*/
function safeMkdirRecursive(string $dirname, int $mode = 0777): void {
$current = '';
foreach (explode(DIRECTORY_SEPARATOR, $dirname) as $part) {
$current .= $part;
if ($current !== '' && !@mkdir($current, $mode) && !is_dir($current)) {
throw new RuntimeException('Failed to create directory: ' . $current);
}
$current .= DIRECTORY_SEPARATOR;
}
}
Disclaimer: I have not tested this on Windows!
Upvotes: 2