utils: handle child exceptions in fork_call() more gracefully
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Thu, 3 Apr 2014 13:43:39 +0000 (16:43 +0300)
committerMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Tue, 8 Apr 2014 09:17:46 +0000 (12:17 +0300)
Create a new exception type GbpChildBTError for handling exceptions that
happen in the function that is called in fork_call(). Allows us to catch
exceptions in the called "child" function and print out correct
traceback.

Change-Id: Ib99630df0fa788dc0ec86450460f9daede0a4f59
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
obs_service_gbp_utils/__init__.py
tests/test_obs_service_gbp_utils.py

index 0d67aeb9a5ea9fbd111a1a02d89a77bbf0f3d8d9..9a2cc89a530414418a82e6cc3a37a0bea4a6f84d 100644 (file)
@@ -24,7 +24,7 @@ import grp
 import pwd
 import sys
 from multiprocessing import Process, Queue
-
+from traceback import format_exception_only, extract_tb, format_list
 
 _RET_FORK_OK = 0
 _RET_FORK_ERR = 1
@@ -34,6 +34,22 @@ class GbpServiceError(Exception):
     """General error class for the source service"""
     pass
 
+class GbpChildBTError(Exception):
+    """Exception for handling unhandled child exceptions in fork_call()"""
+    def __init__(self, *args):
+        self.typ, self.val, traceback = sys.exc_info()
+        self.tb_list = extract_tb(traceback)
+        super(GbpChildBTError, self).__init__(*args)
+
+    def prettyprint_tb(self):
+        """Get traceback in a format easier to comprehend"""
+        child_tb = format_list(self.tb_list)
+        child_tb += format_exception_only(self.typ, self.val)
+        sep = '-' * 4 + ' CHILD TRACEBACK ' + '-' * 50 + '\n'
+        pp_tb = sep + ''.join(child_tb) + sep
+        return pp_tb
+
+
 def _demoted_child_call(uid, gid, ret_data_q, func, args, kwargs):
     """Call a function/method with different uid/gid"""
     # Set UID and GID
@@ -52,7 +68,7 @@ def _demoted_child_call(uid, gid, ret_data_q, func, args, kwargs):
     try:
         ret = func(*args, **kwargs)
     except Exception as err:
-        ret_data_q.put(err)
+        ret_data_q.put(GbpChildBTError())
         sys.exit(_RET_FORK_ERR)
     else:
         ret_data_q.put(ret)
index 7bf0c99cda0ed7e8fe7f621b786ffed23c85455b..a8f20162ab42e5c8cd392413b261fea7baa758be 100644 (file)
@@ -27,7 +27,8 @@ from multiprocessing import Queue
 
 from gbp_repocache import MirrorGitRepository
 from obs_service_gbp_utils import fork_call, _demoted_child_call, _RET_FORK_OK
-from obs_service_gbp_utils import GbpServiceError, write_treeish_meta
+from obs_service_gbp_utils import write_treeish_meta
+from obs_service_gbp_utils import GbpServiceError, GbpChildBTError
 
 from tests import UnitTestsBase
 
@@ -85,11 +86,13 @@ class TestForkCall(object):
 
     def test_fail(self):
         """Tests for function call failures"""
-        with assert_raises(_DummyException):
+        with assert_raises(GbpChildBTError) as exc:
             fork_call(None, None, self._dummy_raise)
+        eq_(exc.exception.typ, _DummyException)
 
-        with assert_raises(TypeError):
+        with assert_raises(GbpChildBTError) as exc:
             fork_call(None, None, self._dummy_ok, 'unexptected_arg')
+        eq_(exc.exception.typ, TypeError)
 
     def test_demoted_call_no(self):
         """Test running with different UID/GID"""
@@ -110,8 +113,9 @@ class TestForkCall(object):
             self._no_fork_call(99999, None, self._dummy_ok)
         with assert_raises(GbpServiceError):
             self._no_fork_call(None, 99999, self._dummy_ok)
-        with assert_raises(_DummyException):
+        with assert_raises(GbpChildBTError) as exc:
             self._no_fork_call(self._uid, self._gid, self._dummy_raise)
+        eq_(exc.exception.typ, _DummyException)
 
 class TestGitMeta(UnitTestsBase):
     """Test writing treeish meta into a file"""