Skip to content
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

Better handle startup failures - do not leave dangling processes #201

Merged
merged 3 commits into from
Mar 16, 2020

Conversation

matthijskooijman
Copy link
Contributor

Description

This PR fixes #196, by making sure that if any startup failures occur after the omxplayer process is started, that the omxplayer process is killed before raising an exception. This prevents a situation where an omxplayer might be running in the background, without any reference to its pid to kill or control it.

  • Status: READY*

Todos

  • Tests
  • Documentation (Not needed I think, except maybe a changelog?)

Steps to Test or Reproduce

Without this commit, if you let DBusConnection.__init__ raise DBusException("Foo") and then start a player, the constructor will throw an exception but leave omxplayer running in the background.

This allows calling just the killing part of quit in a subsequent
commit. This only moves some code, without changing behaviour.
@codecov
Copy link

codecov bot commented Feb 15, 2020

Codecov Report

Merging #201 into master will increase coverage by 1.77%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #201      +/-   ##
==========================================
+ Coverage   72.05%   73.82%   +1.77%     
==========================================
  Files           6        6              
  Lines         458      470      +12     
  Branches       25       26       +1     
==========================================
+ Hits          330      347      +17     
+ Misses        117      113       -4     
+ Partials       11       10       -1     

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 625a396...932e1db. Read the comment docs.

Before, if a process was successfully started, but there would be a
problem in subsequent setup (in particular the dbus connection could
fail), the process could keep running in the background.

This was particularly problematic when this caused the OMXPlayer
constructor to raise an exception, since then no player instance would
be created so the process pid would be lost.

This commit ensures that whenever the OMXPlayer constructor or load
method throws, no process will be dangling in the background.

This fixes willprice#196
This adds test coverage for two exception handling cases.
@matthijskooijman
Copy link
Contributor Author

w00ps, misclicked the close button.

Anyway, based on the code coverage report, I added three more testcases (one amended into the second comment, two unrelated ones added in a separate commit). Code coverage should now improve rather then be reduced by this PR :-)

@willprice
Copy link
Owner

Thanks for this @matthijskooijman, it looks really good. I don't have much time this week to look at this. I'd like to test it out myself before merging. I'll try and do that next week.

Thanks again for your contribution!

@matthijskooijman
Copy link
Contributor Author

matthijskooijman commented Feb 20, 2020

I don't have much time this week to look at this. I'd like to test it out myself before merging. I'll try and do that next week.

I know very well how that works, so don't worry (but thanks for replying anyway!)
:-)

args=(self, process, on_exit))
self._process_monitor.start()
return process
except:
Copy link
Owner

Choose a reason for hiding this comment

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

Is there any chance we can make this except block a bit more specific rather than just a catch all? Can we catch ThreadError and/or RuntimeError?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realize that the common wisdom is to catch errors specifically, but that only applies when your handling code responds to a particular error. In this case, the handling code is cleanup that should run whenever the code in question raises an exception, regardless of which one exactly (since then the contract is: When an exception is raised, no process is left running).

Copy link
Owner

Choose a reason for hiding this comment

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

OK, fair enough--good point.

os.killpg(process_group_id, signal.SIGTERM)
logger.debug('SIGTERM Sent to pid: %s' % process_group_id)
except OSError:
logger.error('Could not find the process to kill')
Copy link
Owner

Choose a reason for hiding this comment

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

Under what circumstances might we get an OSError? If process_group_id no longer exists by the time os.killpg occurs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I've seen exactly that, yes. Also, this is just code that was already there, I just moved it. :-)

if pause:
time.sleep(0.5) # Wait for the DBus interface to be initialised
self.pause()
except:
Copy link
Owner

Choose a reason for hiding this comment

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

Same comments as above RE exception specificity

if self._process:
self._terminate_process(self._process)
self._process = None
raise
Copy link
Owner

Choose a reason for hiding this comment

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

Don't we need to catch the exception, get the traceback info via sys.exc_info() and reraise that (see https://stackoverflow.com/questions/18188563/how-to-re-raise-an-exception-in-nested-try-except-blocks)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think Python handles this already by saying something like "While handling this exception, another exception occured", or "WHich was the direct cause of this exception", something like that. We could try do reproduce the original exception exactly, but that might just make debugging more complicated if it looks like the exception handler never ran?

Copy link
Owner

Choose a reason for hiding this comment

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

Yep, you're right, I didn't realise this was also supported in py 2.7 (https://docs.python.org/2.7/tutorial/errors.html section 8.3) 👍

@willprice
Copy link
Owner

Thanks again @matthijskooijman. This is great and thanks for putting the effort into writing the tests 👍. I've pulled down your branch and run the tests and things seem good (although there are a few spurious errors in the integration tests, I don't think they're anything to do with your changes, I'll have a look into them separately).

Sorry it's taken me so long to look at this, but I hope to merge this in this week :)

@willprice willprice merged commit cfdf99f into willprice:master Mar 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Might leave untracked process on dbus connection error
2 participants