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
diff --git a/lib/lp/app/widgets/date.py b/lib/lp/app/widgets/date.py
index dd8ef78..85d0756 100644
--- a/lib/lp/app/widgets/date.py
+++ b/lib/lp/app/widgets/date.py
@@ -348,8 +348,12 @@ class DateTimeWidget(TextWidget):
348 failure = e348 failure = e
349 else:349 else:
350 return350 return
351 if failure:351 try:
352 raise ConversionError('Invalid date value', failure)352 if failure:
353 raise ConversionError('Invalid date value', failure)
354 finally:
355 # Avoid traceback reference cycles.
356 del failure
353357
354 def _toFieldValue(self, input):358 def _toFieldValue(self, input):
355 """Return parsed input (datetime) as a date."""359 """Return parsed input (datetime) as a date."""
diff --git a/lib/lp/bugs/mail/handler.py b/lib/lp/bugs/mail/handler.py
index b64b815..3a151e2 100644
--- a/lib/lp/bugs/mail/handler.py
+++ b/lib/lp/bugs/mail/handler.py
@@ -257,6 +257,8 @@ class MaloneHandler:
257 def process(self, signed_msg, to_addr, filealias=None, log=None):257 def process(self, signed_msg, to_addr, filealias=None, log=None):
258 """See IMailHandler."""258 """See IMailHandler."""
259259
260 processing_errors = []
261
260 try:262 try:
261 (final_result, add_comment_to_bug,263 (final_result, add_comment_to_bug,
262 commands, ) = self.extractAndAuthenticateCommands(264 commands, ) = self.extractAndAuthenticateCommands(
@@ -269,7 +271,6 @@ class MaloneHandler:
269 bugtask = None271 bugtask = None
270 bugtask_event = None272 bugtask_event = None
271273
272 processing_errors = []
273 while len(commands) > 0:274 while len(commands) > 0:
274 command = commands.pop(0)275 command = commands.pop(0)
275 try:276 try:
@@ -332,6 +333,10 @@ class MaloneHandler:
332 'Submit Request Failure',333 'Submit Request Failure',
333 error.message, signed_msg, error.failing_command)334 error.message, signed_msg, error.failing_command)
334335
336 finally:
337 # Avoid traceback reference cycles.
338 del processing_errors
339
335 return True340 return True
336341
337 def sendHelpEmail(self, to_address):342 def sendHelpEmail(self, to_address):
diff --git a/lib/lp/bugs/tests/test_bzremotecomponentfinder.py b/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
index 13faf66..767d84b 100644
--- a/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
+++ b/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
@@ -104,7 +104,11 @@ class TestBugzillaRemoteComponentFinder(TestCaseWithFactory):
104 finder.getRemoteProductsAndComponents()104 finder.getRemoteProductsAndComponents()
105 except Exception as e:105 except Exception as e:
106 asserted = e106 asserted = e
107 self.assertIs(None, asserted)107 try:
108 self.assertIs(None, asserted)
109 finally:
110 # Avoid traceback reference cycles.
111 del asserted
108112
109 @responses.activate113 @responses.activate
110 def test_store(self):114 def test_store(self):
diff --git a/lib/lp/code/mail/codehandler.py b/lib/lp/code/mail/codehandler.py
index 508fb46..fb44573 100644
--- a/lib/lp/code/mail/codehandler.py
+++ b/lib/lp/code/mail/codehandler.py
@@ -262,19 +262,23 @@ class CodeHandler:
262 def processCommands(self, context, commands):262 def processCommands(self, context, commands):
263 """Process the various merge proposal commands against the context."""263 """Process the various merge proposal commands against the context."""
264 processing_errors = []264 processing_errors = []
265 with BranchMergeProposalNoPreviewDiffDelta.monitor(265 try:
266 context.merge_proposal):266 with BranchMergeProposalNoPreviewDiffDelta.monitor(
267 for command in commands:267 context.merge_proposal):
268 try:268 for command in commands:
269 command.execute(context)269 try:
270 except EmailProcessingError as error:270 command.execute(context)
271 processing_errors.append((error, command))271 except EmailProcessingError as error:
272272 processing_errors.append((error, command))
273 if len(processing_errors) > 0:273
274 errors, commands = zip(*processing_errors)274 if len(processing_errors) > 0:
275 raise IncomingEmailError(275 errors, commands = zip(*processing_errors)
276 '\n'.join(str(error) for error in errors),276 raise IncomingEmailError(
277 list(commands))277 '\n'.join(str(error) for error in errors),
278 list(commands))
279 finally:
280 # Avoid traceback reference cycles.
281 del processing_errors
278282
279 return len(commands)283 return len(commands)
280284
diff --git a/lib/lp/code/model/branchhosting.py b/lib/lp/code/model/branchhosting.py
index a245307..57e373c 100644
--- a/lib/lp/code/model/branchhosting.py
+++ b/lib/lp/code/model/branchhosting.py
@@ -74,9 +74,13 @@ class BranchHostingClient:
74 raise74 raise
75 except Exception:75 except Exception:
76 _, val, tb = sys.exc_info()76 _, val, tb = sys.exc_info()
77 reraise(77 try:
78 RequestExceptionWrapper, RequestExceptionWrapper(*val.args),78 reraise(
79 tb)79 RequestExceptionWrapper,
80 RequestExceptionWrapper(*val.args), tb)
81 finally:
82 # Avoid traceback reference cycles.
83 del val, tb
80 finally:84 finally:
81 action.finish()85 action.finish()
82 if as_json:86 if as_json:
diff --git a/lib/lp/code/model/githosting.py b/lib/lp/code/model/githosting.py
index 4f07f44..787e63e 100644
--- a/lib/lp/code/model/githosting.py
+++ b/lib/lp/code/model/githosting.py
@@ -89,9 +89,13 @@ class GitHostingClient:
89 raise89 raise
90 except Exception:90 except Exception:
91 _, val, tb = sys.exc_info()91 _, val, tb = sys.exc_info()
92 reraise(92 try:
93 RequestExceptionWrapper, RequestExceptionWrapper(*val.args),93 reraise(
94 tb)94 RequestExceptionWrapper,
95 RequestExceptionWrapper(*val.args), tb)
96 finally:
97 # Avoid traceback reference cycles.
98 del val, tb
95 finally:99 finally:
96 action.finish()100 action.finish()
97 if response.content:101 if response.content:
diff --git a/lib/lp/code/xmlrpc/git.py b/lib/lp/code/xmlrpc/git.py
index 21e728d..6e181c3 100644
--- a/lib/lp/code/xmlrpc/git.py
+++ b/lib/lp/code/xmlrpc/git.py
@@ -454,14 +454,18 @@ class GitAPI(LaunchpadXMLRPCView):
454 result = run_with_login(454 result = run_with_login(
455 requester_id, self._translatePath,455 requester_id, self._translatePath,
456 six.ensure_text(path).strip("/"), permission, auth_params)456 six.ensure_text(path).strip("/"), permission, auth_params)
457 if isinstance(result, xmlrpc_client.Fault):457 try:
458 logger.error("translatePath failed: %r", result)458 if isinstance(result, xmlrpc_client.Fault):
459 else:459 logger.error("translatePath failed: %r", result)
460 # The results of path translation are not sensitive for logging460 else:
461 # purposes (they may refer to private artifacts, but contain no461 # The results of path translation are not sensitive for
462 # credentials).462 # logging purposes (they may refer to private artifacts, but
463 logger.info("translatePath succeeded: %s", result)463 # contain no credentials).
464 return result464 logger.info("translatePath succeeded: %s", result)
465 return result
466 finally:
467 # Avoid traceback reference cycles.
468 del result
465469
466 @return_fault470 @return_fault
467 def _notify(self, requester, translated_path, statistics, auth_params):471 def _notify(self, requester, translated_path, statistics, auth_params):
@@ -498,11 +502,15 @@ class GitAPI(LaunchpadXMLRPCView):
498 result = run_with_login(502 result = run_with_login(
499 requester_id, self._notify,503 requester_id, self._notify,
500 translated_path, statistics, auth_params)504 translated_path, statistics, auth_params)
501 if isinstance(result, xmlrpc_client.Fault):505 try:
502 logger.error("notify failed: %r", result)506 if isinstance(result, xmlrpc_client.Fault):
503 else:507 logger.error("notify failed: %r", result)
504 logger.info("notify succeeded: %s" % result)508 else:
505 return result509 logger.info("notify succeeded: %s" % result)
510 return result
511 finally:
512 # Avoid traceback reference cycles.
513 del result
506514
507 @return_fault515 @return_fault
508 def _getMergeProposalURL(self, requester, translated_path, branch,516 def _getMergeProposalURL(self, requester, translated_path, branch,
@@ -536,14 +544,18 @@ class GitAPI(LaunchpadXMLRPCView):
536 result = run_with_login(544 result = run_with_login(
537 requester_id, self._getMergeProposalURL,545 requester_id, self._getMergeProposalURL,
538 translated_path, branch, auth_params)546 translated_path, branch, auth_params)
539 if isinstance(result, xmlrpc_client.Fault):547 try:
540 logger.error("getMergeProposalURL failed: %r", result)548 if isinstance(result, xmlrpc_client.Fault):
541 else:549 logger.error("getMergeProposalURL failed: %r", result)
542 # The result of getMergeProposalURL is not sensitive for logging550 else:
543 # purposes (it may refer to private artifacts, but contains no551 # The result of getMergeProposalURL is not sensitive for
544 # credentials, only the merge proposal URL).552 # logging purposes (it may refer to private artifacts, but
545 logger.info("getMergeProposalURL succeeded: %s" % result)553 # contains no credentials, only the merge proposal URL).
546 return result554 logger.info("getMergeProposalURL succeeded: %s" % result)
555 return result
556 finally:
557 # Avoid traceback reference cycles.
558 del result
547559
548 @return_fault560 @return_fault
549 def _authenticateWithPassword(self, username, password):561 def _authenticateWithPassword(self, username, password):
@@ -571,15 +583,19 @@ class GitAPI(LaunchpadXMLRPCView):
571 logger.info(583 logger.info(
572 "Request received: authenticateWithPassword('%s')", username)584 "Request received: authenticateWithPassword('%s')", username)
573 result = self._authenticateWithPassword(username, password)585 result = self._authenticateWithPassword(username, password)
574 if isinstance(result, xmlrpc_client.Fault):586 try:
575 logger.error("authenticateWithPassword failed: %r", result)587 if isinstance(result, xmlrpc_client.Fault):
576 else:588 logger.error("authenticateWithPassword failed: %r", result)
577 # The results of authentication may be sensitive, but we can at589 else:
578 # least log the authenticated user.590 # The results of authentication may be sensitive, but we can
579 logger.info(591 # at least log the authenticated user.
580 "authenticateWithPassword succeeded: %s",592 logger.info(
581 result.get("uid", result.get("user")))593 "authenticateWithPassword succeeded: %s",
582 return result594 result.get("uid", result.get("user")))
595 return result
596 finally:
597 # Avoid traceback reference cycles.
598 del result
583599
584 def _renderPermissions(self, set_of_permissions):600 def _renderPermissions(self, set_of_permissions):
585 """Render a set of permission strings for XML-RPC output."""601 """Render a set of permission strings for XML-RPC output."""
@@ -647,17 +663,21 @@ class GitAPI(LaunchpadXMLRPCView):
647 result = run_with_login(663 result = run_with_login(
648 requester_id, self._checkRefPermissions,664 requester_id, self._checkRefPermissions,
649 translated_path, ref_paths, auth_params)665 translated_path, ref_paths, auth_params)
650 if isinstance(result, xmlrpc_client.Fault):666 try:
651 logger.error("checkRefPermissions failed: %r", result)667 if isinstance(result, xmlrpc_client.Fault):
652 else:668 logger.error("checkRefPermissions failed: %r", result)
653 # The results of ref permission checks are not sensitive for669 else:
654 # logging purposes (they may refer to private artifacts, but670 # The results of ref permission checks are not sensitive for
655 # contain no credentials).671 # logging purposes (they may refer to private artifacts, but
656 logger.info(672 # contain no credentials).
657 "checkRefPermissions succeeded: %s",673 logger.info(
658 [(ref_path.data, permissions)674 "checkRefPermissions succeeded: %s",
659 for ref_path, permissions in result])675 [(ref_path.data, permissions)
660 return result676 for ref_path, permissions in result])
677 return result
678 finally:
679 # Avoid traceback reference cycles.
680 del result
661681
662 def _validateRequesterCanManageRepoCreation(682 def _validateRequesterCanManageRepoCreation(
663 self, requester, repository, auth_params):683 self, requester, repository, auth_params):
@@ -713,11 +733,15 @@ class GitAPI(LaunchpadXMLRPCView):
713 translated_path, auth_params)733 translated_path, auth_params)
714 except Exception as e:734 except Exception as e:
715 result = e735 result = e
716 if isinstance(result, xmlrpc_client.Fault):736 try:
717 logger.error("confirmRepoCreation failed: %r", result)737 if isinstance(result, xmlrpc_client.Fault):
718 else:738 logger.error("confirmRepoCreation failed: %r", result)
719 logger.info("confirmRepoCreation succeeded: %s" % result)739 else:
720 return result740 logger.info("confirmRepoCreation succeeded: %s" % result)
741 return result
742 finally:
743 # Avoid traceback reference cycles.
744 del result
721745
722 def _abortRepoCreation(self, requester, translated_path, auth_params):746 def _abortRepoCreation(self, requester, translated_path, auth_params):
723 naked_repo = removeSecurityProxy(747 naked_repo = removeSecurityProxy(
@@ -740,8 +764,12 @@ class GitAPI(LaunchpadXMLRPCView):
740 translated_path, auth_params)764 translated_path, auth_params)
741 except Exception as e:765 except Exception as e:
742 result = e766 result = e
743 if isinstance(result, xmlrpc_client.Fault):767 try:
744 logger.error("abortRepoCreation failed: %r", result)768 if isinstance(result, xmlrpc_client.Fault):
745 else:769 logger.error("abortRepoCreation failed: %r", result)
746 logger.info("abortRepoCreation succeeded: %s" % result)770 else:
747 return result771 logger.info("abortRepoCreation succeeded: %s" % result)
772 return result
773 finally:
774 # Avoid traceback reference cycles.
775 del result
diff --git a/lib/lp/oci/model/ociregistryclient.py b/lib/lp/oci/model/ociregistryclient.py
index 51b00af..f3e623b 100644
--- a/lib/lp/oci/model/ociregistryclient.py
+++ b/lib/lp/oci/model/ociregistryclient.py
@@ -387,18 +387,22 @@ class OCIRegistryClient:
387 preloaded_data = cls._preloadFiles(build, manifest, digests)387 preloaded_data = cls._preloadFiles(build, manifest, digests)
388388
389 exceptions = []389 exceptions = []
390 for push_rule in build.recipe.push_rules:390 try:
391 for tag in cls._calculateTags(build.recipe):391 for push_rule in build.recipe.push_rules:
392 try:392 for tag in cls._calculateTags(build.recipe):
393 cls._upload_to_push_rule(393 try:
394 push_rule, build, manifest, digests, preloaded_data,394 cls._upload_to_push_rule(
395 tag)395 push_rule, build, manifest, digests,
396 except Exception as e:396 preloaded_data, tag)
397 exceptions.append(e)397 except Exception as e:
398 if len(exceptions) == 1:398 exceptions.append(e)
399 raise exceptions[0]399 if len(exceptions) == 1:
400 elif len(exceptions) > 1:400 raise exceptions[0]
401 raise MultipleOCIRegistryError(exceptions)401 elif len(exceptions) > 1:
402 raise MultipleOCIRegistryError(exceptions)
403 finally:
404 # Avoid traceback reference cycles.
405 del exceptions
402406
403 @classmethod407 @classmethod
404 def makeMultiArchManifest(cls, http_client, push_rule, build_request,408 def makeMultiArchManifest(cls, http_client, push_rule, build_request,
diff --git a/lib/lp/services/gpg/handler.py b/lib/lp/services/gpg/handler.py
index 643246b..86770c9 100644
--- a/lib/lp/services/gpg/handler.py
+++ b/lib/lp/services/gpg/handler.py
@@ -140,18 +140,16 @@ class GPGHandler:
140140
141 def getVerifiedSignatureResilient(self, content, signature=None):141 def getVerifiedSignatureResilient(self, content, signature=None):
142 """See IGPGHandler."""142 """See IGPGHandler."""
143 errors = []143 stored_errors = []
144144
145 for i in range(3):145 for i in range(3):
146 try:146 try:
147 signature = self.getVerifiedSignature(content, signature)147 signature = self.getVerifiedSignature(content, signature)
148 except GPGKeyNotFoundError as info:148 except GPGKeyNotFoundError as info:
149 errors.append(info)149 stored_errors.append(str(info))
150 else:150 else:
151 return signature151 return signature
152152
153 stored_errors = [str(err) for err in errors]
154
155 raise GPGVerificationError(153 raise GPGVerificationError(
156 "Verification failed 3 times: %s " % stored_errors)154 "Verification failed 3 times: %s " % stored_errors)
157155
diff --git a/lib/lp/services/looptuner.py b/lib/lp/services/looptuner.py
index 6072bf6..32e95ec 100644
--- a/lib/lp/services/looptuner.py
+++ b/lib/lp/services/looptuner.py
@@ -216,7 +216,11 @@ class LoopTuner:
216 # failure, so log it.216 # failure, so log it.
217 self.log.exception("Unhandled exception in cleanUp")217 self.log.exception("Unhandled exception in cleanUp")
218 # Reraise the original exception.218 # Reraise the original exception.
219 reraise(exc_info[0], exc_info[1], tb=exc_info[2])219 try:
220 reraise(exc_info[0], exc_info[1], tb=exc_info[2])
221 finally:
222 # Avoid traceback reference cycles.
223 del exc_info
220 else:224 else:
221 cleanup()225 cleanup()
222226
diff --git a/lib/lp/services/timeout.py b/lib/lp/services/timeout.py
index 6ba700c..b8b3a7d 100644
--- a/lib/lp/services/timeout.py
+++ b/lib/lp/services/timeout.py
@@ -239,7 +239,11 @@ class with_timeout:
239 exc_info = t.exc_info239 exc_info = t.exc_info
240 # Remove the cyclic reference for faster GC.240 # Remove the cyclic reference for faster GC.
241 del t.exc_info241 del t.exc_info
242 reraise(exc_info[0], exc_info[1], tb=exc_info[2])242 try:
243 reraise(exc_info[0], exc_info[1], tb=exc_info[2])
244 finally:
245 # Avoid traceback reference cycles.
246 del exc_info
243 return t.result247 return t.result
244248
245 return call_with_timeout249 return call_with_timeout
diff --git a/lib/lp/services/webapp/adapter.py b/lib/lp/services/webapp/adapter.py
index 37c819d..637ad17 100644
--- a/lib/lp/services/webapp/adapter.py
+++ b/lib/lp/services/webapp/adapter.py
@@ -642,7 +642,8 @@ class LaunchpadTimeoutTracer(PostgresTimeoutTracer):
642 try:642 try:
643 reraise(info[0], info[1], tb=info[2])643 reraise(info[0], info[1], tb=info[2])
644 finally:644 finally:
645 info = None645 # Avoid traceback reference cycles.
646 del info
646647
647 def connection_raw_execute_error(self, connection, raw_cursor,648 def connection_raw_execute_error(self, connection, raw_cursor,
648 statement, params, error):649 statement, params, error):

Subscribers

People subscribed via source and target branches

to status/vote changes: