manhole 1.8.1
Manhole is in-process service that will accept unix domain socket connections and present thestacktraces for all threads and an interactive prompt.
License: BSD-2-Clause
Requires Python: >=3.8
Keywords: debugging, manhole, thread, socket, unix domain socket

========
Overview
========




Features
========

* Uses unix domain sockets, only root or same effective user can connect.
* Can run the connection in a thread or in a signal handler (see ``oneshot_on`` option).
* Can start the thread listening for connections from a signal handler (see ``activate_on`` option)
* Compatible with apps that fork, reinstalls the Manhole thread after fork - had to monkeypatch os.fork/os.forkpty for
  this.
* Compatible with gevent and eventlet with some limitations - you need to either:

  * Use ``oneshot_on``, *or*
  * Disable thread monkeypatching (eg: ``gevent.monkey.patch_all(thread=False)``, ``eventlet.monkey_patch(thread=False)``

  Note: on eventlet `you might <https://github.com/eventlet/eventlet/issues/401>`_ need to setup the hub first to prevent
  circular import problems:

  .. sourcecode:: python

    import eventlet
    eventlet.hubs.get_hub()  # do this first
    eventlet.monkey_patch(thread=False)

* The thread is compatible with apps that use signalfd (will mask all signals for the Manhole threads).

Options
-------

.. code-block:: python

    manhole.install(
        verbose=True,
        verbose_destination=2,
        patch_fork=True,
        activate_on=None,
        oneshot_on=None,
        sigmask=manhole.ALL_SIGNALS,
        socket_path=None,
        reinstall_delay=0.5,
        locals=None,
        strict=True,
    )

* ``verbose`` - Set it to ``False`` to squelch the logging.
* ``verbose_destination`` - Destination for verbose messages. Set it to a file descriptor or handle. Default is
  unbuffered stderr (stderr ``2`` file descriptor).
* ``patch_fork`` - Set it to ``False`` if you don't want your ``os.fork`` and ``os.forkpy`` monkeypatched
* ``activate_on`` - Set to ``"USR1"``, ``"USR2"`` or some other signal name, or a number if you want the Manhole thread
  to start when this signal is sent. This is desirable in case you don't want the thread active all the time.
* ``thread`` - Set to ``True`` to start the always-on ManholeThread. Default: ``True``.
  Automatically switched to ``False`` if ``oneshot_on`` or ``activate_on`` are used.
* ``oneshot_on`` - Set to ``"USR1"``, ``"USR2"`` or some other signal name, or a number if you want the Manhole to
  listen for connection in the signal handler. This is desireable in case you don't want threads at all.
* ``sigmask`` - Will set the signal mask to the given list (using ``signalfd.sigprocmask``). No action is done if
  ``signalfd`` is not importable. **NOTE**: This is done so that the Manhole thread doesn't *steal* any signals;
  Normally that is fine because Python will force all the signal handling to be run in the main thread but signalfd
  doesn't.
* ``socket_path`` - Use a specific path for the unix domain socket (instead of ``/tmp/manhole-<pid>``). This disables
  ``patch_fork`` as children cannot reuse the same path.
* ``reinstall_delay`` - Delay the unix domain socket creation *reinstall_delay* seconds. This alleviates
  cleanup failures when using fork+exec patterns.
* ``locals`` - Names to add to manhole interactive shell locals.
* ``daemon_connection`` - The connection thread is daemonic (dies on app exit). Default: ``False``.
* ``redirect_stderr`` - Redirect output from stderr to manhole console. Default: ``True``.
* ``strict`` - If ``True`` then ``AlreadyInstalled`` will be raised when attempting to install manhole twice.
  Default: ``True``.

Environment variable installation
---------------------------------

Manhole can be installed via the ``PYTHONMANHOLE`` environment variable.

This::

    PYTHONMANHOLE='' python yourapp.py

Is equivalent to having this in ``yourapp.py``::

    import manhole
    manhole.install()

Any extra text in the environment variable is passed to ``manhole.install()``. Example::

    PYTHONMANHOLE='oneshot_on="USR2"' python yourapp.py

What happens when you actually connect to the socket
----------------------------------------------------

1. Credentials are checked (if it's same user or root)
2. ``sys.__std*__``/``sys.std*`` are redirected to the UDS
3. Stacktraces for each thread are written to the UDS
4. REPL is started so you can fiddle with the process

Known issues
============

* Using threads and file handle (not raw file descriptor) ``verbose_destination`` can cause deadlocks. See bug reports:
  `PyPy <https://github.com/pypy/pypy/issues/1895>`_ and `Python 3.4 <http://bugs.python.org/issue22697>`_.

SIGTERM and socket cleanup
--------------------------

By default Python doesn't call the ``atexit`` callbacks with the default SIGTERM handling. This makes manhole leave
stray socket files around. If this is undesirable you should install a custom SIGTERM handler so ``atexit`` is
properly invoked.

Example:

.. code-block:: python

    import signal
    import sys

    def handle_sigterm(signo, frame):
        sys.exit(128 + signo)  # this will raise SystemExit and cause atexit to be called

    signal.signal(signal.SIGTERM, handle_sigterm)

Using Manhole with uWSGI
------------------------

Because uWSGI overrides signal handling Manhole is a bit more tricky to setup. One way is to use "uWSGI signals" (not
the POSIX signals) and have the workers check a file for the pid you want to open the Manhole in.

Stick something this in your WSGI application file:

.. sourcecode:: python

    from __future__ import print_function
    import sys
    import os
    import manhole

    stack_dump_file = '/tmp/manhole-pid'
    uwsgi_signal_number = 17

    try:
        import uwsgi

        if not os.path.exists(stack_dump_file):
            open(stack_dump_file, 'w')

        def open_manhole(dummy_signum):
            with open(stack_dump_file, 'r') as fh:
                pid = fh.read().strip()
                if pid == str(os.getpid()):
                    inst = manhole.install(strict=False, thread=False)
                    inst.handle_oneshot(dummy_signum, dummy_signum)

        uwsgi.register_signal(uwsgi_signal_number, 'workers', open_manhole)
        uwsgi.add_file_monitor(uwsgi_signal_number, stack_dump_file)

        print("Listening for stack mahole requests via %r" % (stack_dump_file,), file=sys.stderr)
    except ImportError:
        print("Not running under uwsgi; unable to configure manhole trigger", file=sys.stderr)
    except IOError:
        print("IOError creating manhole trigger %r" % (stack_dump_file,), file=sys.stderr)


    # somewhere bellow you'd have something like
    from django.core.wsgi import get_wsgi_application
    application = get_wsgi_application()
    # or
    def application(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/plain'), ('Content-Length', '2')])
        yield b'OK'

To open the Manhole just run `echo 1234 > /tmp/manhole-pid` and then `manhole-cli 1234`.

Requirements
============

:OS: Linux, OS X
:Runtime: Python 2.7, 3.4, 3.5, 3.6 or PyPy

Similar projects
================

* Twisted's `manhole <http://twistedmatrix.com/documents/current/api/twisted.conch.manhole.html>`__ - it has colors and
  server-side history.
* `wsgi-shell <https://github.com/GrahamDumpleton/wsgi-shell>`_ - spawns a thread.
* `pyrasite <https://github.com/lmacken/pyrasite>`_ - uses gdb to inject code.
* `pydbattach <https://github.com/albertz/pydbattach>`_ - uses gdb to inject code.
* `pystuck <https://github.com/alonho/pystuck>`_ - very similar, uses `rpyc <https://github.com/tomerfiliba/rpyc>`_ for
  communication.
* `pyringe <https://github.com/google/pyringe>`_ - uses gdb to inject code, more reliable, but relies on `dbg` python
  builds unfortunatelly.
* `pdb-clone <https://pypi.python.org/pypi/pdb-clone>`_ - uses gdb to inject code, with a `different strategy
  <https://code.google.com/p/pdb-clone/wiki/RemoteDebugging>`_.


Changelog
=========

1.8.1 (2024-07-24)
------------------

* Fixed buffering issue on Python 3.11. See ``66``.
* Cleaned up some packaging/test problems.
* Removed more leftover Python 2 code.
* Fixed license metadata. See: ``68``.

1.8.0 (2021-04-08)
------------------

* Simplified connection closing code.
  Contributed by Anton Ryzhov in ``62``.
* Made connection shutdown in ``manhole-cli`` more graceful.
  Contributed by Anton Ryzhov in ``63``.

1.7.0 (2021-03-22)
------------------

* Fixed memory leak via ``sys.last_type``, ``sys.last_value``, ``sys.last_traceback``.
  Contributed by Anton Ryzhov in ``59``.
* Fixed a bunch of double-close bugs and simplified stream handler code.
  Contributed by Anton Ryzhov in ``58``.
* Loosen up ``pid`` argument parsing in ``manhole-cli`` to allow using paths with any prefix
  (not just ``/tmp``).

1.6.0 (2019-01-19)
------------------

* Testing improvements (changed some skips to xfail, added osx in Travis).
* Fixed long standing Python 2.7 bug where ``sys.getfilesystemencoding()`` would be broken after installing a threaded
  manhole. See ``51``.
* Dropped support for Python 2.6, 3.3 and 3.4.
* Fixed handling when ``socket.setdefaulttimeout()`` is used.
  Contributed by "honnix" in ``53``.
* Fixed some typos. Contributed by Jesús Cea in ``43``.
* Fixed handling in ``manhole-cli`` so that timeout is actually seconds and not milliseconds.
  Contributed by Nir Soffer in ``45``.
* Cleaned up useless polling options in ``manhole-cli``.
  Contributed by Nir Soffer in ``46``.
* Documented and implemented a solution for using Manhole with Eventlet.
  See ``49``.

1.5.0 (2017-08-31)
------------------

* Added two string aliases for ``connection_handler`` option. Now you can conveniently use ``connection_handler="exec"``.
* Improved ``handle_connection_exec``. It now has a clean way to exit (``exit()``) and properly closes the socket.

1.4.0 (2017-08-29)
------------------

* Added the ``connection_handler`` install option. Default value is ``manhole.handle_connection_repl``, and alternate
  ``manhole.handle_connection_exec`` is provided (very simple: no output redirection, no stacktrace dumping).
* Dropped Python 3.2 from the test grid. It may work but it's a huge pain to support (pip/pytest don't support it anymore).
* Added Python 3.5 and 3.6 in the test grid.
* Fixed issues with piping to ``manhole-cli``. Now ``echo foobar | manhole-cli`` will wait 1 second for output from manhole
  (you can customize this with the ``--timeout`` option).
* Fixed issues with newer PyPy (caused by gevent/eventlet socket unwrapping).

1.3.0 (2015-09-03)
------------------

* Allowed Manhole to be configured without any thread or activation (in case you want to manually activate).
* Added an example and tests for using Manhole with uWSGi.
* Fixed error handling in ``manhole-cli`` on Python 3 (exc vars don't leak anymore).
* Fixed support for running in gevent/eventlet-using apps on Python 3 (now that they support Python 3).
* Allowed reinstalling the manhole (in non-``strict`` mode). Previous install is undone.

1.2.0 (2015-07-06)
------------------

* Changed ``manhole-cli``:

  * Won't spam the terminal with errors if socket file doesn't exist.
  * Allowed sending any signal (new ``--signal`` argument).
  * Fixed some validation issues for the ``PID`` argument.

1.1.0 (2015-06-06)
------------------

* Added support for installing the manhole via the ``PYTHONMANHOLE`` environment variable.
* Added a ``strict`` install option. Set it to false to avoid getting the ``AlreadyInstalled`` exception.
* Added a ``manhole-cli`` script that emulates ``socat readline unix-connect:/tmp/manhole-1234``.

1.0.0 (2014-10-13)
------------------

* Added ``socket_path`` install option (contributed by `Nir Soffer`_).
* Added ``reinstall_delay`` install option.
* Added ``locals`` install option (contributed by `Nir Soffer`_).
* Added ``redirect_stderr`` install option (contributed by `Nir Soffer`_).
* Lots of internals cleanup (contributed by `Nir Soffer`_).

0.6.2 (2014-04-28)
------------------

* Fix OS X regression.

0.6.1 (2014-04-28)
------------------

* Support for OS X (contributed by `Saulius Menkevičius`_).

.. _Saulius Menkevičius: https://github.com/razzmatazz
.. _Nir Soffer: https://github.com/nirs