Skip to content

[Process] fixed fatal errors in getOutput and getErrorOutput when process was not started #9452

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

Closed
wants to merge 2 commits into from

Conversation

max-voloshyn
Copy link
Contributor

Q A
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #10022
License MIT
Doc PR

Added logic exceptions for prevent fatal errors in getOutput and getErrorOutput when process was not started.

Fatal error: Call to a member function readAndCloseHandles() on a non-object in /home/max/symfony/src/Symfony/Component/Process/Process.php on line 1117

Call Stack:
    0.0002     231816   1. {main}() /home/max/phpunit/composer/bin/phpunit:0
    0.0038     526688   2. PHPUnit_TextUI_Command::main() /home/max/phpunit/composer/bin/phpunit:63
    0.0038     526912   3. PHPUnit_TextUI_Command->run() /home/max/phpunit/PHPUnit/TextUI/Command.php:126
    0.2261    4022696   4. PHPUnit_TextUI_TestRunner->doRun() /home/max/phpunit/PHPUnit/TextUI/Command.php:173
    0.2302    4353544   5. PHPUnit_Framework_TestSuite->run() /home/max/phpunit/PHPUnit/TextUI/TestRunner.php:415
    0.3218    5675504   6. PHPUnit_Framework_TestSuite->run() /home/max/phpunit/PHPUnit/Framework/TestSuite.php:729
   15.1171   38257632   7. PHPUnit_Framework_TestCase->run() /home/max/phpunit/PHPUnit/Framework/TestSuite.php:729
   15.1172   38257632   8. PHPUnit_Framework_TestResult->run() /home/max/phpunit/PHPUnit/Framework/TestCase.php:782
   15.1173   38258544   9. PHPUnit_Framework_TestCase->runBare() /home/max/phpunit/PHPUnit/Framework/TestResult.php:661
   15.1174   38275544  10. PHPUnit_Framework_TestCase->runTest() /home/max/phpunit/PHPUnit/Framework/TestCase.php:839
   15.1175   38276408  11. ReflectionMethod->invokeArgs() /home/max/phpunit/PHPUnit/Framework/TestCase.php:987
   15.1175   38276440  12. Symfony\Component\Process\Tests\AbstractProcessTest->testGetOutputProcessNotRunning() /home/max/phpunit/PHPUnit/Framework/TestCase.php:987
   15.1175   38277728  13. Symfony\Component\Process\Process->getOutput() /home/max/symfony/src/Symfony/Component/Process/Tests/AbstractProcessTest.php:636
   15.1175   38277824  14. Symfony\Component\Process\Process->readPipes() /home/max/symfony/src/Symfony/Component/Process/Process.php:383

* @return string The process output
*
* @api
*/
public function getOutput()
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('%s does not make sense if command is not started', __FUNCTION__));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Process must be started before calling __FUNCTION__

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited Command -> Process

@max-voloshyn
Copy link
Contributor Author

@Tobion thanks, code will be fixed

@@ -401,12 +406,17 @@ public function getIncrementalOutput()
/**
* Returns the current error output of the process (STDERR).
*
* @throws Exception\LogicException

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @Tobion said - throws after return with empty line between. You can find an example in here http://symfony.com/doc/current/contributing/code/standards.html

@max-voloshyn
Copy link
Contributor Author

@Tobion, @piotrpasich, thanks!
Fixed and squashed.

@Tobion
Copy link
Contributor

Tobion commented Nov 6, 2013

Looking at the process code, it looks to me that there will also be PHP errors when calling some other methods before starting the process. For example hasBeenStopped will access $this->processInformation['stopped'] which IMO is not set when the process is not started. Would be a similar problem to the one you fixed here. Maybe you can have a look at them as well?

@max-voloshyn
Copy link
Contributor Author

OK, I will look at them on weekend.

@max-voloshyn
Copy link
Contributor Author

I have noticed that many similar problems are beginning in updateStatus method when we do:

if (self::STATUS_STARTED !== $this->status) {
    return;
}

Maybe it would be better to throw logic exception instead of simple return, because we really can't update status of not started process.

Thus we can don't throw logic exceptions in all dependent methods.
Only disadvantage I see here is less verbose of exception message.
But on the other hand it is better than we are having now.

@Tobion, what you think about it?

@max-voloshyn
Copy link
Contributor Author

Another opinions will come in handy :)

@@ -371,10 +371,16 @@ public function signal($signal)
*
* @return string The process output
*
* @throws Exception\LogicException In case the process is not started
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception\LogicException does not correspond with the class used in the code (LogicException) on of those should be changed (same below)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @max-voloshin

@jakzal
Copy link
Contributor

jakzal commented Dec 27, 2013

@max-voloshin do you have plans to finish this PR?

@max-voloshyn max-voloshyn deleted the fix_fatals branch December 28, 2013 12:11
@max-voloshyn max-voloshyn reopened this Dec 28, 2013
@max-voloshyn
Copy link
Contributor Author

@jakzal , finish:)

Similar problems can be fixed in another PR, if anyone has enough experience for this.
I have some thoughts: #9452 (comment), but I am not sure.. So I left it as is.

@fabpot
Copy link
Member

fabpot commented Jan 17, 2014

@romainneutron Can you check this PR please?

@romainneutron
Copy link
Contributor

This looks good to me.

@max-voloshin FYI the updateStatus method might be renamed updateStatusIfRunning. This method should not throw an exception otherwise you would broke a lot of the process logic

@romainneutron
Copy link
Contributor

Humm, wait.

The check on $this->isStarted() might be wrong. $this->isStarted() would return false in case the process is terminated (once it has stop).

I'm doing some tests right now

@romainneutron
Copy link
Contributor

this piece of code should fail (and actually does not) :

$process = new Process('php -m');
$process->run();
$process->wait();

@max-voloshyn
Copy link
Contributor Author

@romainneutron , thanks! I will add tests for this and improve code.

*/
public function wait($callback = null)
{
if (!$this->isStarted()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be if (!$this->isStarted() || $this->status === self::STATUS_TERMINATED)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romainneutron, with this change I break testTTYCommand, because in start method we have

if ($this->tty) {
    $this->status = self::STATUS_TERMINATED;
    return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(
I've to admit the TTY mode is something I don't know very well. I'm gonna dig this tonight

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello and sorry for the late reply,

I've justed submitted #10412 that should make this change doable and your PR mergeable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romainneutron, thanks! I will wait for merging your PR and then rebase my branch with 2.3

@fabpot
Copy link
Member

fabpot commented Mar 10, 2014

#10412 has been merged now.

@Tobion
Copy link
Contributor

Tobion commented Mar 10, 2014

My remark #9452 (comment) at is still open and I think we should not merge this before finding a general solution.

@max-voloshin I basically agree with #9452 (comment) but it doesnt work universally. In getStatus for example it should not throw an exception when calling updateStatus. So one has to be careful to apply it in class.

@romainneutron
Copy link
Contributor

Hello @Tobion, thanks for your comment.
It actually does not produce any PHP error as long as the property is null.

The following code:

error_reporting(-1);
ini_set('display_errors', 'on');
$a = null;
var_dump($a['key']);

produces :

NULL

whereas (note that $a is now an array)

error_reporting(-1);
ini_set('display_errors', 'on');
$a = array();
var_dump($a['key']);

produces :

PHP Notice:  Undefined index: key in /tmp/test.php on line 4
PHP Stack trace:
PHP   1. {main}() /tmp/test.php:0

So the following code should not produce any error:

$process = new Process('php -m');
$process->getTermSignal();

@romainneutron
Copy link
Contributor

I've done a review of the methods and here's what could be fixed:

  • Process::getExitCode, Process::getExitCodeText and Process::isSuccessful return null when the process has not started yet, it should throw an exception as well as for getErrorOutput.

What about other methods that point to the final status of the process such as Process::getTermSignal, Process::hasBeenStopped, etc...? I think we should do the same as for the methods above and what you did.

  • checkTimeout throws a timeout exception when the process is not started yet or already terminated, it could be fixed with:
    public function checkTimeout()
    {
        if ($this->status !== self::STATUS_STARTED) {
            return;
        }
        // ...
    }

@Tobion
Copy link
Contributor

Tobion commented Mar 12, 2014

@romainneutron Thanks for the information. I didn't know that. One can learn php inconsistencies even after many years..

But even when it does not throw an error, the methods will not return the expected values. So hasBeenSignaled can also return null at the moment, instead of a Boolean as documented.

@romainneutron
Copy link
Contributor

right, do you think we should cast to a boolean or throw an exception in case the process has not run ?
I think we should throw an exception to be consistent with other methods.

@Tobion
Copy link
Contributor

Tobion commented Mar 12, 2014

I think we should throw an exception to be consistent with other methods.

Agreed

@fabpot
Copy link
Member

fabpot commented Mar 18, 2014

Closing in favor of #10480

@fabpot fabpot closed this Mar 18, 2014
fabpot added a commit that referenced this pull request Mar 18, 2014
…ut when process was not started (romainneutron)

This PR was merged into the 2.3 branch.

Discussion
----------

[Process] Fixed fatal errors in getOutput and getErrorOutput when process was not started

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #10022
| License       | MIT

This PR replaces #9452 and address the latest changes.
Side note : I've not updated `getExitCode`, `getExitCodeText` and `isSuccessful` as they were explicitly tested to return null in case the process is not started or terminated. I think it would be a BC break to do that.

Commits
-------

449fe01 [Process] Trow exceptions in case a Process method is supposed to be called after termination
0ae6858 [Process] fixed fatal errors in getOutput and getErrorOutput when process was not started
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants