diff options
author | Daniel P. Berrange <berrange@redhat.com> | 2015-07-02 14:49:12 +0100 |
---|---|---|
committer | abhishekkekane <abhishek.kekane@nttdata.com> | 2015-08-03 02:16:39 -0700 |
commit | 397729bca2e58433d4dbe09a3fba51887b2d0966 (patch) | |
tree | 7ffe5e42951a7574e791af5ff00dd320f3580d14 | |
parent | be38e4420f5c16b2b3641523504a23bb7bc002f9 (diff) | |
download | oslo-concurrency-397729bca2e58433d4dbe09a3fba51887b2d0966.tar.gz |
processutils: ensure on_completion callback is always called
If the subprocess.Popen.communicate method raises an exception,
the on_completion callback is never invoked. If a caller is
trying to use on_execute + on_completion to track lifecycle
of a process this creates a problem, as they cannot reliably
detect completion.
Conflicts:
oslo/concurrency/processutils.py
tests/unit/test_processutils.py
Change-Id: I22b2d7bde8797276f7670bc289d915dab5122481
Closes-bug: #1470868
(cherry picked from commit ab78480659834ed07c84f8569df5d2c27965af00)
-rw-r--r-- | oslo/concurrency/processutils.py | 21 | ||||
-rw-r--r-- | tests/unit/test_processutils.py | 19 |
2 files changed, 30 insertions, 10 deletions
diff --git a/oslo/concurrency/processutils.py b/oslo/concurrency/processutils.py index 240d296..174270c 100644 --- a/oslo/concurrency/processutils.py +++ b/oslo/concurrency/processutils.py @@ -223,16 +223,17 @@ def execute(*cmd, **kwargs): if on_execute: on_execute(obj) - result = obj.communicate(process_input) - - obj.stdin.close() # pylint: disable=E1101 - _returncode = obj.returncode # pylint: disable=E1101 - end_time = time.time() - start_time - LOG.log(loglevel, 'CMD "%s" returned: %s in %ss' % - (sanitized_cmd, _returncode, end_time)) - - if on_completion: - on_completion(obj) + try: + result = obj.communicate(process_input) + + obj.stdin.close() # pylint: disable=E1101 + _returncode = obj.returncode # pylint: disable=E1101 + end_time = time.time() - start_time + LOG.log(loglevel, 'CMD "%s" returned: %s in %ss' % + (sanitized_cmd, _returncode, end_time)) + finally: + if on_completion: + on_completion(obj) if not ignore_exit_code and _returncode not in check_exit_code: (stdout, stderr) = result diff --git a/tests/unit/test_processutils.py b/tests/unit/test_processutils.py index 903c2ed..d0e689e 100644 --- a/tests/unit/test_processutils.py +++ b/tests/unit/test_processutils.py @@ -20,6 +20,7 @@ import logging import multiprocessing import os import stat +import subprocess import tempfile import fixtures @@ -72,6 +73,24 @@ class UtilsTest(test_base.BaseTestCase): self.assertEqual(1, on_execute_callback.call_count) self.assertEqual(1, on_completion_callback.call_count) + @mock.patch.object(subprocess.Popen, "communicate") + def test_execute_with_callback_and_errors(self, mock_comm): + on_execute_callback = mock.Mock() + on_completion_callback = mock.Mock() + + def fake_communicate(*args): + raise IOError("Broken pipe") + + mock_comm.side_effect = fake_communicate + + self.assertRaises(IOError, + processutils.execute, + "/bin/true", + on_execute=on_execute_callback, + on_completion=on_completion_callback) + self.assertEqual(1, on_execute_callback.call_count) + self.assertEqual(1, on_completion_callback.call_count) + class ProcessExecutionErrorTest(test_base.BaseTestCase): |