We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Reduce "During handling of the above exception, another exception occurred" CLI output
Sometimes when encountering exceptions, Certbot outputs multiple stack traces on the CLI which are almost identical.

These take up a lot of room, and make any other important output hard to read. 

As part of the terminal output revamp, we should find a way to improve this so it is more compact and not so duplicative.

I am not sure if it is possible to tackle this in a generic way, or whether we would need to tackle specific instances where this has been a problem.

e.g:

    $ sudo certbot install --nginx --cert-name random27579.example.org
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Plugins selected: Authenticator None, Installer nginx
    Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/default
    Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/default
    Rolling back to previous server configuration...
    Encountered exception during recovery:
    Traceback (most recent call last):
      File "/home/alex/devel/certbot/certbot/certbot/_internal/client.py", line 530, in deploy_certificate
        self.installer.restart()
      File "/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py", line 918, in restart
        nginx_restart(self.conf('ctl'), self.nginx_conf, self.conf('sleep-seconds'))
      File "/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py", line 1199, in nginx_restart
        raise errors.MisconfigurationError(
    certbot.errors.MisconfigurationError: nginx restart failed:
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] still could not bind()


    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "/home/alex/devel/certbot/certbot/certbot/_internal/error_handler.py", line 125, in _call_registered
        self.funcs[-1]()
      File "/home/alex/devel/certbot/certbot/certbot/_internal/client.py", line 630, in _rollback_and_restart
        self.installer.restart()
      File "/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py", line 918, in restart
        nginx_restart(self.conf('ctl'), self.nginx_conf, self.conf('sleep-seconds'))
      File "/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py", line 1199, in nginx_restart
        raise errors.MisconfigurationError(
    certbot.errors.MisconfigurationError: nginx restart failed:
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] still could not bind()

    nginx restart failed:
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] still could not bind()


    IMPORTANT NOTES:
    - An error occurred and we failed to restore your config and restart
      your server. Please post to
      https://community.letsencrypt.org/c/help with details about your
      configuration and this error you received.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `certbot/certbot/_internal/error_handler.py`
Content:
```
1 """Registers functions to be called if an exception or signal occurs."""
2 import functools
3 import logging
4 import signal
5 import traceback
6 
7 from acme.magic_typing import Any
8 from acme.magic_typing import Callable
9 from acme.magic_typing import Dict
10 from acme.magic_typing import List
11 from acme.magic_typing import Union
12 from certbot import errors
13 from certbot.compat import os
14 
15 logger = logging.getLogger(__name__)
16 
17 
18 # _SIGNALS stores the signals that will be handled by the ErrorHandler. These
19 # signals were chosen as their default handler terminates the process and could
20 # potentially occur from inside Python. Signals such as SIGILL were not
21 # included as they could be a sign of something devious and we should terminate
22 # immediately.
23 if os.name != "nt":
24     _SIGNALS = [signal.SIGTERM]
25     for signal_code in [signal.SIGHUP, signal.SIGQUIT,
26                         signal.SIGXCPU, signal.SIGXFSZ]:
27         # Adding only those signals that their default action is not Ignore.
28         # This is platform-dependent, so we check it dynamically.
29         if signal.getsignal(signal_code) != signal.SIG_IGN:
30             _SIGNALS.append(signal_code)
31 else:
32     # POSIX signals are not implemented natively in Windows, but emulated from the C runtime.
33     # As consumed by CPython, most of handlers on theses signals are useless, in particular
34     # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops
35     # immediately the process without calling the attached handler. Besides, non-POSIX signals
36     # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the
37     # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable
38     # behavior in fact, and maps to the handler to SIGINT. However in this case, a
39     # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager
40     # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler.
41     #
42     # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal,
43     # https://docs.python.org/2/library/os.html#os.kill,
44     # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question
45     _SIGNALS = []
46 
47 
48 class ErrorHandler(object):
49     """Context manager for running code that must be cleaned up on failure.
50 
51     The context manager allows you to register functions that will be called
52     when an exception (excluding SystemExit) or signal is encountered.
53     Usage::
54 
55         handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs)
56         handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs)
57 
58         with handler:
59             do_something()
60 
61     Or for one cleanup function::
62 
63         with ErrorHandler(func, args, kwargs):
64             do_something()
65 
66     If an exception is raised out of do_something, the cleanup functions will
67     be called in last in first out order. Then the exception is raised.
68     Similarly, if a signal is encountered, the cleanup functions are called
69     followed by the previously received signal handler.
70 
71     Each registered cleanup function is called exactly once. If a registered
72     function raises an exception, it is logged and the next function is called.
73     Signals received while the registered functions are executing are
74     deferred until they finish.
75 
76     """
77     def __init__(self, func, *args, **kwargs):
78         self.call_on_regular_exit = False
79         self.body_executed = False
80         self.funcs = []  # type: List[Callable[[], Any]]
81         self.prev_handlers = {}  # type: Dict[int, Union[int, None, Callable]]
82         self.received_signals = []  # type: List[int]
83         if func is not None:
84             self.register(func, *args, **kwargs)
85 
86     def __enter__(self):
87         self.body_executed = False
88         self._set_signal_handlers()
89 
90     def __exit__(self, exec_type, exec_value, trace):
91         self.body_executed = True
92         retval = False
93         # SystemExit is ignored to properly handle forks that don't exec
94         if exec_type is SystemExit:
95             return retval
96         if exec_type is None:
97             if not self.call_on_regular_exit:
98                 return retval
99         elif exec_type is errors.SignalExit:
100             logger.debug("Encountered signals: %s", self.received_signals)
101             retval = True
102         else:
103             logger.debug("Encountered exception:\n%s", "".join(
104                 traceback.format_exception(exec_type, exec_value, trace)))
105 
106         self._call_registered()
107         self._reset_signal_handlers()
108         self._call_signals()
109         return retval
110 
111     def register(self, func, *args, **kwargs):
112         # type: (Callable, *Any, **Any) -> None
113         """Sets func to be run with the given arguments during cleanup.
114 
115         :param function func: function to be called in case of an error
116 
117         """
118         self.funcs.append(functools.partial(func, *args, **kwargs))
119 
120     def _call_registered(self):
121         """Calls all registered functions"""
122         logger.debug("Calling registered functions")
123         while self.funcs:
124             try:
125                 self.funcs[-1]()
126             except Exception:  # pylint: disable=broad-except
127                 logger.error("Encountered exception during recovery: ", exc_info=True)
128             self.funcs.pop()
129 
130     def _set_signal_handlers(self):
131         """Sets signal handlers for signals in _SIGNALS."""
132         for signum in _SIGNALS:
133             prev_handler = signal.getsignal(signum)
134             # If prev_handler is None, the handler was set outside of Python
135             if prev_handler is not None:
136                 self.prev_handlers[signum] = prev_handler
137                 signal.signal(signum, self._signal_handler)
138 
139     def _reset_signal_handlers(self):
140         """Resets signal handlers for signals in _SIGNALS."""
141         for signum in self.prev_handlers:
142             signal.signal(signum, self.prev_handlers[signum])
143         self.prev_handlers.clear()
144 
145     def _signal_handler(self, signum, unused_frame):
146         """Replacement function for handling received signals.
147 
148         Store the received signal. If we are executing the code block in
149         the body of the context manager, stop by raising signal exit.
150 
151         :param int signum: number of current signal
152 
153         """
154         self.received_signals.append(signum)
155         if not self.body_executed:
156             raise errors.SignalExit
157 
158     def _call_signals(self):
159         """Finally call the deferred signals."""
160         for signum in self.received_signals:
161             logger.debug("Calling signal %s", signum)
162             os.kill(os.getpid(), signum)
163 
164 class ExitHandler(ErrorHandler):
165     """Context manager for running code that must be cleaned up.
166 
167     Subclass of ErrorHandler, with the same usage and parameters.
168     In addition to cleaning up on all signals, also cleans up on
169     regular exit.
170     """
171     def __init__(self, func, *args, **kwargs):
172         ErrorHandler.__init__(self, func, *args, **kwargs)
173         self.call_on_regular_exit = True
174
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py
--- a/certbot/certbot/_internal/error_handler.py
+++ b/certbot/certbot/_internal/error_handler.py
@@ -123,8 +123,10 @@
         while self.funcs:
             try:
                 self.funcs[-1]()
-            except Exception:  # pylint: disable=broad-except
-                logger.error("Encountered exception during recovery: ", exc_info=True)
+            except Exception as exc:  # pylint: disable=broad-except
+                output = traceback.format_exception_only(type(exc), exc)
+                logger.error("Encountered exception during recovery: %s",
+                             ''.join(output).rstrip())
             self.funcs.pop()
 
     def _set_signal_handlers(self):

{"golden_diff": "diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py\n--- a/certbot/certbot/_internal/error_handler.py\n+++ b/certbot/certbot/_internal/error_handler.py\n@@ -123,8 +123,10 @@\n         while self.funcs:\n             try:\n                 self.funcs[-1]()\n-            except Exception:  # pylint: disable=broad-except\n-                logger.error(\"Encountered exception during recovery: \", exc_info=True)\n+            except Exception as exc:  # pylint: disable=broad-except\n+                output = traceback.format_exception_only(type(exc), exc)\n+                logger.error(\"Encountered exception during recovery: %s\",\n+                             ''.join(output).rstrip())\n             self.funcs.pop()\n \n     def _set_signal_handlers(self):\n", "issue": "Reduce \"During handling of the above exception, another exception occurred\" CLI output\nSometimes when encountering exceptions, Certbot outputs multiple stack traces on the CLI which are almost identical.\r\n\r\nThese take up a lot of room, and make any other important output hard to read. \r\n\r\nAs part of the terminal output revamp, we should find a way to improve this so it is more compact and not so duplicative.\r\n\r\nI am not sure if it is possible to tackle this in a generic way, or whether we would need to tackle specific instances where this has been a problem.\r\n\r\ne.g:\r\n\r\n    $ sudo certbot install --nginx --cert-name random27579.example.org\r\n    Saving debug log to /var/log/letsencrypt/letsencrypt.log\r\n    Plugins selected: Authenticator None, Installer nginx\r\n    Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/default\r\n    Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/default\r\n    Rolling back to previous server configuration...\r\n    Encountered exception during recovery:\r\n    Traceback (most recent call last):\r\n      File \"/home/alex/devel/certbot/certbot/certbot/_internal/client.py\", line 530, in deploy_certificate\r\n        self.installer.restart()\r\n      File \"/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py\", line 918, in restart\r\n        nginx_restart(self.conf('ctl'), self.nginx_conf, self.conf('sleep-seconds'))\r\n      File \"/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py\", line 1199, in nginx_restart\r\n        raise errors.MisconfigurationError(\r\n    certbot.errors.MisconfigurationError: nginx restart failed:\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] still could not bind()\r\n\r\n\r\n    During handling of the above exception, another exception occurred:\r\n\r\n    Traceback (most recent call last):\r\n      File \"/home/alex/devel/certbot/certbot/certbot/_internal/error_handler.py\", line 125, in _call_registered\r\n        self.funcs[-1]()\r\n      File \"/home/alex/devel/certbot/certbot/certbot/_internal/client.py\", line 630, in _rollback_and_restart\r\n        self.installer.restart()\r\n      File \"/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py\", line 918, in restart\r\n        nginx_restart(self.conf('ctl'), self.nginx_conf, self.conf('sleep-seconds'))\r\n      File \"/home/alex/devel/certbot/certbot-nginx/certbot_nginx/_internal/configurator.py\", line 1199, in nginx_restart\r\n        raise errors.MisconfigurationError(\r\n    certbot.errors.MisconfigurationError: nginx restart failed:\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] still could not bind()\r\n\r\n    nginx restart failed:\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)\r\n    nginx: [emerg] still could not bind()\r\n\r\n\r\n    IMPORTANT NOTES:\r\n    - An error occurred and we failed to restore your config and restart\r\n      your server. Please post to\r\n      https://community.letsencrypt.org/c/help with details about your\r\n      configuration and this error you received.\n", "before_files": [{"content": "\"\"\"Registers functions to be called if an exception or signal occurs.\"\"\"\nimport functools\nimport logging\nimport signal\nimport traceback\n\nfrom acme.magic_typing import Any\nfrom acme.magic_typing import Callable\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Union\nfrom certbot import errors\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\n# _SIGNALS stores the signals that will be handled by the ErrorHandler. These\n# signals were chosen as their default handler terminates the process and could\n# potentially occur from inside Python. Signals such as SIGILL were not\n# included as they could be a sign of something devious and we should terminate\n# immediately.\nif os.name != \"nt\":\n    _SIGNALS = [signal.SIGTERM]\n    for signal_code in [signal.SIGHUP, signal.SIGQUIT,\n                        signal.SIGXCPU, signal.SIGXFSZ]:\n        # Adding only those signals that their default action is not Ignore.\n        # This is platform-dependent, so we check it dynamically.\n        if signal.getsignal(signal_code) != signal.SIG_IGN:\n            _SIGNALS.append(signal_code)\nelse:\n    # POSIX signals are not implemented natively in Windows, but emulated from the C runtime.\n    # As consumed by CPython, most of handlers on theses signals are useless, in particular\n    # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops\n    # immediately the process without calling the attached handler. Besides, non-POSIX signals\n    # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the\n    # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable\n    # behavior in fact, and maps to the handler to SIGINT. However in this case, a\n    # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager\n    # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler.\n    #\n    # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal,\n    # https://docs.python.org/2/library/os.html#os.kill,\n    # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question\n    _SIGNALS = []\n\n\nclass ErrorHandler(object):\n    \"\"\"Context manager for running code that must be cleaned up on failure.\n\n    The context manager allows you to register functions that will be called\n    when an exception (excluding SystemExit) or signal is encountered.\n    Usage::\n\n        handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs)\n        handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs)\n\n        with handler:\n            do_something()\n\n    Or for one cleanup function::\n\n        with ErrorHandler(func, args, kwargs):\n            do_something()\n\n    If an exception is raised out of do_something, the cleanup functions will\n    be called in last in first out order. Then the exception is raised.\n    Similarly, if a signal is encountered, the cleanup functions are called\n    followed by the previously received signal handler.\n\n    Each registered cleanup function is called exactly once. If a registered\n    function raises an exception, it is logged and the next function is called.\n    Signals received while the registered functions are executing are\n    deferred until they finish.\n\n    \"\"\"\n    def __init__(self, func, *args, **kwargs):\n        self.call_on_regular_exit = False\n        self.body_executed = False\n        self.funcs = []  # type: List[Callable[[], Any]]\n        self.prev_handlers = {}  # type: Dict[int, Union[int, None, Callable]]\n        self.received_signals = []  # type: List[int]\n        if func is not None:\n            self.register(func, *args, **kwargs)\n\n    def __enter__(self):\n        self.body_executed = False\n        self._set_signal_handlers()\n\n    def __exit__(self, exec_type, exec_value, trace):\n        self.body_executed = True\n        retval = False\n        # SystemExit is ignored to properly handle forks that don't exec\n        if exec_type is SystemExit:\n            return retval\n        if exec_type is None:\n            if not self.call_on_regular_exit:\n                return retval\n        elif exec_type is errors.SignalExit:\n            logger.debug(\"Encountered signals: %s\", self.received_signals)\n            retval = True\n        else:\n            logger.debug(\"Encountered exception:\\n%s\", \"\".join(\n                traceback.format_exception(exec_type, exec_value, trace)))\n\n        self._call_registered()\n        self._reset_signal_handlers()\n        self._call_signals()\n        return retval\n\n    def register(self, func, *args, **kwargs):\n        # type: (Callable, *Any, **Any) -> None\n        \"\"\"Sets func to be run with the given arguments during cleanup.\n\n        :param function func: function to be called in case of an error\n\n        \"\"\"\n        self.funcs.append(functools.partial(func, *args, **kwargs))\n\n    def _call_registered(self):\n        \"\"\"Calls all registered functions\"\"\"\n        logger.debug(\"Calling registered functions\")\n        while self.funcs:\n            try:\n                self.funcs[-1]()\n            except Exception:  # pylint: disable=broad-except\n                logger.error(\"Encountered exception during recovery: \", exc_info=True)\n            self.funcs.pop()\n\n    def _set_signal_handlers(self):\n        \"\"\"Sets signal handlers for signals in _SIGNALS.\"\"\"\n        for signum in _SIGNALS:\n            prev_handler = signal.getsignal(signum)\n            # If prev_handler is None, the handler was set outside of Python\n            if prev_handler is not None:\n                self.prev_handlers[signum] = prev_handler\n                signal.signal(signum, self._signal_handler)\n\n    def _reset_signal_handlers(self):\n        \"\"\"Resets signal handlers for signals in _SIGNALS.\"\"\"\n        for signum in self.prev_handlers:\n            signal.signal(signum, self.prev_handlers[signum])\n        self.prev_handlers.clear()\n\n    def _signal_handler(self, signum, unused_frame):\n        \"\"\"Replacement function for handling received signals.\n\n        Store the received signal. If we are executing the code block in\n        the body of the context manager, stop by raising signal exit.\n\n        :param int signum: number of current signal\n\n        \"\"\"\n        self.received_signals.append(signum)\n        if not self.body_executed:\n            raise errors.SignalExit\n\n    def _call_signals(self):\n        \"\"\"Finally call the deferred signals.\"\"\"\n        for signum in self.received_signals:\n            logger.debug(\"Calling signal %s\", signum)\n            os.kill(os.getpid(), signum)\n\nclass ExitHandler(ErrorHandler):\n    \"\"\"Context manager for running code that must be cleaned up.\n\n    Subclass of ErrorHandler, with the same usage and parameters.\n    In addition to cleaning up on all signals, also cleans up on\n    regular exit.\n    \"\"\"\n    def __init__(self, func, *args, **kwargs):\n        ErrorHandler.__init__(self, func, *args, **kwargs)\n        self.call_on_regular_exit = True\n", "path": "certbot/certbot/_internal/error_handler.py"}], "after_files": [{"content": "\"\"\"Registers functions to be called if an exception or signal occurs.\"\"\"\nimport functools\nimport logging\nimport signal\nimport traceback\n\nfrom acme.magic_typing import Any\nfrom acme.magic_typing import Callable\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Union\nfrom certbot import errors\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\n# _SIGNALS stores the signals that will be handled by the ErrorHandler. These\n# signals were chosen as their default handler terminates the process and could\n# potentially occur from inside Python. Signals such as SIGILL were not\n# included as they could be a sign of something devious and we should terminate\n# immediately.\nif os.name != \"nt\":\n    _SIGNALS = [signal.SIGTERM]\n    for signal_code in [signal.SIGHUP, signal.SIGQUIT,\n                        signal.SIGXCPU, signal.SIGXFSZ]:\n        # Adding only those signals that their default action is not Ignore.\n        # This is platform-dependent, so we check it dynamically.\n        if signal.getsignal(signal_code) != signal.SIG_IGN:\n            _SIGNALS.append(signal_code)\nelse:\n    # POSIX signals are not implemented natively in Windows, but emulated from the C runtime.\n    # As consumed by CPython, most of handlers on theses signals are useless, in particular\n    # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops\n    # immediately the process without calling the attached handler. Besides, non-POSIX signals\n    # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the\n    # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable\n    # behavior in fact, and maps to the handler to SIGINT. However in this case, a\n    # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager\n    # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler.\n    #\n    # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal,\n    # https://docs.python.org/2/library/os.html#os.kill,\n    # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question\n    _SIGNALS = []\n\n\nclass ErrorHandler(object):\n    \"\"\"Context manager for running code that must be cleaned up on failure.\n\n    The context manager allows you to register functions that will be called\n    when an exception (excluding SystemExit) or signal is encountered.\n    Usage::\n\n        handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs)\n        handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs)\n\n        with handler:\n            do_something()\n\n    Or for one cleanup function::\n\n        with ErrorHandler(func, args, kwargs):\n            do_something()\n\n    If an exception is raised out of do_something, the cleanup functions will\n    be called in last in first out order. Then the exception is raised.\n    Similarly, if a signal is encountered, the cleanup functions are called\n    followed by the previously received signal handler.\n\n    Each registered cleanup function is called exactly once. If a registered\n    function raises an exception, it is logged and the next function is called.\n    Signals received while the registered functions are executing are\n    deferred until they finish.\n\n    \"\"\"\n    def __init__(self, func, *args, **kwargs):\n        self.call_on_regular_exit = False\n        self.body_executed = False\n        self.funcs = []  # type: List[Callable[[], Any]]\n        self.prev_handlers = {}  # type: Dict[int, Union[int, None, Callable]]\n        self.received_signals = []  # type: List[int]\n        if func is not None:\n            self.register(func, *args, **kwargs)\n\n    def __enter__(self):\n        self.body_executed = False\n        self._set_signal_handlers()\n\n    def __exit__(self, exec_type, exec_value, trace):\n        self.body_executed = True\n        retval = False\n        # SystemExit is ignored to properly handle forks that don't exec\n        if exec_type is SystemExit:\n            return retval\n        if exec_type is None:\n            if not self.call_on_regular_exit:\n                return retval\n        elif exec_type is errors.SignalExit:\n            logger.debug(\"Encountered signals: %s\", self.received_signals)\n            retval = True\n        else:\n            logger.debug(\"Encountered exception:\\n%s\", \"\".join(\n                traceback.format_exception(exec_type, exec_value, trace)))\n\n        self._call_registered()\n        self._reset_signal_handlers()\n        self._call_signals()\n        return retval\n\n    def register(self, func, *args, **kwargs):\n        # type: (Callable, *Any, **Any) -> None\n        \"\"\"Sets func to be run with the given arguments during cleanup.\n\n        :param function func: function to be called in case of an error\n\n        \"\"\"\n        self.funcs.append(functools.partial(func, *args, **kwargs))\n\n    def _call_registered(self):\n        \"\"\"Calls all registered functions\"\"\"\n        logger.debug(\"Calling registered functions\")\n        while self.funcs:\n            try:\n                self.funcs[-1]()\n            except Exception as exc:  # pylint: disable=broad-except\n                output = traceback.format_exception_only(type(exc), exc)\n                logger.error(\"Encountered exception during recovery: %s\",\n                             ''.join(output).rstrip())\n            self.funcs.pop()\n\n    def _set_signal_handlers(self):\n        \"\"\"Sets signal handlers for signals in _SIGNALS.\"\"\"\n        for signum in _SIGNALS:\n            prev_handler = signal.getsignal(signum)\n            # If prev_handler is None, the handler was set outside of Python\n            if prev_handler is not None:\n                self.prev_handlers[signum] = prev_handler\n                signal.signal(signum, self._signal_handler)\n\n    def _reset_signal_handlers(self):\n        \"\"\"Resets signal handlers for signals in _SIGNALS.\"\"\"\n        for signum in self.prev_handlers:\n            signal.signal(signum, self.prev_handlers[signum])\n        self.prev_handlers.clear()\n\n    def _signal_handler(self, signum, unused_frame):\n        \"\"\"Replacement function for handling received signals.\n\n        Store the received signal. If we are executing the code block in\n        the body of the context manager, stop by raising signal exit.\n\n        :param int signum: number of current signal\n\n        \"\"\"\n        self.received_signals.append(signum)\n        if not self.body_executed:\n            raise errors.SignalExit\n\n    def _call_signals(self):\n        \"\"\"Finally call the deferred signals.\"\"\"\n        for signum in self.received_signals:\n            logger.debug(\"Calling signal %s\", signum)\n            os.kill(os.getpid(), signum)\n\nclass ExitHandler(ErrorHandler):\n    \"\"\"Context manager for running code that must be cleaned up.\n\n    Subclass of ErrorHandler, with the same usage and parameters.\n    In addition to cleaning up on all signals, also cleans up on\n    regular exit.\n    \"\"\"\n    def __init__(self, func, *args, **kwargs):\n        ErrorHandler.__init__(self, func, *args, **kwargs)\n        self.call_on_regular_exit = True\n", "path": "certbot/certbot/_internal/error_handler.py"}]}