Merge lp:~bjornt/landscape-client/disable-apport-dpkg-crash into lp:~landscape/landscape-client/trunk

Proposed by Björn Tillenius
Status: Merged
Approved by: Jerry Seutter
Approved revision: 589
Merged at revision: 589
Proposed branch: lp:~bjornt/landscape-client/disable-apport-dpkg-crash
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 138 lines (+84/-2)
2 files modified
landscape/package/facade.py (+28/-0)
landscape/package/tests/test_facade.py (+56/-2)
To merge this branch: bzr merge lp:~bjornt/landscape-client/disable-apport-dpkg-crash
Reviewer Review Type Date Requested Status
Jerry Seutter (community) Approve
Chris Glass (community) Approve
Review via email: mp+135153@code.launchpad.net

Description of the change

Prevent Apport from generating crash reports when dpkg returns an error.

When dpkg returns an error, a SystemError is raised and the error is
being caught and cleaned up in the Apt C binding. However, the Apport
hook was still being executed, generating a crash reports, even though
nothing crashed.

Now we install a custom excepthook, which passes all errors to Apport,
except for SystemErrrors. We install the excepthook only in the child
process that executes dpkg after a fork.

To post a comment you must log in.
Revision history for this message
Chris Glass (tribaal) wrote :

Looks good! +1

review: Approve
Revision history for this message
Jerry Seutter (jseutter) wrote :

+1 looks good :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/package/facade.py'
2--- landscape/package/facade.py 2012-05-08 23:54:37 +0000
3+++ landscape/package/facade.py 2012-11-20 13:45:23 +0000
4@@ -1,6 +1,7 @@
5 import hashlib
6 import os
7 import subprocess
8+import sys
9 import tempfile
10 from cStringIO import StringIO
11 from operator import attrgetter
12@@ -59,6 +60,7 @@
13 class LandscapeInstallProgress(InstallProgress):
14
15 dpkg_exited = None
16+ old_excepthook = None
17
18 def wait_child(self):
19 """Override to find out whether dpkg exited or not.
20@@ -76,6 +78,32 @@
21 self.dpkg_exited = os.WIFEXITED(res)
22 return res
23
24+ def fork(self):
25+ """Fork and override the excepthook in the child process."""
26+ pid = super(LandscapeInstallProgress, self).fork()
27+ if pid == 0:
28+ # No need to clean up after ourselves, since the child
29+ # process will die after dpkg has been run.
30+ self.old_excepthook = sys.excepthook
31+ sys.excepthook = self._prevent_dpkg_apport_error
32+ return pid
33+
34+ def _prevent_dpkg_apport_error(self, exc_type, exc_obj, exc_tb):
35+ """Prevent dpkg errors from generating Apport crash reports.
36+
37+ When dpkg reports an error, a SystemError is raised and cleaned
38+ up in C code. However, it seems like the Apport except hook is
39+ called before the C code clears the error, generating crash
40+ reports even though nothing crashed.
41+
42+ This exception hook doesn't call the Apport hook for
43+ SystemErrors, but it calls it for all other errors.
44+ """
45+ if exc_type is SystemError:
46+ sys.__excepthook__(exc_type, exc_obj, exc_tb)
47+ return
48+ self.old_excepthook(exc_type, exc_obj, exc_tb)
49+
50
51 class AptFacade(object):
52 """Wrapper for tasks using Apt.
53
54=== modified file 'landscape/package/tests/test_facade.py'
55--- landscape/package/tests/test_facade.py 2012-05-08 23:21:07 +0000
56+++ landscape/package/tests/test_facade.py 2012-11-20 13:45:23 +0000
57@@ -1,4 +1,5 @@
58 import os
59+import sys
60 import textwrap
61 import tempfile
62
63@@ -9,7 +10,8 @@
64 from landscape.constants import UBUNTU_PATH
65 from landscape.lib.fs import read_file, create_file
66 from landscape.package.facade import (
67- TransactionError, DependencyError, ChannelError, AptFacade)
68+ TransactionError, DependencyError, ChannelError, AptFacade,
69+ LandscapeInstallProgress)
70
71 from landscape.tests.mocker import ANY
72 from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper
73@@ -1045,12 +1047,64 @@
74
75 This test executes dpkg for real, which should fail, complaining
76 that superuser privileges are needed.
77- """
78+
79+ The error from the dpkg sub process is included.
80+ """
81+ self._add_system_package("foo")
82+ self.facade.reload_channels()
83+ foo = self.facade.get_packages_by_name("foo")[0]
84+ self.facade.mark_remove(foo)
85+ exception = self.assertRaises(
86+ TransactionError, self.facade.perform_changes)
87+ self.assertIn("Error in function: \nSystemError:", exception.args[0])
88+
89+ def test_perform_changes_dpkg_error_retains_excepthook(self):
90+ """
91+ We install a special excepthook when preforming package
92+ operations, to prevent Apport from generating crash reports when
93+ dpkg returns a failure. It's only installed when doing the
94+ actual package operation, so the original excepthook is there
95+ after the perform_changes() method returns.
96+ """
97+ old_excepthook = sys.excepthook
98 self._add_system_package("foo")
99 self.facade.reload_channels()
100 foo = self.facade.get_packages_by_name("foo")[0]
101 self.facade.mark_remove(foo)
102 self.assertRaises(TransactionError, self.facade.perform_changes)
103+ self.assertIs(old_excepthook, sys.excepthook)
104+
105+ def test_prevent_dpkg_apport_error_system_error(self):
106+ """
107+ C{_prevent_dpkg_apport_error} prevents the Apport excepthook
108+ from being called when a SystemError happens, since SystemErrors
109+ are expected to happen and will be caught in the Apt C binding..
110+ """
111+ hook_calls = []
112+
113+ progress = LandscapeInstallProgress()
114+ progress.old_excepthook = (
115+ lambda exc_type, exc_value, exc_tb: hook_calls.append(
116+ (exc_type, exc_value, exc_tb)))
117+ progress._prevent_dpkg_apport_error(
118+ SystemError, SystemError("error"), object())
119+ self.assertEqual([], hook_calls)
120+
121+ def test_prevent_dpkg_apport_error_non_system_error(self):
122+ """
123+ If C{_prevent_dpkg_apport_error} gets an exception that isn't a
124+ SystemError, the old Apport hook is being called.
125+ """
126+ hook_calls = []
127+
128+ progress = LandscapeInstallProgress()
129+ progress.old_excepthook = (
130+ lambda exc_type, exc_value, exc_tb: hook_calls.append(
131+ (exc_type, exc_value, exc_tb)))
132+ error = object()
133+ traceback = object()
134+ progress._prevent_dpkg_apport_error(Exception, error, traceback)
135+ self.assertEqual([(Exception, error, traceback)], hook_calls)
136
137 def test_perform_changes_dpkg_exit_dirty(self):
138 """

Subscribers

People subscribed via source and target branches

to all changes: