<p dir="ltr">A quick read-through didn't raise any red flags from me (bit I don't write extension modules very often so I'm not the target audience).</p>
<br><div class="gmail_quote"><div dir="ltr">On Fri, Jun 3, 2016, 14:16 Petr Viktorin <<a href="mailto:encukou@gmail.com">encukou@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hello fro the PyCon sprints!<br>
<br>
Here is the first approximation for a PEP that introduces efficient<br>
access to the module state from methods of extension types. This is the<br>
biggest unresolved issue of PEP 489 (multi-phase init).<br>
There are still a few XXXs to be fleshed out, but the base is pretty<br>
solid. Please leave your comments!<br>
<br>
I also posted the pre-PEP at:<br>
<a href="https://github.com/encukou/peps/blob/3fdf3b1fad0220c7fe39044dfefc6d76759e0d1a/pep-9999.txt" rel="noreferrer" target="_blank">https://github.com/encukou/peps/blob/3fdf3b1fad0220c7fe39044dfefc6d76759e0d1a/pep-9999.txt</a><br>
<br>
The beginnings of an implementation live at:<br>
<a href="https://github.com/encukou/cpython/tree/module-state-access" rel="noreferrer" target="_blank">https://github.com/encukou/cpython/tree/module-state-access</a><br>
<br>
Thanks to Nick and Eric for helping out!<br>
<br>
----<br>
<br>
PEP: XXX<br>
Title: Module State Access from C Extension Methods<br>
Version: $Revision$<br>
Last-Modified: $Date$<br>
Author: Petr Viktorin <<a href="mailto:encukou@gmail.com" target="_blank">encukou@gmail.com</a>>,<br>
        Nick Coghlan <<a href="mailto:ncoghlan@gmail.com" target="_blank">ncoghlan@gmail.com</a>>,<br>
        Eric Snow <<a href="mailto:ericsnowcurrently@gmail.com" target="_blank">ericsnowcurrently@gmail.com</a>><br>
Discussions-To: <a href="mailto:import-sig@python.org" target="_blank">import-sig@python.org</a><br>
Status: Active<br>
Type: Process<br>
Content-Type: text/x-rst<br>
Created: 02-Jun-2016<br>
Python-Version: 3.6<br>
Post-History:<br>
<br>
<br>
Abstract<br>
========<br>
<br>
This PEP proposes to add a way for CPython extension methods to access<br>
context such as<br>
the state of the modules they are defined in.<br>
<br>
This will allow extension methods to use direct pointer dereferences<br>
rather than PyState_FindModule for looking up module state, reducing or<br>
eliminating the<br>
performance cost of using module-scoped state over process global state.<br>
<br>
This fixes one of the remaining roadblocks for adoption of PEP 3121<br>
(Extension<br>
module initialization and finalization) and PEP 489<br>
(Multi-phase extension module initialization).<br>
<br>
While this PEP takes an additional step towards fully solving the<br>
problems that PEP 3121 and PEP 489 started<br>
tackling, it does not attempt to resolve *all* remaining concerns. In<br>
particular, accessing the module state from slot methods (``nb_add``,<br>
etc) remains slower than accessing that state from other extension methods.<br>
<br>
<br>
Rationale<br>
=========<br>
<br>
PEP 489 introduced a new way to initialize extension modules, which brings<br>
several advantages to extensions that implement it:<br>
<br>
    * The extension modules behave more like their Python counterparts.<br>
    * The extension modules can easily support loading into pre-existing<br>
      module objects, which paves the way for extension module support for<br>
      ``runpy`` or for systems that enable extension module reloading.<br>
    * Loading multiple modules from the same extension is possible, which<br>
      makes testing module isolation (a key feature for proper<br>
sub-interpreter<br>
      support) possible from a single interpreter.<br>
<br>
The biggest hurdle for adoption of PEP 489 is allowing access to module<br>
state<br>
from methods of extension types.<br>
Currently, the way to access this state from extension methods is by<br>
looking up the module via<br>
``PyState_FindModule`` (in contrast to module level functions in<br>
extension modules, which<br>
receive a module reference as an argument).<br>
However, ``PyState_FindModule`` queries the thread-local state, making<br>
it relatively<br>
costly compared to C level process global access and consequently<br>
deterring module authors from using it.<br>
<br>
Also, ``PyState_FindModule`` relies on the assumption that in each<br>
subinterpreter, there is at most one module corresponding to<br>
a given ``PyModuleDef``.  This does not align well with Python's import<br>
machinery.  Since PEP 489 aimed to fix that,  the assumption does<br>
not hold for modules that use multi-phase initialization, so<br>
``PyState_FindModule`` is unavailable for these modules.<br>
<br>
A faster, safer way of accessing module-level state from extension methods<br>
is needed.<br>
<br>
<br>
Background<br>
===========<br>
<br>
The implementation of a Python method may need access to one or more of<br>
the following pieces of information:<br>
<br>
   * The instance it is called on (``self``)<br>
   * The underlying function<br>
   * The class the method was defined in<br>
   * The corresponding module<br>
   * The module state<br>
<br>
In Python code, the Python-level equivalents may be retrieved as::<br>
<br>
    import sys<br>
<br>
    class Foo:<br>
        def meth(self):<br>
            instance = self<br>
            module_globals = globals()<br>
            module_object = sys.modules[__name__]<br>
            underlying_function = Foo.meth<br>
            defining_class = Foo<br>
<br>
.. note::<br>
<br>
    The defining class is not ``type(self)``, since ``type(self)`` might<br>
    be a subclass of ``Foo``.<br>
<br>
Implicitly, the last three of those rely on name-based lookup via the<br>
function's ``__globals__`` attribute:<br>
either the ``Foo`` attribute to access the defining class and Python<br>
function object, or ``__name__`` to find the module object in<br>
``sys.modules``.<br>
In Python code, this is feasible, as ``__globals__`` is set<br>
appropriately when the function definition is executed, and<br>
even if the namespace has been manipulated to return a different object,<br>
at worst an exception will be raised.<br>
<br>
By contrast, extension methods are typically implemented as normal C<br>
functions. This means that they only have access to their arguments, and<br>
any C level thread local and process global state. Traditionally, many<br>
extension modules have stored<br>
their shared state in C level process globals, causing problems when:<br>
<br>
    * running multiple initialize/finalize cycles in the same process<br>
    * reloading modules (e.g. to test conditional imports)<br>
    * loading extension modules in subinterpreters<br>
<br>
PEP 3121 attempted to resolve this by offering the<br>
``PyState_FindModule`` API, but this still had significant problems when<br>
it comes to extension methods (rather than module level functions):<br>
<br>
    * it is markedly slower than directly accessing C level process<br>
global state<br>
    * there is still some inherent reliance on process global state that<br>
means it still doesn't reliably handle module reloading<br>
<br>
It's also the case that when looking up a C-level struct such as module<br>
state, supplying<br>
an unexpected object layout can crash the interpreter, so it's<br>
significantly more important to ensure that extension<br>
methods receive the kind of object they expect.<br>
<br>
Proposal<br>
========<br>
<br>
Currently, a bound extension method (``PyCFunction`` or<br>
``PyCFunctionWithKeywords``) receives only<br>
``self``, and (if applicable) the supplied positional and keyword<br>
arguments.<br>
<br>
While module-level extension functions already receive access to the<br>
defining module object via their<br>
``self`` argument, methods of extension types don't have that luxury:<br>
they receive the bound instance<br>
via ``self``, and hence have no direct access to the defining class or<br>
the module level state.<br>
<br>
The additional module level context described above can be made<br>
available with two changes.<br>
Both additions are optional; extension authors need to opt in to start<br>
using them:<br>
<br>
    * Add a pointer to the module to heap type objects.<br>
<br>
    * Pass the defining class to the underlying C function.<br>
<br>
      The defining class is readily available at the time built-in<br>
      method objects (``PyCFunctionObject``) are created, so it can be<br>
stored<br>
      in a new struct that extends ``PyCFunctionObject``.<br>
<br>
The module state can then be retrieved from the module object via<br>
``PyModule_GetState``.<br>
<br>
Note that this proposal implies that any type whose method needs to access<br>
module-global state must be a heap type dynamically created during extension<br>
module initialisation, rather than a static type predefined when the<br>
extension<br>
module is compiled.<br>
<br>
This is necessary to support loading multiple module objects from a single<br>
extension: a static type, as a C-level global, has no information about<br>
which module it belongs to.<br>
<br>
<br>
Slot methods<br>
------------<br>
<br>
The above changes don't cover slot methods, such as ``tp_iter`` or<br>
``nb_add``.<br>
<br>
The problem with slot methods is that their C API is fixed, so we can't<br>
simply add a new argument to pass in the defining class.<br>
Two possible solutions have been proposed to this problem:<br>
<br>
    * Look up the class through walking the MRO.<br>
      This is potentially expensive, but will be useful if performance<br>
is not<br>
      a problem (such as when raising a module-level exception).<br>
    * Storing a pointer to the defining class of each slot in a separate<br>
table,<br>
      ``__typeslots__`` [#typeslots-mail]_.  This is technically<br>
feasible and fast,<br>
      but quite invasive.<br>
<br>
Due to the invasiveness of the latter approach, this PEP proposes adding<br>
a MRO walking helper for use in slot method implementations, deferring<br>
the more complex alternative as a potential future optimisation.<br>
<br>
<br>
Specification<br>
=============<br>
<br>
Adding module references to heap types<br>
--------------------------------------<br>
<br>
The ``PyHeapTypeObject`` struct will get a new member, ``PyObject<br>
*ht_module``,<br>
that can store a pointer to the module object for which the type was<br>
defined.<br>
It will be ``NULL`` by default, and should not be modified after the type<br>
object is created.<br>
<br>
A new flag, ``Py_TPFLAGS_HAVE_MODULE``, will be set on any type object where<br>
the ``ht_module`` member is present and non-NULL.<br>
<br>
A new factory method will be added for creating modules::<br>
<br>
    PyObject* PyType_FromModuleAndSpec(PyObject *module,<br>
                                       PyType_Spec *spec,<br>
                                       PyObject *bases)<br>
<br>
This acts the same as ``PyType_FromSpecWithBases``, and additionally sets<br>
``ht_module`` to the provided module object.<br>
<br>
Additionally, an accessor, ``PyObject * PyType_GetModule(PyTypeObject *)``<br>
will be provided.<br>
It will return the ``ht_module`` if a heap type with<br>
Py_TPFLAGS_HAVE_MODULE is passed in,<br>
otherwise it will set a SystemError and return NULL.<br>
<br>
Usually, creating a class with ``ht_module`` set will create a reference<br>
cycle involving the class and the module.<br>
This is not a problem, as tearing down modules is not a<br>
performance-sensitive<br>
operation.<br>
Module-level functions typically also create reference cycles.<br>
<br>
<br>
Passing the defining class to extension methods<br>
-----------------------------------------------<br>
<br>
A new style of C-level functions will be added to the current selection of<br>
``PyCFunction`` and ``PyCFunctionWithKeywords``::<br>
<br>
    PyObject *PyCMethod(PyObject *self,<br>
                        PyTypeObject *defining_class,<br>
                        PyObject *args, PyObject *kwargs)<br>
<br>
A new method object flag, ``METH_METHOD``, will be added to signal that<br>
the underlying C function is ``PyCMethod``.<br>
<br>
To hold the extra information, a new structure extending<br>
``PyCFunctionObject``<br>
will be added::<br>
<br>
    typedef struct {<br>
        PyCFunctionObject func;<br>
        PyTypeObject *mm_class; /* Passed as 'defining_class' arg to the<br>
C func */<br>
    } PyCMethodObject;<br>
<br>
Method construction and calling code and will be updated to honor<br>
``METH_METHOD``.<br>
<br>
Slot methods<br>
------------<br>
<br>
XXX: Exact API TBD<br>
<br>
<br>
Helpers<br>
-------<br>
<br>
XXX: I'd like to port a bunch of modules to see what helpers would be<br>
convenient<br>
<br>
<br>
Argument Clinic<br>
---------------<br>
<br>
XXX [How does this affect Argument Clinic?]<br>
<br>
<br>
<br>
Summary of API Changes and Additions<br>
====================================<br>
<br>
XXX, see above for now<br>
<br>
<br>
Backwards Compatibility<br>
=======================<br>
<br>
One new pointer is added to all heap types.<br>
All other changes are adding new functions and structures.<br>
<br>
<br>
Implementation<br>
==============<br>
<br>
An initial implementation is available in a Github repository [#gh-repo]_;<br>
a patchset is at [#gh-patch]_.<br>
<br>
<br>
Possible Future Extensions<br>
==========================<br>
<br>
Easy creation of types with module references<br>
---------------------------------------------<br>
<br>
It would be possible to add a PEP 489 execution slot type make<br>
creating heap types significantly easier than calling<br>
``PyType_FromModuleAndSpec``.<br>
This is left to a future PEP.<br>
<br>
<br>
Optimization<br>
------------<br>
<br>
CPython optimizes calls to methods that have restricted signatures,<br>
such as not allowing keyword arguments.<br>
<br>
As proposed here, methods defined with the ``METH_METHOD`` flag do not<br>
support<br>
these optimizations.<br>
<br>
<br>
Discussion<br>
==========<br>
<br>
XXX Static exceptions<br>
<br>
<br>
References<br>
==========<br>
<br>
.. [#typeslots-mail] [Import-SIG] On singleton modules, heap types, and<br>
subinterpreters<br>
   (<a href="https://mail.python.org/pipermail/import-sig/2015-July/001035.html" rel="noreferrer" target="_blank">https://mail.python.org/pipermail/import-sig/2015-July/001035.html</a>)<br>
<br>
.. [#gh-repo]<br>
   <a href="https://github.com/encukou/cpython/commits/module-state-access" rel="noreferrer" target="_blank">https://github.com/encukou/cpython/commits/module-state-access</a><br>
<br>
.. [#gh-patch]<br>
<br>
<a href="https://github.com/encukou/cpython/compare/master...encukou:module-state-access.patch" rel="noreferrer" target="_blank">https://github.com/encukou/cpython/compare/master...encukou:module-state-access.patch</a><br>
<br>
<br>
Copyright<br>
=========<br>
<br>
This document has been placed in the public domain.<br>
<br>
<br>
<br>
..<br>
   Local Variables:<br>
   mode: indented-text<br>
   indent-tabs-mode: nil<br>
   sentence-end-double-space: t<br>
   fill-column: 70<br>
   coding: utf-8<br>
   End:<br>
<br>
<br>
_______________________________________________<br>
Import-SIG mailing list<br>
<a href="mailto:Import-SIG@python.org" target="_blank">Import-SIG@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/import-sig" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/import-sig</a><br>
</blockquote></div>