Merge ~cjwatson/launchpad:py3-traceback-reference-cycles into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 695d975ad2d8d9e66f3340a0b6e3f3784437ae13
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:py3-traceback-reference-cycles
Merge into: launchpad:master
Diff against target: 443 lines (+157/-93)
12 files modified
lib/lp/app/widgets/date.py (+6/-2)
lib/lp/bugs/mail/handler.py (+6/-1)
lib/lp/bugs/tests/test_bzremotecomponentfinder.py (+5/-1)
lib/lp/code/mail/codehandler.py (+17/-13)
lib/lp/code/model/branchhosting.py (+7/-3)
lib/lp/code/model/githosting.py (+7/-3)
lib/lp/code/xmlrpc/git.py (+79/-51)
lib/lp/oci/model/ociregistryclient.py (+16/-12)
lib/lp/services/gpg/handler.py (+2/-4)
lib/lp/services/looptuner.py (+5/-1)
lib/lp/services/timeout.py (+5/-1)
lib/lp/services/webapp/adapter.py (+2/-1)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+402724@code.launchpad.net

Commit message

Avoid various traceback reference cycles

Description of the change

On Python 3, exceptions have their associated traceback as a `__traceback__` attribute, which means that it's possible to end up with reference cycles by storing exception objects in variables local to frames that are part of their traceback. Delete those local variables before returning to avoid various cases of this.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/app/widgets/date.py b/lib/lp/app/widgets/date.py
2index dd8ef78..85d0756 100644
3--- a/lib/lp/app/widgets/date.py
4+++ b/lib/lp/app/widgets/date.py
5@@ -348,8 +348,12 @@ class DateTimeWidget(TextWidget):
6 failure = e
7 else:
8 return
9- if failure:
10- raise ConversionError('Invalid date value', failure)
11+ try:
12+ if failure:
13+ raise ConversionError('Invalid date value', failure)
14+ finally:
15+ # Avoid traceback reference cycles.
16+ del failure
17
18 def _toFieldValue(self, input):
19 """Return parsed input (datetime) as a date."""
20diff --git a/lib/lp/bugs/mail/handler.py b/lib/lp/bugs/mail/handler.py
21index b64b815..3a151e2 100644
22--- a/lib/lp/bugs/mail/handler.py
23+++ b/lib/lp/bugs/mail/handler.py
24@@ -257,6 +257,8 @@ class MaloneHandler:
25 def process(self, signed_msg, to_addr, filealias=None, log=None):
26 """See IMailHandler."""
27
28+ processing_errors = []
29+
30 try:
31 (final_result, add_comment_to_bug,
32 commands, ) = self.extractAndAuthenticateCommands(
33@@ -269,7 +271,6 @@ class MaloneHandler:
34 bugtask = None
35 bugtask_event = None
36
37- processing_errors = []
38 while len(commands) > 0:
39 command = commands.pop(0)
40 try:
41@@ -332,6 +333,10 @@ class MaloneHandler:
42 'Submit Request Failure',
43 error.message, signed_msg, error.failing_command)
44
45+ finally:
46+ # Avoid traceback reference cycles.
47+ del processing_errors
48+
49 return True
50
51 def sendHelpEmail(self, to_address):
52diff --git a/lib/lp/bugs/tests/test_bzremotecomponentfinder.py b/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
53index 13faf66..767d84b 100644
54--- a/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
55+++ b/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
56@@ -104,7 +104,11 @@ class TestBugzillaRemoteComponentFinder(TestCaseWithFactory):
57 finder.getRemoteProductsAndComponents()
58 except Exception as e:
59 asserted = e
60- self.assertIs(None, asserted)
61+ try:
62+ self.assertIs(None, asserted)
63+ finally:
64+ # Avoid traceback reference cycles.
65+ del asserted
66
67 @responses.activate
68 def test_store(self):
69diff --git a/lib/lp/code/mail/codehandler.py b/lib/lp/code/mail/codehandler.py
70index 508fb46..fb44573 100644
71--- a/lib/lp/code/mail/codehandler.py
72+++ b/lib/lp/code/mail/codehandler.py
73@@ -262,19 +262,23 @@ class CodeHandler:
74 def processCommands(self, context, commands):
75 """Process the various merge proposal commands against the context."""
76 processing_errors = []
77- with BranchMergeProposalNoPreviewDiffDelta.monitor(
78- context.merge_proposal):
79- for command in commands:
80- try:
81- command.execute(context)
82- except EmailProcessingError as error:
83- processing_errors.append((error, command))
84-
85- if len(processing_errors) > 0:
86- errors, commands = zip(*processing_errors)
87- raise IncomingEmailError(
88- '\n'.join(str(error) for error in errors),
89- list(commands))
90+ try:
91+ with BranchMergeProposalNoPreviewDiffDelta.monitor(
92+ context.merge_proposal):
93+ for command in commands:
94+ try:
95+ command.execute(context)
96+ except EmailProcessingError as error:
97+ processing_errors.append((error, command))
98+
99+ if len(processing_errors) > 0:
100+ errors, commands = zip(*processing_errors)
101+ raise IncomingEmailError(
102+ '\n'.join(str(error) for error in errors),
103+ list(commands))
104+ finally:
105+ # Avoid traceback reference cycles.
106+ del processing_errors
107
108 return len(commands)
109
110diff --git a/lib/lp/code/model/branchhosting.py b/lib/lp/code/model/branchhosting.py
111index a245307..57e373c 100644
112--- a/lib/lp/code/model/branchhosting.py
113+++ b/lib/lp/code/model/branchhosting.py
114@@ -74,9 +74,13 @@ class BranchHostingClient:
115 raise
116 except Exception:
117 _, val, tb = sys.exc_info()
118- reraise(
119- RequestExceptionWrapper, RequestExceptionWrapper(*val.args),
120- tb)
121+ try:
122+ reraise(
123+ RequestExceptionWrapper,
124+ RequestExceptionWrapper(*val.args), tb)
125+ finally:
126+ # Avoid traceback reference cycles.
127+ del val, tb
128 finally:
129 action.finish()
130 if as_json:
131diff --git a/lib/lp/code/model/githosting.py b/lib/lp/code/model/githosting.py
132index 4f07f44..787e63e 100644
133--- a/lib/lp/code/model/githosting.py
134+++ b/lib/lp/code/model/githosting.py
135@@ -89,9 +89,13 @@ class GitHostingClient:
136 raise
137 except Exception:
138 _, val, tb = sys.exc_info()
139- reraise(
140- RequestExceptionWrapper, RequestExceptionWrapper(*val.args),
141- tb)
142+ try:
143+ reraise(
144+ RequestExceptionWrapper,
145+ RequestExceptionWrapper(*val.args), tb)
146+ finally:
147+ # Avoid traceback reference cycles.
148+ del val, tb
149 finally:
150 action.finish()
151 if response.content:
152diff --git a/lib/lp/code/xmlrpc/git.py b/lib/lp/code/xmlrpc/git.py
153index 21e728d..6e181c3 100644
154--- a/lib/lp/code/xmlrpc/git.py
155+++ b/lib/lp/code/xmlrpc/git.py
156@@ -454,14 +454,18 @@ class GitAPI(LaunchpadXMLRPCView):
157 result = run_with_login(
158 requester_id, self._translatePath,
159 six.ensure_text(path).strip("/"), permission, auth_params)
160- if isinstance(result, xmlrpc_client.Fault):
161- logger.error("translatePath failed: %r", result)
162- else:
163- # The results of path translation are not sensitive for logging
164- # purposes (they may refer to private artifacts, but contain no
165- # credentials).
166- logger.info("translatePath succeeded: %s", result)
167- return result
168+ try:
169+ if isinstance(result, xmlrpc_client.Fault):
170+ logger.error("translatePath failed: %r", result)
171+ else:
172+ # The results of path translation are not sensitive for
173+ # logging purposes (they may refer to private artifacts, but
174+ # contain no credentials).
175+ logger.info("translatePath succeeded: %s", result)
176+ return result
177+ finally:
178+ # Avoid traceback reference cycles.
179+ del result
180
181 @return_fault
182 def _notify(self, requester, translated_path, statistics, auth_params):
183@@ -498,11 +502,15 @@ class GitAPI(LaunchpadXMLRPCView):
184 result = run_with_login(
185 requester_id, self._notify,
186 translated_path, statistics, auth_params)
187- if isinstance(result, xmlrpc_client.Fault):
188- logger.error("notify failed: %r", result)
189- else:
190- logger.info("notify succeeded: %s" % result)
191- return result
192+ try:
193+ if isinstance(result, xmlrpc_client.Fault):
194+ logger.error("notify failed: %r", result)
195+ else:
196+ logger.info("notify succeeded: %s" % result)
197+ return result
198+ finally:
199+ # Avoid traceback reference cycles.
200+ del result
201
202 @return_fault
203 def _getMergeProposalURL(self, requester, translated_path, branch,
204@@ -536,14 +544,18 @@ class GitAPI(LaunchpadXMLRPCView):
205 result = run_with_login(
206 requester_id, self._getMergeProposalURL,
207 translated_path, branch, auth_params)
208- if isinstance(result, xmlrpc_client.Fault):
209- logger.error("getMergeProposalURL failed: %r", result)
210- else:
211- # The result of getMergeProposalURL is not sensitive for logging
212- # purposes (it may refer to private artifacts, but contains no
213- # credentials, only the merge proposal URL).
214- logger.info("getMergeProposalURL succeeded: %s" % result)
215- return result
216+ try:
217+ if isinstance(result, xmlrpc_client.Fault):
218+ logger.error("getMergeProposalURL failed: %r", result)
219+ else:
220+ # The result of getMergeProposalURL is not sensitive for
221+ # logging purposes (it may refer to private artifacts, but
222+ # contains no credentials, only the merge proposal URL).
223+ logger.info("getMergeProposalURL succeeded: %s" % result)
224+ return result
225+ finally:
226+ # Avoid traceback reference cycles.
227+ del result
228
229 @return_fault
230 def _authenticateWithPassword(self, username, password):
231@@ -571,15 +583,19 @@ class GitAPI(LaunchpadXMLRPCView):
232 logger.info(
233 "Request received: authenticateWithPassword('%s')", username)
234 result = self._authenticateWithPassword(username, password)
235- if isinstance(result, xmlrpc_client.Fault):
236- logger.error("authenticateWithPassword failed: %r", result)
237- else:
238- # The results of authentication may be sensitive, but we can at
239- # least log the authenticated user.
240- logger.info(
241- "authenticateWithPassword succeeded: %s",
242- result.get("uid", result.get("user")))
243- return result
244+ try:
245+ if isinstance(result, xmlrpc_client.Fault):
246+ logger.error("authenticateWithPassword failed: %r", result)
247+ else:
248+ # The results of authentication may be sensitive, but we can
249+ # at least log the authenticated user.
250+ logger.info(
251+ "authenticateWithPassword succeeded: %s",
252+ result.get("uid", result.get("user")))
253+ return result
254+ finally:
255+ # Avoid traceback reference cycles.
256+ del result
257
258 def _renderPermissions(self, set_of_permissions):
259 """Render a set of permission strings for XML-RPC output."""
260@@ -647,17 +663,21 @@ class GitAPI(LaunchpadXMLRPCView):
261 result = run_with_login(
262 requester_id, self._checkRefPermissions,
263 translated_path, ref_paths, auth_params)
264- if isinstance(result, xmlrpc_client.Fault):
265- logger.error("checkRefPermissions failed: %r", result)
266- else:
267- # The results of ref permission checks are not sensitive for
268- # logging purposes (they may refer to private artifacts, but
269- # contain no credentials).
270- logger.info(
271- "checkRefPermissions succeeded: %s",
272- [(ref_path.data, permissions)
273- for ref_path, permissions in result])
274- return result
275+ try:
276+ if isinstance(result, xmlrpc_client.Fault):
277+ logger.error("checkRefPermissions failed: %r", result)
278+ else:
279+ # The results of ref permission checks are not sensitive for
280+ # logging purposes (they may refer to private artifacts, but
281+ # contain no credentials).
282+ logger.info(
283+ "checkRefPermissions succeeded: %s",
284+ [(ref_path.data, permissions)
285+ for ref_path, permissions in result])
286+ return result
287+ finally:
288+ # Avoid traceback reference cycles.
289+ del result
290
291 def _validateRequesterCanManageRepoCreation(
292 self, requester, repository, auth_params):
293@@ -713,11 +733,15 @@ class GitAPI(LaunchpadXMLRPCView):
294 translated_path, auth_params)
295 except Exception as e:
296 result = e
297- if isinstance(result, xmlrpc_client.Fault):
298- logger.error("confirmRepoCreation failed: %r", result)
299- else:
300- logger.info("confirmRepoCreation succeeded: %s" % result)
301- return result
302+ try:
303+ if isinstance(result, xmlrpc_client.Fault):
304+ logger.error("confirmRepoCreation failed: %r", result)
305+ else:
306+ logger.info("confirmRepoCreation succeeded: %s" % result)
307+ return result
308+ finally:
309+ # Avoid traceback reference cycles.
310+ del result
311
312 def _abortRepoCreation(self, requester, translated_path, auth_params):
313 naked_repo = removeSecurityProxy(
314@@ -740,8 +764,12 @@ class GitAPI(LaunchpadXMLRPCView):
315 translated_path, auth_params)
316 except Exception as e:
317 result = e
318- if isinstance(result, xmlrpc_client.Fault):
319- logger.error("abortRepoCreation failed: %r", result)
320- else:
321- logger.info("abortRepoCreation succeeded: %s" % result)
322- return result
323+ try:
324+ if isinstance(result, xmlrpc_client.Fault):
325+ logger.error("abortRepoCreation failed: %r", result)
326+ else:
327+ logger.info("abortRepoCreation succeeded: %s" % result)
328+ return result
329+ finally:
330+ # Avoid traceback reference cycles.
331+ del result
332diff --git a/lib/lp/oci/model/ociregistryclient.py b/lib/lp/oci/model/ociregistryclient.py
333index 51b00af..f3e623b 100644
334--- a/lib/lp/oci/model/ociregistryclient.py
335+++ b/lib/lp/oci/model/ociregistryclient.py
336@@ -387,18 +387,22 @@ class OCIRegistryClient:
337 preloaded_data = cls._preloadFiles(build, manifest, digests)
338
339 exceptions = []
340- for push_rule in build.recipe.push_rules:
341- for tag in cls._calculateTags(build.recipe):
342- try:
343- cls._upload_to_push_rule(
344- push_rule, build, manifest, digests, preloaded_data,
345- tag)
346- except Exception as e:
347- exceptions.append(e)
348- if len(exceptions) == 1:
349- raise exceptions[0]
350- elif len(exceptions) > 1:
351- raise MultipleOCIRegistryError(exceptions)
352+ try:
353+ for push_rule in build.recipe.push_rules:
354+ for tag in cls._calculateTags(build.recipe):
355+ try:
356+ cls._upload_to_push_rule(
357+ push_rule, build, manifest, digests,
358+ preloaded_data, tag)
359+ except Exception as e:
360+ exceptions.append(e)
361+ if len(exceptions) == 1:
362+ raise exceptions[0]
363+ elif len(exceptions) > 1:
364+ raise MultipleOCIRegistryError(exceptions)
365+ finally:
366+ # Avoid traceback reference cycles.
367+ del exceptions
368
369 @classmethod
370 def makeMultiArchManifest(cls, http_client, push_rule, build_request,
371diff --git a/lib/lp/services/gpg/handler.py b/lib/lp/services/gpg/handler.py
372index 643246b..86770c9 100644
373--- a/lib/lp/services/gpg/handler.py
374+++ b/lib/lp/services/gpg/handler.py
375@@ -140,18 +140,16 @@ class GPGHandler:
376
377 def getVerifiedSignatureResilient(self, content, signature=None):
378 """See IGPGHandler."""
379- errors = []
380+ stored_errors = []
381
382 for i in range(3):
383 try:
384 signature = self.getVerifiedSignature(content, signature)
385 except GPGKeyNotFoundError as info:
386- errors.append(info)
387+ stored_errors.append(str(info))
388 else:
389 return signature
390
391- stored_errors = [str(err) for err in errors]
392-
393 raise GPGVerificationError(
394 "Verification failed 3 times: %s " % stored_errors)
395
396diff --git a/lib/lp/services/looptuner.py b/lib/lp/services/looptuner.py
397index 6072bf6..32e95ec 100644
398--- a/lib/lp/services/looptuner.py
399+++ b/lib/lp/services/looptuner.py
400@@ -216,7 +216,11 @@ class LoopTuner:
401 # failure, so log it.
402 self.log.exception("Unhandled exception in cleanUp")
403 # Reraise the original exception.
404- reraise(exc_info[0], exc_info[1], tb=exc_info[2])
405+ try:
406+ reraise(exc_info[0], exc_info[1], tb=exc_info[2])
407+ finally:
408+ # Avoid traceback reference cycles.
409+ del exc_info
410 else:
411 cleanup()
412
413diff --git a/lib/lp/services/timeout.py b/lib/lp/services/timeout.py
414index 6ba700c..b8b3a7d 100644
415--- a/lib/lp/services/timeout.py
416+++ b/lib/lp/services/timeout.py
417@@ -239,7 +239,11 @@ class with_timeout:
418 exc_info = t.exc_info
419 # Remove the cyclic reference for faster GC.
420 del t.exc_info
421- reraise(exc_info[0], exc_info[1], tb=exc_info[2])
422+ try:
423+ reraise(exc_info[0], exc_info[1], tb=exc_info[2])
424+ finally:
425+ # Avoid traceback reference cycles.
426+ del exc_info
427 return t.result
428
429 return call_with_timeout
430diff --git a/lib/lp/services/webapp/adapter.py b/lib/lp/services/webapp/adapter.py
431index 37c819d..637ad17 100644
432--- a/lib/lp/services/webapp/adapter.py
433+++ b/lib/lp/services/webapp/adapter.py
434@@ -642,7 +642,8 @@ class LaunchpadTimeoutTracer(PostgresTimeoutTracer):
435 try:
436 reraise(info[0], info[1], tb=info[2])
437 finally:
438- info = None
439+ # Avoid traceback reference cycles.
440+ del info
441
442 def connection_raw_execute_error(self, connection, raw_cursor,
443 statement, params, error):

Subscribers

People subscribed via source and target branches

to status/vote changes: