diff --git a/Lib/pdb.py b/Lib/pdb.py --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -134,6 +134,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): + _previous_sigint_handler = None + def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False): bdb.Bdb.__init__(self, skip=skip) @@ -187,8 +189,17 @@ self.message("\nProgram interrupted. (Use 'cont' to resume).") self.set_step() self.set_trace(frame) - # restore previous signal handler - signal.signal(signal.SIGINT, self._previous_sigint_handler) + + def set_sigint_handler(self): + if not self.nosigint: + try: + Pdb._previous_sigint_handler = \ + signal.signal(signal.SIGINT, self.sigint_handler) + except ValueError: + # ValueError happens when set_sigint_handler() is invoked from + # a non-main thread. Would printing a message here (once) make + # sense? + pass def reset(self): bdb.Bdb.reset(self) @@ -337,6 +348,9 @@ (expr, newvalue, oldvalue)) def interaction(self, frame, traceback): + # restore previous signal handler + if self._previous_sigint_handler: + signal.signal(signal.SIGINT, self._previous_sigint_handler) if self.setup(frame, traceback): # no interaction desired at this time (happens if .pdbrc contains # a command like "continue") @@ -1035,16 +1049,7 @@ """c(ont(inue)) Continue execution, only stop when a breakpoint is encountered. """ - if not self.nosigint: - try: - self._previous_sigint_handler = \ - signal.signal(signal.SIGINT, self.sigint_handler) - except ValueError: - # ValueError happens when do_continue() is invoked from - # a non-main thread in which case we just continue without - # SIGINT set. Would printing a message here (once) make - # sense? - pass + self.set_sigint_handler() self.set_continue() return 1 do_c = do_cont = do_continue diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -905,6 +905,46 @@ (Pdb) continue """ +def test_pdb_issue_20766(): + """Testing for reference leaks when setting SIGINT handler. + + >>> def set_trace(): + ... import sys, pdb + ... pdbinst = pdb.Pdb() + ... pdbinst.set_trace(sys._getframe().f_back) + ... return pdbinst + + >>> def foo(prev_pdbinst): + ... import gc + ... pdbinst = set_trace(); + ... gc.collect() + ... references = gc.get_referents(pdbinst._previous_sigint_handler) + ... print(references) + ... if prev_pdbinst and prev_pdbinst not in references: + ... print('self._previous_sigint_handler does not refer to the' + ... ' previous Pdb instance.') + ... return pdbinst + + >>> def test_function(): + ... i = 2 + ... pdbinst = None + ... while i: + ... i -= 1 + ... pdbinst = foo(pdbinst) + + >>> with PdbTestInput(['continue', + ... 'continue']): + ... test_function() + > (4)foo() + -> gc.collect() + (Pdb) continue + [, '_signal'] + > (4)foo() + -> gc.collect() + (Pdb) continue + [, '_signal'] + self._previous_sigint_handler does not refer to the previous Pdb instance. + """ class PdbTestCase(unittest.TestCase):