# Buggy code
def _handle_signals(client, signum, sigframe):
    trace = traceback.format_exc()
    try:
        hardcrash = client.options.hard_crash
    except (AttributeError, KeyError):
        hardcrash = False

    if signum == signal.SIGINT:
        exit_msg = "\nExiting gracefully on Ctrl-c"
        try:
            jid = client.local_client.pub_data["jid"]
            exit_msg += (
                "\n"
                "This job's jid is: {0}\n"
                "The minions may not have all finished running and any remaining "
                "minions will return upon completion. To look up the return data "
                "for this job later, run the following command:\n\n"
                "salt-run jobs.lookup_jid {0}".format(jid)
            )
        except (AttributeError, KeyError):
            pass
    else:
        exit_msg = None

    _handle_interrupt(
        SystemExit(exit_msg),
        Exception("\nExiting with hard crash on Ctrl-c"),
        hardcrash,
        trace=trace,
    )


# Traceback (AttributeError)
[root@localhost ~]# salt '*' test.sleep 30
^C[ERROR   ] An un-handled exception was caught by salt's global exception handler:
AttributeError: 'NoneType' object has no attribute '__context__'
Traceback (most recent call last):
File "/usr/bin/salt", line 6, in <module>
exec(compile(open(__file__).read(), __file__, 'exec'))
File "/root/salt/scripts/salt", line 10, in <module>
salt_main()
File "/root/salt/salt/scripts.py", line 480, in salt_main
client.run()
File "/root/salt/salt/cli/salt.py", line 191, in run
for full_ret in cmd_func(**kwargs):
File "/root/salt/salt/client/__init__.py", line 817, in cmd_cli
**kwargs):
File "/root/salt/salt/client/__init__.py", line 1608, in get_cli_event_returns
**kwargs
File "/root/salt/salt/client/__init__.py", line 1189, in get_iter_returns
for raw in ret_iter:
File "/root/salt/salt/client/__init__.py", line 1114, in get_returns_no_block
no_block=True, auto_reconnect=self.auto_reconnect)
File "/root/salt/salt/utils/event.py", line 634, in get_event
ret = self._get_event(wait, tag, match_func, no_block)
File "/root/salt/salt/utils/event.py", line 541, in _get_event
raw = self.subscriber.read_sync(timeout=wait)
File "/root/salt/salt/transport/ipc.py", line 699, in read_sync
self.io_loop.start()
File "/usr/lib64/python3.4/site-packages/tornado/ioloop.py", line 863, in start
event_pairs = self._impl.poll(poll_timeout)
File "/root/salt/salt/scripts.py", line 46, in _handle_signals
trace = traceback.format_exc()
File "/usr/lib64/python3.4/traceback.py", line 256, in format_exc
return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
File "/usr/lib64/python3.4/traceback.py", line 181, in format_exception
return list(_format_exception_iter(etype, value, tb, limit, chain))
File "/usr/lib64/python3.4/traceback.py", line 146, in _format_exception_iter
for value, tb in values:
File "/usr/lib64/python3.4/traceback.py", line 125, in _iter_chain
context = exc.__context__
AttributeError: 'NoneType' object has no attribute '__context__'
Traceback (most recent call last):
File "/usr/bin/salt", line 6, in <module>
exec(compile(open(__file__).read(), __file__, 'exec'))
File "/root/salt/scripts/salt", line 10, in <module>
salt_main()
File "/root/salt/salt/scripts.py", line 480, in salt_main
client.run()
File "/root/salt/salt/cli/salt.py", line 191, in run
for full_ret in cmd_func(**kwargs):
File "/root/salt/salt/client/__init__.py", line 817, in cmd_cli
**kwargs):
File "/root/salt/salt/client/__init__.py", line 1608, in get_cli_event_returns
**kwargs
File "/root/salt/salt/client/__init__.py", line 1189, in get_iter_returns
for raw in ret_iter:
File "/root/salt/salt/client/__init__.py", line 1114, in get_returns_no_block
no_block=True, auto_reconnect=self.auto_reconnect)
File "/root/salt/salt/utils/event.py", line 634, in get_event
ret = self._get_event(wait, tag, match_func, no_block)
File "/root/salt/salt/utils/event.py", line 541, in _get_event
raw = self.subscriber.read_sync(timeout=wait)
File "/root/salt/salt/transport/ipc.py", line 699, in read_sync
self.io_loop.start()
File "/usr/lib64/python3.4/site-packages/tornado/ioloop.py", line 863, in start
event_pairs = self._impl.poll(poll_timeout)
File "/root/salt/salt/scripts.py", line 46, in _handle_signals
trace = traceback.format_exc()
File "/usr/lib64/python3.4/traceback.py", line 256, in format_exc
return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
File "/usr/lib64/python3.4/traceback.py", line 181, in format_exception
return list(_format_exception_iter(etype, value, tb, limit, chain))
File "/usr/lib64/python3.4/traceback.py", line 146, in _format_exception_iter
for value, tb in values:
File "/usr/lib64/python3.4/traceback.py", line 125, in _iter_chain
context = exc.__context__
AttributeError: 'NoneType' object has no attribute '__context__'
[root@localhost ~]#

# Fixed code
def _handle_signals(client, signum, sigframe):
    try:
        # This raises AttributeError on Python 3.4 and 3.5 if there is no current exception.
        # Ref: https://bugs.python.org/issue23003
        trace = traceback.format_exc()
    except AttributeError:
        trace = ""
    try:
        hardcrash = client.options.hard_crash
    except (AttributeError, KeyError):
        hardcrash = False

    if signum == signal.SIGINT:
        exit_msg = "\nExiting gracefully on Ctrl-c"
        try:
            jid = client.local_client.pub_data["jid"]
            exit_msg += (
                "\n"
                "This job's jid is: {0}\n"
                "The minions may not have all finished running and any remaining "
                "minions will return upon completion. To look up the return data "
                "for this job later, run the following command:\n\n"
                "salt-run jobs.lookup_jid {0}".format(jid)
            )
        except (AttributeError, KeyError):
            pass
    else:
        exit_msg = None

    _handle_interrupt(
        SystemExit(exit_msg),
        Exception("\nExiting with hard crash on Ctrl-c"),
        hardcrash,
        trace=trace,
    )