Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dc1e6b1

Browse files
[Process] Fix pipes handling
1 parent a1c95f7 commit dc1e6b1

File tree

4 files changed

+101
-162
lines changed

4 files changed

+101
-162
lines changed

src/Symfony/Component/Process/Pipes/AbstractPipes.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ abstract class AbstractPipes implements PipesInterface
2929
/** @var bool */
3030
private $blocked = true;
3131

32+
public function __construct($input)
33+
{
34+
if (is_resource($input)) {
35+
$this->input = $input;
36+
} elseif (is_string($input)) {
37+
$this->inputBuffer = $input;
38+
} else {
39+
$this->inputBuffer = (string) $input;
40+
}
41+
}
42+
3243
/**
3344
* {@inheritdoc}
3445
*/
@@ -71,4 +82,64 @@ protected function unblock()
7182

7283
$this->blocked = false;
7384
}
85+
86+
/**
87+
* Writes input to stdin.
88+
*/
89+
protected function write()
90+
{
91+
if (!isset($this->pipes[0])) {
92+
return;
93+
}
94+
95+
$e = array();
96+
$r = null !== $this->input ? array($this->input) : $e;
97+
$w = array($this->pipes[0]);
98+
99+
// let's have a look if something changed in streams
100+
if (false === $n = @stream_select($r, $w, $e, 0, 0)) {
101+
return;
102+
}
103+
104+
foreach ($w as $stdin) {
105+
if (isset($this->inputBuffer[0])) {
106+
$written = fwrite($stdin, $this->inputBuffer);
107+
$this->inputBuffer = substr($this->inputBuffer, $written);
108+
if (isset($this->inputBuffer[0])) {
109+
return array($this->pipes[0]);
110+
}
111+
}
112+
113+
foreach ($r as $input) {
114+
for (;;) {
115+
$data = fread($input, self::CHUNK_SIZE);
116+
if (!isset($data[0])) {
117+
break;
118+
}
119+
$written = fwrite($stdin, $data);
120+
$data = substr($data, $written);
121+
if (isset($data[0])) {
122+
$this->inputBuffer = $data;
123+
124+
return array($this->pipes[0]);
125+
}
126+
}
127+
if (!isset($data[0]) && feof($input)) {
128+
// no more data to read on input resource
129+
// use an empty buffer in the next reads
130+
$this->input = null;
131+
}
132+
}
133+
}
134+
135+
// no input to read on resource, buffer is empty
136+
if (null === $this->input && !isset($this->inputBuffer[0])) {
137+
fclose($this->pipes[0]);
138+
unset($this->pipes[0]);
139+
}
140+
141+
if (!$w) {
142+
return array($this->pipes[0]);
143+
}
144+
}
74145
}

src/Symfony/Component/Process/Pipes/UnixPipes.php

Lines changed: 17 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@ public function __construct($ttyMode, $ptyMode, $input, $disableOutput)
3535
$this->ptyMode = (bool) $ptyMode;
3636
$this->disableOutput = (bool) $disableOutput;
3737

38-
if (is_resource($input)) {
39-
$this->input = $input;
40-
} else {
41-
$this->input = fopen('php://temp', 'w+');
42-
fwrite($this->input, $input);
43-
fseek($this->input, 0);
44-
}
38+
parent::__construct($input);
4539
}
4640

4741
public function __destruct()
@@ -100,36 +94,15 @@ public function getFiles()
10094
*/
10195
public function readAndWrite($blocking, $close = false)
10296
{
103-
// only stdin is left open, job has been done !
104-
// we can now close it
105-
if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) {
106-
fclose($this->pipes[0]);
107-
unset($this->pipes[0]);
108-
}
109-
110-
if (empty($this->pipes)) {
111-
return array();
112-
}
113-
11497
$this->unblock();
98+
$w = $this->write();
11599

116-
$read = array();
117-
118-
if (null !== $this->input) {
119-
// if input is a resource, let's add it to stream_select argument to
120-
// fill a buffer
121-
$r = array_merge($this->pipes, array('input' => $this->input));
122-
} else {
123-
$r = $this->pipes;
124-
}
125-
// discard read on stdin
100+
$read = $e = array();
101+
$r = $this->pipes;
126102
unset($r[0]);
127103

128-
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
129-
$e = null;
130-
131104
// let's have a look if something changed in streams
132-
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
105+
if ($r && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
133106
// if a system call has been interrupted, forget about it, let's try again
134107
// otherwise, an error occurred, let's reset pipes
135108
if (!$this->hasSystemCallBeenInterrupted()) {
@@ -139,44 +112,24 @@ public function readAndWrite($blocking, $close = false)
139112
return $read;
140113
}
141114

142-
// nothing has changed
143-
if (0 === $n) {
144-
return $read;
145-
}
146-
147115
foreach ($r as $pipe) {
148116
// prior PHP 5.4 the array passed to stream_select is modified and
149117
// lose key association, we have to find back the key
150-
$type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input';
151-
$data = '';
152-
if ($type !== 'input') {
153-
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
154-
$data .= $dataread;
155-
}
156-
// Remove extra null chars returned by fread
157-
if ('' !== $data) {
158-
$read[$type] = rtrim($data, "\x00");
159-
}
160-
} elseif (isset($w[0])) {
161-
stream_copy_to_stream($this->input, $w[0], 4096);
162-
}
118+
$read[$type = array_search($pipe, $this->pipes, true)] = '';
163119

164-
if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
165-
if ($type === 'input') {
166-
// no more data to read on input resource
167-
// use an empty buffer in the next reads
168-
$this->input = null;
169-
} else {
170-
fclose($this->pipes[$type]);
171-
unset($this->pipes[$type]);
172-
}
120+
do {
121+
$data = fread($pipe, self::CHUNK_SIZE);
122+
$read[$type] .= $data;
123+
} while (isset($data[0]));
124+
125+
if (!isset($read[$type][0])) {
126+
unset($read[$type]);
173127
}
174-
}
175128

176-
// no input to read on resource and stdin still open
177-
if (null === $this->input && isset($this->pipes[0])) {
178-
fclose($this->pipes[0]);
179-
unset($this->pipes[0]);
129+
if ($close && feof($pipe)) {
130+
fclose($pipe);
131+
unset($this->pipes[$type]);
132+
}
180133
}
181134

182135
return $read;

src/Symfony/Component/Process/Pipes/WindowsPipes.php

Lines changed: 11 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,13 @@ public function __construct($disableOutput, $input)
5252
Process::STDERR => tempnam(sys_get_temp_dir(), 'err_sf_proc'),
5353
);
5454
foreach ($this->files as $offset => $file) {
55-
if (false === $file || false === $this->fileHandles[$offset] = fopen($file, 'rb')) {
55+
if (false === $file || false === $this->fileHandles[$offset] = @fopen($file, 'rb')) {
5656
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
5757
}
5858
}
5959
}
6060

61-
if (is_resource($input)) {
62-
$this->input = $input;
63-
} else {
64-
$this->inputBuffer = $input;
65-
}
61+
parent::__construct($input);
6662
}
6763

6864
public function __destruct()
@@ -109,28 +105,20 @@ public function getFiles()
109105
*/
110106
public function readAndWrite($blocking, $close = false)
111107
{
112-
$this->write($blocking, $close);
108+
$this->unblock();
109+
$this->write();
113110

114111
$read = array();
115112
$fh = $this->fileHandles;
116113
foreach ($fh as $type => $fileHandle) {
117-
if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
118-
continue;
119-
}
120-
$data = '';
121-
$dataread = null;
122-
while (!feof($fileHandle)) {
123-
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
124-
$data .= $dataread;
125-
}
126-
}
127-
if (0 < $length = strlen($data)) {
128-
$this->readBytes[$type] += $length;
114+
$data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
115+
116+
if (isset($data[0])) {
117+
$this->readBytes[$type] += strlen($data);
129118
$read[$type] = $data;
130119
}
131-
132-
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
133-
fclose($this->fileHandles[$type]);
120+
if ($close) {
121+
fclose($fileHandle);
134122
unset($this->fileHandles[$type]);
135123
}
136124
}
@@ -143,7 +131,7 @@ public function readAndWrite($blocking, $close = false)
143131
*/
144132
public function areOpen()
145133
{
146-
return (bool) $this->pipes && (bool) $this->fileHandles;
134+
return $this->pipes && $this->fileHandles;
147135
}
148136

149137
/**
@@ -183,71 +171,4 @@ private function removeFiles()
183171
}
184172
$this->files = array();
185173
}
186-
187-
/**
188-
* Writes input to stdin.
189-
*
190-
* @param bool $blocking
191-
* @param bool $close
192-
*/
193-
private function write($blocking, $close)
194-
{
195-
if (empty($this->pipes)) {
196-
return;
197-
}
198-
199-
$this->unblock();
200-
201-
$r = null !== $this->input ? array('input' => $this->input) : null;
202-
$w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
203-
$e = null;
204-
205-
// let's have a look if something changed in streams
206-
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
207-
// if a system call has been interrupted, forget about it, let's try again
208-
// otherwise, an error occurred, let's reset pipes
209-
if (!$this->hasSystemCallBeenInterrupted()) {
210-
$this->pipes = array();
211-
}
212-
213-
return;
214-
}
215-
216-
// nothing has changed
217-
if (0 === $n) {
218-
return;
219-
}
220-
221-
if (null !== $w && 0 < count($r)) {
222-
$data = '';
223-
while ($dataread = fread($r['input'], self::CHUNK_SIZE)) {
224-
$data .= $dataread;
225-
}
226-
227-
$this->inputBuffer .= $data;
228-
229-
if (false === $data || (true === $close && feof($r['input']) && '' === $data)) {
230-
// no more data to read on input resource
231-
// use an empty buffer in the next reads
232-
$this->input = null;
233-
}
234-
}
235-
236-
if (null !== $w && 0 < count($w)) {
237-
while (strlen($this->inputBuffer)) {
238-
$written = fwrite($w[0], $this->inputBuffer, 2 << 18);
239-
if ($written > 0) {
240-
$this->inputBuffer = (string) substr($this->inputBuffer, $written);
241-
} else {
242-
break;
243-
}
244-
}
245-
}
246-
247-
// no input to read on resource, buffer is empty and stdin still open
248-
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
249-
fclose($this->pipes[0]);
250-
unset($this->pipes[0]);
251-
}
252-
}
253174
}

src/Symfony/Component/Process/Tests/ProcessTest.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,6 @@ public function testProcessPipes($code, $size)
192192
*/
193193
public function testSetStreamAsInput($code, $size)
194194
{
195-
if ('\\' === DIRECTORY_SEPARATOR) {
196-
$this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?');
197-
}
198195
$expected = str_repeat(str_repeat('*', 1024), $size).'!';
199196
$expectedLength = (1024 * $size) + 1;
200197

@@ -791,18 +788,15 @@ public function testIdleTimeout()
791788

792789
public function testIdleTimeoutNotExceededWhenOutputIsSent()
793790
{
794-
if ('\\' === DIRECTORY_SEPARATOR) {
795-
$this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?');
796-
}
797-
$process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('while (true) {echo "foo\n"; usleep(10000);}')));
791+
$process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('while (true) {echo \'foo \'; usleep(1000);}')));
798792
$process->setTimeout(1);
799793
$process->start();
800794

801795
while (false === strpos($process->getOutput(), 'foo')) {
802796
usleep(1000);
803797
}
804798

805-
$process->setIdleTimeout(0.1);
799+
$process->setIdleTimeout(0.5);
806800

807801
try {
808802
$process->wait();

0 commit comments

Comments
 (0)