-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Process isolation blocks infinitely upon fatal error in child process #1340
Conversation
FWIW, despite lots of manual testing, I'm not sure how to add an environment-specific test for this. - It wouldn't run anyway by default (because travis != Windows). Also, running all tests of clean master yields plenty of failures on Windows — doesn't really count as an argument, just mentioning… |
Oh my. Unfortunately, the changes of this PR are causing the same effect on Linux (as opposed to Windows), but already in case of a PHP Notice (as opposed to a Fatal error). (example) |
Maybe only switch the behavior for Windows then? |
Which tests fail under Windows? |
I should have some time to look into this and #1169 over the weekend. If the solution doesn't break BC, we should probably target the |
Maybe. Need to debug further before concluding on anything. One combination I didn't test yet is append mode on top of this change, so that's one additional possibility… Meanwhile I suspect that the pipes are unexpectedly closed ahead of time — a regular PHP Notice is normally supposed to be caught by PHPUnit's error handler (within the child process, as opposed to causing a special behavior in the parent process), so it should, theoretically, appear in the regular test result output like any other test…
More than happy to provide test output + help to make them pass, but separate issue 😉 |
Based on that, the most obvious consideration (was): diff --git a/src/Util/PHP/Default.php b/src/Util/PHP/Default.php
index fb21932..ebce217 100644
--- a/src/Util/PHP/Default.php
+++ b/src/Util/PHP/Default.php
@@ -87,14 +87,15 @@ public function runJob($job, array $settings = array())
}
$this->process($pipes[0], $job);
- fclose($pipes[0]);
$stderr = stream_get_contents($pipes[2]);
- fclose($pipes[2]);
$stdout = stream_get_contents($pipes[1]);
+
+ fclose($pipes[0]);
fclose($pipes[1]);
+ fclose($pipes[2]);
proc_close($process);
$this->cleanup(); …but for yet another unknown reason, that happens to halt script execution altogether. (example) Reverting that idea, and instead changing stderr to append mode… still mutes the fatal error entirely (despite reading stderr first): $ phpunit -v --debug -c core/ --process-isolation --testsuite Kernel core/tests/Drupal/Tests/ProcessIsolationUnitTest.php
PHPUnit 4.1.3 by Sebastian Bergmann.
Configuration read from H:\unleashedmind\test\drupal8\core\phpunit.xml.dist
Starting test 'Drupal\Tests\ProcessIsolationUnitTest::testSameProcess'.
.
Starting test 'Drupal\Tests\ProcessIsolationUnitTest::testIsolated'.
.
Starting test 'Drupal\Tests\ProcessIsolationUnitTest::testIsolatedFatalError'.
.
Time: 9.57 seconds, Memory: 3.75Mb
OK (3 tests, 2 assertions) The first two are simple counter-tests, expected to pass, I originally planned to save the following for a completely separate PR… We're using The more I'm looking into this, the more I suspect that the anti-behavior is caused by piping a script into The STDIN injection is problematic for several reasons anyway:
Fact is, - protected function process($pipe, $job)
- fwrite(
- $pipe,
- "<?php require_once " . var_export($this->tempFile, true) . "; ?>"
- );
+ public function runJob($job, array $settings = array())
+ $process = proc_open(
- $runtime->getBinary() . $this->settingsToParameters($settings),
+ $runtime->getBinary() . $this->settingsToParameters($settings) . ' -f ' . escapeshellarg($this->tempFile), I prepared a local branch to use the Windows-specific temp file approach by default (and removing the Windows-specific code), but was confronted with PHPUnit's Usage Help text when executing tests (i.e., Now, there's a fair chance that I'm misguided, but I wasn't even aware that it's possible to inject a script via STDIN into the PHP binary in the way PHPUnit does it. I've never seen that approach in any other project, so there's a good chance that this custom approach is the root cause of the weird behaviors we're facing (because apparently not well-tested/supported by PHP core either way). Thoughts? |
Need to step back and sleep over this. The newly added regression test unexpectedly runs against master (on Windows), but it freezes against the proposed stdout/stderr swap (on Windows). In fact, the isolated test case freezes before it even starts… That behavior is negated to the one I'm seeing sans isolation. |
Child process must produce > 4096 bytes on STDERR (e.g., a large PHP backtrace). For parity/completeness, do the same for STDIN and STDOUT.
Child process must produce > 4096 bytes on STDERR (e.g., a large PHP backtrace). For parity/completeness, do the same for STDIN and STDOUT.
Child process must produce > 4096 bytes on STDERR (e.g., a large PHP backtrace). For parity/completeness, do the same for STDIN and STDOUT.
…ever on Windows if the output is too large. See https://bugs.php.net/bug.php?id=51800
Child process must produce > 4096 bytes on STDERR (e.g., a large PHP backtrace).
…ever on Windows if the output is too large. See https://bugs.php.net/bug.php?id=51800
Alright - figuring this out was extremely painful. Ultimately, I had a brainwave and checked whether and how Symfony Process is doing it — turns out they ran into the same problem and equally implemented the only workaround that works: Using a temporary file handle for either STDOUT or STDERR, instead of a pipe. Their code apparently also points to https://bugs.php.net/bug.php?id=51800, so that's indeed the canonical bug report. Up until this week, I totally wasn't aware that Speaking of, I think we should consider to replace all of PHPUnit's custom process handling with the Symfony Process component - it certainly doesn't make sense to duplicate efforts and figure out this (hair-pulling) stuff more than once. Separate issue though. For now, I'd recommend to move forward with this PR. |
Ouch. Latest build passed on all versions, except hhvm. hhvm seems to print STDERR during test execution already, and doesn't print "Call stack:" before a call stack of a fatal error. |
Sorry for all the noise, rebasing, etc. Tests finally pass on all platforms, including Windows (locally). Ready to merge from my perspective. Created #1342 to potentially replace this code with Symfony Process. |
No worries, @sun. All of this information has been very helpful for me :) |
@whatthejeff In that case, I probably should have pushed all of my countless local debugging iteration steps and should only have performed a squash/rebase last prior to a potential merge, in order to share the maximum info instead of only the result… — Anyway, consider me exhausted ;) |
Well your hard work is very much appreciated. I hate to ask one more thing, but would you mind opening a new PR against the |
Oh, isaacs/github#18 is still not possible? Sad panda. Sure, let me do that in a moment — I assume it's OK to leave the issue #id of the regression tests as-is. |
Yeah, the regression tests are fine as-is. |
Child process must produce > 4096 bytes on STDERR (e.g., a large PHP backtrace).
…ever on Windows if the output is too large. See https://bugs.php.net/bug.php?id=51800
- Fix process isolation infinitely blocks on Windows. sebastianbergmann/phpunit#1340 - Fix standard IO streams are not defined in process isolation. sebastianbergmann/phpunit#1348 - Fix isolated child process leaks into parent process. sebastianbergmann/phpunit#1351
- Fix process isolation infinitely blocks on Windows. sebastianbergmann/phpunit#1340 - Fix standard IO streams are not defined in process isolation. sebastianbergmann/phpunit#1348 - Fix isolated child process leaks into parent process. sebastianbergmann/phpunit#1351
- Fix process isolation infinitely blocks on Windows. sebastianbergmann/phpunit#1340 - Fix standard IO streams are not defined in process isolation. sebastianbergmann/phpunit#1348 - Fix isolated child process leaks into parent process. sebastianbergmann/phpunit#1351
- Fix process isolation infinitely blocks on Windows. sebastianbergmann/phpunit#1340 - Fix standard IO streams are not defined in process isolation. sebastianbergmann/phpunit#1348 - Fix isolated child process leaks into parent process. sebastianbergmann/phpunit#1351
When using
--process-isolation
,@runTestsInSeparateProcess
, or@runInSeparateProcess
and the child process terminates with a PHP Fatal Error, the parent process blocks infinitely on Windows.The fix is to read
stderr
beforestdout
.cf. https://bugs.php.net/bug.php?id=51800:
stderr
contains the fatal error,stdout
is empty. I had to debug this phantom for the past two days…:After
stdin
is written/processed and closed,stream_get_meta_data($pipes[1])
reports thatstdout
still appears to be present:But as soon as
$stdout = stream_get_contents($pipes[1])
is called, the script hangs.Changing the order in which
$stdout
and$stderr
are read suddenly fixes the problem, andstream_get_meta_data()
reports the same info for both pipes.All of the usual resolutions for this common problem did not work in this case, and I'm not able to explain why. However, I'd like to make clear that I did try all of the below:
proc_open()
on Windows.D'oh. Somehow I didn't notice the first PHP bug link. #51800 fully cuts it. Nothing further to add to that.
Since the bug is still unresolved, I'd recommend to move forward with this PR as-is and just simply change the order in which stderr and stdout are read.