Skip to content

Commit

Permalink
Merge pull request #336 from koubaa/doc-update
Browse files Browse the repository at this point in the history
update docs
  • Loading branch information
fangerer authored Jul 22, 2022
2 parents 52d490c + 2a89468 commit d262f43
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 129 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ venv.bak/

# Sphinx
docs/_build/

# vscode
.vscode
86 changes: 45 additions & 41 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ Unix file descriptors, where you have ``dup()`` and ``close()``, and Windows'
Handles vs ``PyObject *``
~~~~~~~~~~~~~~~~~~~~~~~~~

.. XXX I don't like this sentence, but I can't come up with anything better
right now. Please rephrase/rewrite :)
In the old Python/C API, multiple ``PyObject *`` references to the same object
are completely equivalent to each other. Therefore they can be passed to Python/C
API functions interchangeably. As a result, ``Py_INCREF`` an ``Py_DECREF`` can
be called with any reference to an object as long as the total number of calls
of `incref` is equal to the number of calls of `decref` at the end of the object
lifetime.
In order to fully understand the way HPy handles work, it is useful to discuss
the Python/C API ``Pyobject *`` pointer. These pointers always
point to the same object, and a python object's identity is completely given
by its address in memory, and two pointers with the same address can
be passed to Python/C API functions interchangeably. As a result, ``Py_INCREF``
and ``Py_DECREF`` can be called with any reference to an object as long as the
total number of calls of `incref` is equal to the number of calls of `decref`
at the end of the object lifetime.

Whereas using HPy API, each handle must be closed independently.

Expand All @@ -80,26 +79,29 @@ Becomes using HPy API:
Calling any HPy function on a closed handle is an error. Calling
``HPy_Close()`` on the same handle twice is an error. Forgetting to call
``HPy_Close()`` on a handle results in a memory leak. When running in
:ref:`debug-mode:debug mode`, HPy actively checks that you that you don't close a handle
twice and that you don't forget to close any.
:ref:`debug-mode:debug mode`, HPy actively checks that you don't
close a handle twice and that you don't forget to close any.


.. note::
The debug mode is a good example of how powerful it is to decouple the
lifetime of handles and the lifetime of an objects. If you find a memory
leak on CPython, you know that you are missing a ``Py_DECREF`` somewhere but
the only way to find the corresponding ``Py_INCREF`` is to manually and
carefully study the source code. On the other hand, if you forget to call
``HPy_Close()``, the HPy debug mode is able to tell the precise code
location which created the unclosed handle. Similarly, if you try to
operate on a closed handle, it will tell you the precise code locations
which created and closed it.


The other important difference is that Python/C guarantees that multiple
references to the same object results in the very same ``PyObject *`` pointer.
Thus, it is possible to compare C pointers by equality to check whether they
point to the same object::
Debug mode is a good example of how powerful it is to decouple the
identity and therefore the lifetime of handles and those of objects.
If you find a memory leak on CPython, you know that you are missing a
``Py_DECREF`` somewhere but the only way to find the corresponding
``Py_INCREF`` is to manually and carefully study the source code.
On the other hand, if you forget to call ``HPy_Close()``, debug mode
is able to identify the precise code location which created the unclosed
handle. Similarly, if you try to operate on a closed handle, it will
identify the precise code locations which created and closed it. This is
possible because handles are associated with a single call to a C/API
function. As a result, given a handle that is leaked or used after freeing,
it is possible to identify exactly the C/API function that producted it.


Remember that Python/C guarantees that multiple references to the same
object results in the very same ``PyObject *`` pointer. Thus, it is
possible to compare the pointer addresses to check whether they refer
to the same object::

int is_same_object(PyObject *x, PyObject *y)
{
Expand All @@ -118,20 +120,20 @@ identity, you can use ``HPy_Is()``:
:end-before: // END: is_same_object

.. note::
The main benefit of the semantics of handles is that it allows
implementations to use very different models of memory management. On
CPython, implementing handles is trivial because ``HPy`` is basically
``PyObject *`` in disguise, and ``HPy_Dup()`` and ``HPy_Close()`` are just
aliases for ``Py_INCREF`` and ``Py_DECREF``.

Unlike CPython, PyPy does not use reference counting for memory
management: instead, it uses a *moving GC*, which means that the address of
an object might change during its lifetime, and this makes it hard to implement
semantics like ``PyObject *``'s where the address is directly exposed to
the user. HPy solves this problem: on PyPy, handles are integers which
represent indices into a list, which is itself managed by the GC. When an
object moves, the GC fixes the address in the list, without having to touch
all the handles which have been passed to C.
The main benefit of opaque handle semantics is that implementations are
allowed to use very different models of memory management. On CPython,
implementing handles is trivial because ``HPy`` is basically ``PyObject *``
in disguise, and ``HPy_Dup()`` and ``HPy_Close()`` are just aliases for
``Py_INCREF`` and ``Py_DECREF``.

Unlike CPython, PyPy does not use reference counting to manage memory:
instead, it uses a *moving GC*, which means that the address of an object
might change during its lifetime, and this makes it hard to implement
semantics like ``PyObject *``'s where the address *identifies* the object,
and this is directly exposed to the user. HPy solves this problem: on
PyPy, handles are integers which represent indices into a list, which
is itself managed by the GC. When an address changes, the GC edits the
list, without having to touch all the handles which have been passed to C.


HPyContext
Expand All @@ -147,7 +149,9 @@ One of the reasons to include ``HPyContext`` from the day one is to be
future-proof: it is conceivable to use it to hold the interpreter or the
thread state in the future, in particular when there will be support for
sub-interpreters. Another possible usage could be to embed different versions
or implementations of Python inside the same process.
or implementations of Python inside the same process. In addition, the
``HPyContext`` may also be extended by adding new functions to the end without
breaking any extensions built against the current ``HPyContext``.

Moreover, ``HPyContext`` is used by the :term:`HPy Universal ABI` to contain a
sort of virtual function table which is used by the C extensions to call back
Expand Down
58 changes: 28 additions & 30 deletions docs/debug-mode.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
Debug Mode
==========

HPy includes a debug mode which includes a lot of useful run-time checks to
ensure that C extensions use the API correctly. The major points of the debug mode are:
HPy includes a debug mode which includes useful run-time checks to ensure
that C extensions use the API correctly. Its features include:

1. no special compilation flags are required: it is enough to compile the extension
with the Universal ABI.
1. No special compilation flags are required: it is enough to compile the
extension with the Universal ABI.

2. The debug mode can be activated at *import time*, and it can be activated
2. Debug mode can be activated at *import time*, and it can be activated
per-extension.

3. You pay the overhead of the debug mode only if you use it. Extensions loaded
3. You pay the overhead of debug mode only if you use it. Extensions loaded
without the debug mode run at full speed.

This is possible because the whole of the HPy API is provided
as part of the HPy context, so debug mode can pass in a special debugging
context (that wraps the normal context) without affecting the performance of
the regular context at all.
This is possible because the whole of the HPy API is provided as part of the HPy
context, so debug mode can pass in a special debugging context without affecting
the performance of the regular context at all.

The debugging context can already check for:

Expand All @@ -25,23 +24,23 @@ The debugging context can already check for:
* Reading from a memory which is no longer guaranteed to be still valid,
for example, the buffer returned by ``HPyUnicode_AsUTF8AndSize`` after the
corresponding ``HPy`` handle was closed.
* Writing to a memory which should be read-only, for example,
the buffer returned by ``HPyUnicode_AsUTF8AndSize``.
* Writing to memory which should be read-only, for example the buffer
returned by ``HPyUnicode_AsUTF8AndSize``.


Activating Debug Mode
---------------------

The debug mode works only for extensions built with HPy universal ABI.
Debug mode works *only* for extensions built with HPy universal ABI.

To enable the debug mode, use environment variable ``HPY_DEBUG``.
If ``HPY_DEBUG=1``, then all HPy modules are loaded with debug context.
To enable debug mode, use environment variable ``HPY_DEBUG``. If
``HPY_DEBUG=1``, then all HPy modules are loaded with the debug context.
Alternatively ``HPY_DEBUG`` can be set to a comma separated list of names
of the modules that should be loaded in the debug mode.
of the modules that should be loaded in debug mode.

In order to verify that your extension is being loaded in the HPy debug mode,
use environment variable ``HPY_LOG``. If this variable is set, then all HPy
extensions built in universal ABI mode print a message, such as:
In order to verify that your extension is being loaded in debug mode, use
environment variable ``HPY_LOG``. If this variable is set, then all HPy
extensions built in universal ABI mode print a message when loaded, such as:

.. code-block:: console
Expand All @@ -50,8 +49,8 @@ extensions built in universal ABI mode print a message, such as:
.. Note: the output above is tested in test_leak_detector_with_traces_output
If the extension is built in CPython ABI mode, then the ``HPY_LOG``
environment variable has no effect.
If the extension is built in CPython ABI mode, then the ``HPY_LOG`` environment
variable has no effect.

An HPy extension module may be also explicitly loaded in debug mode using::

Expand All @@ -64,27 +63,26 @@ and ``HPY_DEBUG`` have no effect for that extension.
Using Debug Mode
----------------

The HPy debug module exposes ``LeakDetector`` class for detection of
leaked handles. ``LeakDetector`` can be used to check that some code does
not leave behind unclosed ``HPy`` handles. For example:
HPy debug module uses the ``LeakDetector`` class to detect leaked ``HPy``
handles. Example usage of ``LeakDetector``:

.. literalinclude:: examples/tests.py
:language: python
:start-at: def test_leak_detector
:end-before: # END: test_leak_detector

Additionally, the debug module also exposes pytest fixture ``hpy_debug`` that
, for the time being, enables the ``LeakDetector``, but may also enable other
useful debugging facilities.
Additionally, the debug module also provides a pytest fixture, ``hpy_debug``,
that for the time being, enables the ``LeakDetector``. In the future, it
may also enable other useful debugging facilities.

.. literalinclude:: examples/tests.py
:language: python
:start-at: from hpy.debug.pytest import hpy_debug
:end-at: # Run some HPy extension code

**ATTENTION**: the usage of ``LeakDetector`` or ``hpy_debug`` by itself does not
enable the HPy debug mode! If the debug mode is not enabled for any extension,
then those features do nothing useful (but also nothing harmful).
**ATTENTION**: The usage of ``LeakDetector`` or ``hpy_debug`` by itself does not
enable HPy debug mode! If debug mode is not enabled for any extension, then
those features have no effect.

When dealing with handle leaks, it is useful to get a stack trace of the
allocation of the leaked handle. This feature has large memory requirements
Expand Down
Loading

0 comments on commit d262f43

Please sign in to comment.