<div dir="ltr">Hi folks,<br><br>I submit PEP 539 third draft for the finish. Thank you for all the advice and the help!<br><br>Summary of technical changes:<br>* Don't use `bool` types in the implementation, per discussion at python/cpython#1362 [1]<br>* Update for removal of --without-threads<br><br>Best regards,<br>Masayuki<br><br><br>[1]: <a href="https://github.com/python/cpython/pull/1362#discussion_r136357583" target="_blank">https://github.com/python/<wbr>cpython/pull/1362#discussion_<wbr>r136357583</a><br><br>First round:<br><a href="https://mail.python.org/pipermail/python-ideas/2016-December/043983.html" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2016-<wbr>December/043983.html</a><br><br>Second round:<br><a href="https://mail.python.org/pipermail/python-dev/2017-August/149091.html" target="_blank">https://mail.python.org/<wbr>pipermail/python-dev/2017-<wbr>August/149091.html</a><br><br>Discussion for the issue:<br><a href="https://bugs.python.org/issue25658" target="_blank">https://bugs.python.org/<wbr>issue25658</a><br><br>HTML version for PEP 539 draft:<br><a href="https://www.python.org/dev/peps/pep-0539/" target="_blank">https://www.python.org/dev/<wbr>peps/pep-0539/</a><br><br>Diff from first round to version 3:<br><a href="https://gist.github.com/ma8ma/624f9e4435ebdb26230130b11ce12d20/revisions" target="_blank">https://gist.github.com/ma8ma/<wbr>624f9e4435ebdb26230130b11ce12d<wbr>20/revisions</a><br><br>And the pull-request for reference implementation (work in progress):<br><a href="https://github.com/python/cpython/pull/1362" target="_blank">https://github.com/python/<wbr>cpython/pull/1362</a><br><br><br>==============================<wbr>==========<br><br>PEP: 539<br>Title: A New C-API for Thread-Local Storage in CPython<br>Version: $Revision$<br>Last-Modified: $Date$<br>Author: Erik M. Bray, Masayuki Yamamoto<br>BDFL-Delegate: Nick Coghlan<br>Status: Draft<br>Type: Informational<br>Content-Type: text/x-rst<br>Created: 20-Dec-2016<br>Post-History: 16-Dec-2016<br><br><br>Abstract<br>========<br><br>The proposal is to add a new Thread Local Storage (TLS) API to CPython which<br>would supersede use of the existing TLS API within the CPython interpreter,<br>while deprecating the existing API.  The new API is named the "Thread<br>Specific Storage (TSS) API" (see `Rationale for Proposed Solution`_ for the<br>origin of the name).<br><br>Because the existing TLS API is only used internally (it is not mentioned in<br>the documentation, and the header that defines it, ``pythread.h``, is not<br>included in ``Python.h`` either directly or indirectly), this proposal<br>probably only affects CPython, but might also affect other interpreter<br>implementations (PyPy?) that implement parts of the CPython API.<br><br>This is motivated primarily by the fact that the old API uses ``int`` to<br>represent TLS keys across all platforms, which is neither POSIX-compliant,<br>nor portable in any practical sense [1]_.<br><br>.. note::<br><br>    Throughout this document the acronym "TLS" refers to Thread Local<br>    Storage and should not be confused with "Transportation Layer Security"<br>    protocols.<br><br><br>Specification<br>=============<br><br>The current API for TLS used inside the CPython interpreter consists of 6<br>functions::<br><br>    PyAPI_FUNC(int) PyThread_create_key(void)<br>    PyAPI_FUNC(void) PyThread_delete_key(int key)<br>    PyAPI_FUNC(int) PyThread_set_key_value(int key, void *value)<br>    PyAPI_FUNC(void *) PyThread_get_key_value(int key)<br>    PyAPI_FUNC(void) PyThread_delete_key_value(int key)<br>    PyAPI_FUNC(void) PyThread_ReInitTLS(void)<br><br>These would be superseded by a new set of analogous functions::<br><br>    PyAPI_FUNC(int) PyThread_tss_create(Py_tss_t *key)<br>    PyAPI_FUNC(void) PyThread_tss_delete(Py_tss_t *key)<br>    PyAPI_FUNC(int) PyThread_tss_set(Py_tss_t *key, void *value)<br>    PyAPI_FUNC(void *) PyThread_tss_get(Py_tss_t *key)<br><br>The specification also adds a few new features:<br><br>* A new type ``Py_tss_t``--an opaque type the definition of which may<br>  depend on the underlying TLS implementation.  It is defined::<br><br>      typedef struct {<br>          int _is_initialized;<br>          NATIVE_TSS_KEY_T _key;<br>      } Py_tss_t;<br><br>  where ``NATIVE_TSS_KEY_T`` is a macro whose value depends on the<br>  underlying native TLS implementation (e.g. ``pthread_key_t``).<br><br>* A constant default value for ``Py_tss_t`` variables,<br>  ``Py_tss_NEEDS_INIT``.<br><br>* Three new functions::<br><br>      PyAPI_FUNC(Py_tss_t *) PyThread_tss_alloc(void)<br>      PyAPI_FUNC(void) PyThread_tss_free(Py_tss_t *key)<br>      PyAPI_FUNC(int) PyThread_tss_is_created(Py_<wbr>tss_t *key)<br><br>  The first two are needed for dynamic (de-)allocation of a ``Py_tss_t``,<br>  particularly in extension modules built with ``Py_LIMITED_API``, where<br>  static allocation of this type is not possible due to its implementation<br>  being opaque at build time.  A value returned by ``PyThread_tss_alloc`` is<br>  in the same state as a value initialized with ``Py_tss_NEEDS_INIT``, or<br>  ``NULL`` in the case of dynamic allocation failure.  The behavior of<br>  ``PyThread_tss_free`` involves calling ``PyThread_tss_delete``<br>  preventively, or is a no-op if the value pointed to by the ``key``<br>  argument is ``NULL``.  ``PyThread_tss_is_created`` returns non-zero if the<br>  given ``Py_tss_t`` has been initialized (i.e. by ``PyThread_tss_create``).<br><br>The new TSS API does not provide functions which correspond to<br>``PyThread_delete_key_value`` and ``PyThread_ReInitTLS``, because these<br>functions were needed only for CPython's now defunct built-in TLS<br>implementation; that is the existing behavior of these functions is treated<br>as follows: ``PyThread_delete_key_value(<wbr>key)`` is equalivalent to<br>``PyThread_set_key_value(key, NULL)``, and ``PyThread_ReInitTLS()`` is a<br>no-op [8]_.<br><br>The new ``PyThread_tss_`` functions are almost exactly analogous to their<br>original counterparts with a few minor differences:  Whereas<br>``PyThread_create_key`` takes no arguments and returns a TLS key as an<br>``int``, ``PyThread_tss_create`` takes a ``Py_tss_t*`` as an argument and<br>returns an ``int`` status code. The behavior of ``PyThread_tss_create`` is<br>undefined if the value pointed to by the ``key`` argument is not initialized<br>by ``Py_tss_NEEDS_INIT``. The returned status code is zero on success<br>and non-zero on failure.  The meanings of non-zero status codes are not<br>otherwise defined by this specification.<br><br>Similarly the other ``PyThread_tss_`` functions are passed a ``Py_tss_t*``<br>whereas previously the key was passed by value.  This change is necessary, as<br>being an opaque type, the ``Py_tss_t`` type could hypothetically be almost<br>any size.  This is especially necessary for extension modules built with<br>``Py_LIMITED_API``, where the size of the type is not known.  Except for<br>``PyThread_tss_free``, the behaviors of ``PyThread_tss_`` are undefined if the<br>value pointed to by the ``key`` argument is ``NULL``.<br><br>Moreover, because of the use of ``Py_tss_t`` instead of ``int``, there are<br>behaviors in the new API which differ from the existing API with regard to<br>key creation and deletion.  ``PyThread_tss_create`` can be called repeatedly<br>on the same key--calling it on an already initialized key is a no-op and<br>immediately returns success. Similarly for calling ``PyThread_tss_delete``<br>with an uninitialized key.<br><br>The behavior of ``PyThread_tss_delete`` is defined to change the key's<br>initialization state to "uninitialized"--this allows, for example,<br>statically allocated keys to be reset to a sensible state when restarting<br>the CPython interpreter without terminating the process (e.g. embedding<br>Python in an application) [12]_.<br><br>The old ``PyThread_*_key*`` functions will be marked as deprecated in the<br>documentation, but will not generate runtime deprecation warnings.<br><br>Additionally, on platforms where ``sizeof(pthread_key_t) != sizeof(int)``,<br>``PyThread_create_key`` will return immediately with a failure status, and<br>the other TLS functions will all be no-ops on such platforms.<br><br>Comparison of API Specification<br>------------------------------<wbr>-<br><br>=================  =============================  =============================<br>API                Thread Local Storage (TLS)     Thread Specific Storage (TSS)<br>=================  =============================  =============================<br>Version            Existing                       New<br>Key Type           ``int``                        ``Py_tss_t`` (opaque type)<br>Handle Native Key  cast to ``int``                conceal into internal field<br>Function Argument  ``int``                        ``Py_tss_t *``<br>Features           - create key                   - create key<br>                   - delete key                   - delete key<br>                   - set value                    - set value<br>                   - get value                    - get value<br>                   - delete value                 - (set ``NULL`` instead) [8]_<br>                   - reinitialize keys (after     - (unnecessary) [8]_<br>                     fork)<br>                              <wbr>                    - dynamically (de-)allocate<br>                              <wbr>                      key<br>                              <wbr>                    - check key's initialization<br>                              <wbr>                      state<br>Default Value      (``-1`` as key creation        ``Py_tss_NEEDS_INIT``<br>                   failure)<br>Requirement        native threads                 native threads<br>                   (since CPython 3.7 [9]_)<br>Restriction        No support for platforms       Unable to statically allocate<br>                   where native TLS key is        keys when ``Py_LIMITED_API``<br>                   defined in a way that cannot   is defined.<br>                   be safely cast to ``int``.<br>=================  =============================  =============================<br><br>Example<br>-------<br><br>With the proposed changes, a TSS key is initialized like::<br><br>    static Py_tss_t tss_key = Py_tss_NEEDS_INIT;<br>    if (PyThread_tss_create(&tss_key)<wbr>) {<br>        /* ... handle key creation failure ... */<br>    }<br><br>The initialization state of the key can then be checked like::<br><br>    assert(PyThread_tss_is_<wbr>created(&tss_key));<br><br>The rest of the API is used analogously to the old API::<br><br>    int the_value = 1;<br>    if (PyThread_tss_get(&tss_key) == NULL) {<br>        PyThread_tss_set(&tss_key, (void *)&the_value);<br>        assert(PyThread_tss_get(&tss_<wbr>key) != NULL);<br>    }<br>    /* ... once done with the key ... */<br>    PyThread_tss_delete(&tss_key);<br>    assert(!PyThread_tss_is_<wbr>created(&tss_key));<br><br>When ``Py_LIMITED_API`` is defined, a TSS key must be dynamically allocated::<br><br>    static Py_tss_t *ptr_key = PyThread_tss_alloc();<br>    if (ptr_key == NULL) {<br>        /* ... handle key allocation failure ... */<br>    }<br>    assert(!PyThread_tss_is_<wbr>created(ptr_key));<br>    /* ... once done with the key ... */<br>    PyThread_tss_free(ptr_key);<br>    ptr_key = NULL;<br><br><br>Platform Support Changes<br>========================<br><br>A new "Native Thread Implementation" section will be added to PEP 11 that<br>states:<br><br>* As of CPython 3.7, all platforms are required to provide a native thread<br>  implementation (such as pthreads or Windows) to implement the TSS<br>  API.  Any TSS API problems that occur in an implementation without native<br>  threads will be closed as "won't fix".<br><br><br>Motivation<br>==========<br><br>The primary problem at issue here is the type of the keys (``int``) used for<br>TLS values, as defined by the original PyThread TLS API.<br><br>The original TLS API was added to Python by GvR back in 1997, and at the<br>time the key used to represent a TLS value was an ``int``, and so it has<br>been to the time of writing.  This used CPython's own TLS implementation<br>which long remained unused, largely unchanged, in Python/thread.c.  Support<br>for implementation of the API on top of native thread implementations<br>(pthreads and Windows) was added much later, and the built-in implementation<br>has been deemed no longer necessary and has since been removed [9]_.<br><br>The problem with the choice of ``int`` to represent a TLS key, is that while<br>it was fine for CPython's own TLS implementation, and happens to be<br>compatible with Windows (which uses ``DWORD`` for the analogous data), it is<br>not compatible with the POSIX standard for the pthreads API, which defines<br>``pthread_key_t`` as an opaque type not further defined by the standard (as<br>with ``Py_tss_t`` described above) [14]_.  This leaves it up to the underlying<br>implementation how a ``pthread_key_t`` value is used to look up<br>thread-specific data.<br><br>This has not generally been a problem for Python's API, as it just happens<br>that on Linux ``pthread_key_t`` is defined as an ``unsigned int``, and so is<br>fully compatible with Python's TLS API--``pthread_key_t``'s created by<br>``pthread_create_key`` can be freely cast to ``int`` and back (well, not<br>exactly, even this has some limitations as pointed out by issue #22206).<br><br>However, as issue #25658 points out, there are at least some platforms<br>(namely Cygwin, CloudABI, but likely others as well) which have otherwise<br>modern and POSIX-compliant pthreads implementations, but are not compatible<br>with Python's API because their ``pthread_key_t`` is defined in a way that<br>cannot be safely cast to ``int``.  In fact, the possibility of running into<br>this problem was raised by MvL at the time pthreads TLS was added [2]_.<br><br>It could be argued that PEP-11 makes specific requirements for supporting a<br>new, not otherwise officially-support platform (such as CloudABI), and that<br>the status of Cygwin support is currently dubious.  However, this creates a<br>very high barrier to supporting platforms that are otherwise Linux- and/or<br>POSIX-compatible and where CPython might otherwise "just work" except for<br>this one hurdle.  CPython itself imposes this implementation barrier by way<br>of an API that is not compatible with POSIX (and in fact makes invalid<br>assumptions about pthreads).<br><br><br>Rationale for Proposed Solution<br>==============================<wbr>=<br><br>The use of an opaque type (``Py_tss_t``) to key TLS values allows the API to<br>be compatible with all present (POSIX and Windows) and future (C11?) native<br>TLS implementations supported by CPython, as it allows the definition of<br>``Py_tss_t`` to depend on the underlying implementation.<br><br>Since the existing TLS API has been available in *the limited API* [13]_ for<br>some platforms (e.g. Linux), CPython makes an effort to provide the new TSS<br>API at that level likewise.  Note, however, that the ``Py_tss_t`` definition<br>becomes to be an opaque struct when ``Py_LIMITED_API`` is defined, because<br>exposing ``NATIVE_TSS_KEY_T`` as part of the limited API would prevent us<br>from switching native thread implementation without rebuilding extension<br>modules.<br><br>A new API must be introduced, rather than changing the function signatures of<br>the current API, in order to maintain backwards compatibility.  The new API<br>also more clearly groups together these related functions under a single name<br>prefix, ``PyThread_tss_``.  The "tss" in the name stands for "thread-specific<br>storage", and was influenced by the naming and design of the "tss" API that is<br>part of the C11 threads API [15]_.  However, this is in no way meant to imply<br>compatibility with or support for the C11 threads API, or signal any future<br>intention of supporting C11--it's just the influence for the naming and design.<br><br>The inclusion of the special default value ``Py_tss_NEEDS_INIT`` is required<br>by the fact that not all native TLS implementations define a sentinel value<br>for uninitialized TLS keys.  For example, on Windows a TLS key is<br>represented by a ``DWORD`` (``unsigned int``) and its value must be treated<br>as opaque [3]_.  So there is no unsigned integer value that can be safely<br>used to represent an uninitialized TLS key on Windows.  Likewise, POSIX<br>does not specify a sentinel for an uninitialized ``pthread_key_t``, instead<br>relying on the ``pthread_once`` interface to ensure that a given TLS key is<br>initialized only once per-process.  Therefore, the ``Py_tss_t`` type<br>contains an explicit ``._is_initialized`` that can indicate the key's<br>initialization state independent of the underlying implementation.<br><br>Changing ``PyThread_create_key`` to immediately return a failure status on<br>systems using pthreads where ``sizeof(int) != sizeof(pthread_key_t)`` is<br>intended as a sanity check:  Currently, ``PyThread_create_key`` may report<br>initial success on such systems, but attempts to use the returned key are<br>likely to fail.  Although in practice this failure occurs earlier in the<br>interpreter initialization, it's better to fail immediately at the source of<br>problem (``PyThread_create_key``) rather than sometime later when use of an<br>invalid key is attempted.  In other words, this indicates clearly that the<br>old API is not supported on platforms where it cannot be used reliably, and<br>that no effort will be made to add such support.<br><br><br>Rejected Ideas<br>==============<br><br>* Do nothing: The status quo is fine because it works on Linux, and platforms<br>  wishing to be supported by CPython should follow the requirements of<br>  PEP-11.  As explained above, while this would be a fair argument if<br>  CPython were being to asked to make changes to support particular quirks<br>  or features of a specific platform, in this case it is a quirk of CPython<br>  that prevents it from being used to its full potential on otherwise<br>  POSIX-compliant platforms.  The fact that the current implementation<br>  happens to work on Linux is a happy accident, and there's no guarantee<br>  that this will never change.<br><br>* Affected platforms should just configure Python ``--without-threads``:<br>  this is no longer an option as the ``--without-threads`` option has<br>  been removed for Python 3.7 [16]_.<br><br>* Affected platforms should use CPython's built-in TLS implementation<br>  instead of a native TLS implementation: This is a more acceptable<br>  alternative to the previous idea, and in fact there had been a patch to do<br>  just that [4]_.  However, the built-in implementation being "slower and<br>  clunkier" in general than native implementations still needlessly hobbles<br>  performance on affected platforms.  At least one other module<br>  (``tracemalloc``) is also broken if Python is built without a native TLS<br>  implementation.  This idea also cannot be adopted because the built-in<br>  implementation has since been removed.<br><br>* Keep the existing API, but work around the issue by providing a mapping from<br>  ``pthread_key_t`` values to ``int`` values.  A couple attempts were made at<br>  this ([5]_, [6]_), but this injects needless complexity and overhead<br>  into performance-critical code on platforms that are not currently affected<br>  by this issue (such as Linux).  Even if use of this workaround were made<br>  conditional on platform compatibility, it introduces platform-specific code<br>  to maintain, and still has the problem of the previous rejected ideas of<br>  needlessly hobbling performance on affected platforms.<br><br><br>Implementation<br>==============<br><br>An initial version of a patch [7]_ is available on the bug tracker for this<br>issue.  Since the migration to GitHub, its development has continued in the<br>``pep539-tss-api`` feature branch [10]_ in Masayuki Yamamoto's fork of the<br>CPython repository on GitHub. A work-in-progress PR is available at [11]_.<br><br>This reference implementation covers not only the new API implementation<br>features, but also the client code updates needed to replace the existing<br>TLS API with the new TSS API.<br><br><br>Copyright<br>=========<br><br>This document has been placed in the public domain.<br><br><br>References and Footnotes<br>========================<br><br>.. [1] <a href="http://bugs.python.org/issue25658" target="_blank">http://bugs.python.org/<wbr>issue25658</a><br>.. [2] <a href="https://bugs.python.org/msg116292" target="_blank">https://bugs.python.org/<wbr>msg116292</a><br>.. [3] <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms686801(v=vs.85).aspx" target="_blank">https://msdn.microsoft.com/en-<wbr>us/library/windows/desktop/<wbr>ms686801(v=vs.85).aspx</a><br>.. [4] <a href="http://bugs.python.org/file45548/configure-pthread_key_t.patch" target="_blank">http://bugs.python.org/<wbr>file45548/configure-pthread_<wbr>key_t.patch</a><br>.. [5] <a href="http://bugs.python.org/file44269/issue25658-1.patch" target="_blank">http://bugs.python.org/<wbr>file44269/issue25658-1.patch</a><br>.. [6] <a href="http://bugs.python.org/file44303/key-constant-time.diff" target="_blank">http://bugs.python.org/<wbr>file44303/key-constant-time.<wbr>diff</a><br>.. [7] <a href="http://bugs.python.org/file46379/pythread-tss-3.patch" target="_blank">http://bugs.python.org/<wbr>file46379/pythread-tss-3.patch</a><br>.. [8] <a href="https://bugs.python.org/msg298342" target="_blank">https://bugs.python.org/<wbr>msg298342</a><br>.. [9] <a href="http://bugs.python.org/issue30832" target="_blank">http://bugs.python.org/<wbr>issue30832</a><br>.. [10] <a href="https://github.com/python/cpython/compare/master...ma8ma:pep539-tss-api" target="_blank">https://github.com/python/<wbr>cpython/compare/master...<wbr>ma8ma:pep539-tss-api</a><br>.. [11] <a href="https://github.com/python/cpython/pull/1362" target="_blank">https://github.com/python/<wbr>cpython/pull/1362</a><br>.. [12] <a href="https://docs.python.org/3/c-api/init.html#c.Py_FinalizeEx" target="_blank">https://docs.python.org/3/c-<wbr>api/init.html#c.Py_FinalizeEx</a><br>.. [13] It is also called as "stable ABI"<br>        (<a href="https://www.python.org/dev/peps/pep-0384/" target="_blank">https://www.python.org/dev/<wbr>peps/pep-0384/</a>)<br>.. [14] <a href="http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html" target="_blank">http://pubs.opengroup.org/<wbr>onlinepubs/009695399/<wbr>functions/pthread_key_create.<wbr>html</a><br>.. [15] <a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=404" target="_blank">http://www.open-std.org/jtc1/<wbr>sc22/wg14/www/docs/n1570.pdf#<wbr>page=404</a><br>.. [16] <a href="https://bugs.python.org/issue31370" target="_blank">https://bugs.python.org/<wbr>issue31370</a><br></div>