-
Notifications
You must be signed in to change notification settings - Fork 71
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
Better handle startup failures - do not leave dangling processes #201
Conversation
This allows calling just the killing part of quit in a subsequent commit. This only moves some code, without changing behaviour.
Codecov Report
@@ 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.
|
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.
2dc891e
to
932e1db
Compare
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 :-) |
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! |
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: |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) 👍
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 :) |
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.
Todos
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.