This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: KeyboardInterrupt should come with a warning
Type: enhancement Stage: resolved
Components: Documentation Versions: Python 3.10
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: docs@python Nosy List: JelleZijlstra, benfogle, docs@python, miss-islington
Priority: normal Keywords: patch

Created on 2020-11-13 02:20 by benfogle, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
sigint_condition_1.py benfogle, 2020-11-13 02:20 sample code
sigint_condition_2.py benfogle, 2020-11-13 02:20
sigint_tempfile.py benfogle, 2020-11-13 02:20
sigint_zipfile.py benfogle, 2020-11-13 02:20
Pull Requests
URL Status Linked Edit
PR 23255 merged benfogle, 2020-11-13 02:28
PR 32184 merged miss-islington, 2022-03-29 21:21
PR 32185 merged miss-islington, 2022-03-29 21:22
Messages (5)
msg380868 - (view) Author: Benjamin Fogle (benfogle) * Date: 2020-11-13 02:20
This is related to bpo-29988, and I'm happy to move this to there. I made this a separate issue because this is a workaround, not a fix as was being discussed there. Also unlike bpo-29988, this is not restricted to context managers or finally blocks.

TL;DR: Raising exceptions from interrupt handlers (most notably KeyboardInterrupt) can wreak havoc in ways that are impossible to fix. This should be noted in the documentation, with a workaround.

I've attached a few example scripts that cause various strange behavior on Linux when a KeyboardInterrupt is raised at just the right time. There are likely many, many more possible examples:
  - sigint_condition_1.py: Cause a deadlock with threading.Condition
  - sigint_condition_2.py: Cause a double-release and/or notify on unacquired threading.Condition
  - sigint_tempfile.py: Cause NamedTemporaryFiles to not be deleted
  - sigint_zipfile.py: Cause ZipExtFile to corrupt its state

When a user presses Ctrl+C, a KeyboardInterrupt will be raised on the main thread at some later time. This exception may be raised after any bytecode, and most Python code, including the standard library, is not designed to handle exceptions that spring up from nowhere.

As a simple example, consider threading.Condition:

    def __enter__(self):
        return self._lock.__enter__()

The KeyboardInterrupt could be raised just prior to return. In this case, __exit__ will never be called, and the underlying lock will remain acquired. A similar problem occurs if KeyboardInterrupt occurs at the start of __exit__.

This can be mitigated by attempting to catch a KeyboardInterrupt *absolutely everywhere*, but even then, it can't be fixed completely.

    def __enter__(self):
        try:
            # it could happen here, in which case we should not unlock
            ret = self._lock.__enter__()
            # it could happen here, in which case we must unlock
        except KeyboardInterrupt:
	    # it could, in theory, happen again right here
            ...
	    raise
	return ret
        # it could happen here, which is the same problem we had before

This is not restricted to context handlers or try/finally blocks. The zipfile module is a good example of code that is almost certain to enter an inconsistent state if a KeyboardInterrupt is raised while it's doing work:

    class ZipExtFile:
        ...
        def read1(self, n):
            ...
            self._readbuffer = b''
            # what happens if KeyboardInterrupt happens here?
            self._offset = 0
            ...

Due to how widespread this is, it's not worth "fixing". (And honestly, it seems to be a rare problem in practice.) I believe that it would be better to clearly document that KeyboardInterrupt (or any exception propagated from a signal handler) may leave the system
in an inconsistent state. Complex or high reliability applications should avoid catching KeyboardInterrupt as a way of gracefully shutting down, and should prefer registering their own SIGINT handler. They should also avoid raising exceptions from signal handlers at all.
msg416295 - (view) Author: Jelle Zijlstra (JelleZijlstra) * (Python committer) Date: 2022-03-29 21:21
New changeset d0906c90fcfbc4cfb9bb963eaa6bb152dd543b56 by benfogle in branch 'main':
bpo-42340: Document issues around KeyboardInterrupt (GH-23255)
https://github.com/python/cpython/commit/d0906c90fcfbc4cfb9bb963eaa6bb152dd543b56
msg416298 - (view) Author: miss-islington (miss-islington) Date: 2022-03-29 21:45
New changeset 66cde7c51a871a86cf8667adf4bd1d03e9b98eb1 by Miss Islington (bot) in branch '3.10':
bpo-42340: Document issues around KeyboardInterrupt (GH-23255)
https://github.com/python/cpython/commit/66cde7c51a871a86cf8667adf4bd1d03e9b98eb1
msg416299 - (view) Author: miss-islington (miss-islington) Date: 2022-03-29 21:48
New changeset c26af2bc531eb114c378cdad81935f6e838a7ee0 by Miss Islington (bot) in branch '3.9':
bpo-42340: Document issues around KeyboardInterrupt (GH-23255)
https://github.com/python/cpython/commit/c26af2bc531eb114c378cdad81935f6e838a7ee0
msg416300 - (view) Author: Jelle Zijlstra (JelleZijlstra) * (Python committer) Date: 2022-03-29 21:49
Thanks for the patch!
History
Date User Action Args
2022-04-11 14:59:38adminsetgithub: 86506
2022-03-29 21:49:51JelleZijlstrasetstatus: open -> closed
resolution: fixed
messages: + msg416300

stage: patch review -> resolved
2022-03-29 21:48:14miss-islingtonsetmessages: + msg416299
2022-03-29 21:45:51miss-islingtonsetmessages: + msg416298
2022-03-29 21:22:47miss-islingtonsetpull_requests: + pull_request30262
2022-03-29 21:21:58JelleZijlstrasetnosy: + JelleZijlstra
messages: + msg416295
2022-03-29 21:21:47miss-islingtonsetnosy: + miss-islington
pull_requests: + pull_request30261
2020-11-13 02:28:38benfoglesetkeywords: + patch
stage: patch review
pull_requests: + pull_request22151
2020-11-13 02:20:48benfoglesetfiles: + sigint_zipfile.py
2020-11-13 02:20:43benfoglesetfiles: + sigint_tempfile.py
2020-11-13 02:20:36benfoglesetfiles: + sigint_condition_2.py
2020-11-13 02:20:27benfoglesetfiles: + sigint_condition_1.py
2020-11-13 02:20:05benfoglecreate