bug #28689 [Process] fix locking of pipe files on Windows (nicolas-grekas)

This PR was merged into the 2.8 branch.

Discussion
----------

[Process] fix locking of pipe files on Windows

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #28655
| License       | MIT
| Doc PR        | -

Commits
-------

d64bd3b181 [Process] fix locking of pipe files on Windows
This commit is contained in:
Fabien Potencier 2018-10-10 02:50:01 -07:00
commit 9fdc64b479

View File

@ -28,6 +28,7 @@ class WindowsPipes extends AbstractPipes
{ {
private $files = array(); private $files = array();
private $fileHandles = array(); private $fileHandles = array();
private $lockHandles = array();
private $readBytes = array( private $readBytes = array(
Process::STDOUT => 0, Process::STDOUT => 0,
Process::STDERR => 0, Process::STDERR => 0,
@ -47,31 +48,33 @@ class WindowsPipes extends AbstractPipes
Process::STDOUT => Process::OUT, Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR, Process::STDERR => Process::ERR,
); );
$tmpCheck = false;
$tmpDir = sys_get_temp_dir(); $tmpDir = sys_get_temp_dir();
$lastError = 'unknown reason'; $lastError = 'unknown reason';
set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
for ($i = 0;; ++$i) { for ($i = 0;; ++$i) {
foreach ($pipes as $pipe => $name) { foreach ($pipes as $pipe => $name) {
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
if (file_exists($file) && !unlink($file)) {
continue 2; if (!$h = fopen($file.'.lock', 'w')) {
}
$h = fopen($file, 'xb');
if (!$h) {
$error = $lastError;
if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
continue;
}
restore_error_handler(); restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error)); throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
} }
if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) { if (!flock($h, LOCK_EX | LOCK_NB)) {
continue 2; continue 2;
} }
if (isset($this->files[$pipe])) { if (isset($this->lockHandles[$pipe])) {
unlink($this->files[$pipe]); flock($this->lockHandles[$pipe], LOCK_UN);
fclose($this->lockHandles[$pipe]);
} }
$this->lockHandles[$pipe] = $h;
if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
flock($this->lockHandles[$pipe], LOCK_UN);
fclose($this->lockHandles[$pipe]);
unset($this->lockHandles[$pipe]);
continue 2;
}
$this->fileHandles[$pipe] = $h;
$this->files[$pipe] = $file; $this->files[$pipe] = $file;
} }
break; break;
@ -85,7 +88,6 @@ class WindowsPipes extends AbstractPipes
public function __destruct() public function __destruct()
{ {
$this->close(); $this->close();
$this->removeFiles();
} }
/** /**
@ -145,8 +147,11 @@ class WindowsPipes extends AbstractPipes
$read[$type] = $data; $read[$type] = $data;
} }
if ($close) { if ($close) {
ftruncate($fileHandle, 0);
fclose($fileHandle); fclose($fileHandle);
unset($this->fileHandles[$type]); flock($this->lockHandles[$type], LOCK_UN);
fclose($this->lockHandles[$type]);
unset($this->fileHandles[$type], $this->lockHandles[$type]);
} }
} }
@ -167,10 +172,13 @@ class WindowsPipes extends AbstractPipes
public function close() public function close()
{ {
parent::close(); parent::close();
foreach ($this->fileHandles as $handle) { foreach ($this->fileHandles as $type => $handle) {
ftruncate($handle, 0);
fclose($handle); fclose($handle);
flock($this->lockHandles[$type], LOCK_UN);
fclose($this->lockHandles[$type]);
} }
$this->fileHandles = array(); $this->fileHandles = $this->lockHandles = array();
} }
/** /**
@ -185,17 +193,4 @@ class WindowsPipes extends AbstractPipes
{ {
return new static($process->isOutputDisabled(), $input); return new static($process->isOutputDisabled(), $input);
} }
/**
* Removes temporary files.
*/
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = array();
}
} }