Author eric.snow
Recipients eric.snow, jdemeyer, ncoghlan, steve.dower, vstinner
Date 2019-04-26.21:46:44
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1556315205.38.0.290739874884.issue36710@roundup.psfhosted.org>
In-reply-to
Content
I don't think this change is the right way to go (yet), but something related might be.  First, let's be clear on the status quo for CPython.  (This has gotten long, but I want to be clear.)


Status Quo
============

For simplicity sake, let's say nearly all the code operates relative to the 3 levels of runtime state:

* global      - _PyRuntimeState
* interpreter - PyInterpreterState
* thread      - PyThreadState

Furthermore, there are 3 groups of functions in the C-API:

* context-sensitive   - operate relative to the current Python thread
* runtime-dependent   - operate relative to some part of the runtime state, regardless of thread
* runtime-independent - have nothing to do with CPython's runtime state

Most of the C-API is context-sensitive.  A small portion is runtime-dependent.  A handful of functions are runtime-independent (effectively otherwise stateless helper functions that only happen to be part of the C-API).

Each context-sensitive function relies on there being a "runtime context" it can use relative to the current OS thread.  That context consists of the current (i.e. active) PyThreadState, the corresponding PyInterpreterState, and the global _PyRuntimeState.  That context is derived from data in TSS (see caveats below).  This group includes most of the C-API.

Each runtime-dependent function operates against one or more runtime state target, regardless of the current thread context (or even if there isn't one).  The target state (e.g. PyInterpreterState) is always passed explicitly.  Again, this is only a small portion of the C-API.

Caveats:
* for context-sensitive functions, we get the global runtime state from the global C variable (_PyRuntime) rather than via the implicit thread context
* for some of the runtime-dependent functions that target _PyRuntimeState, we rely on the global C variable

All of this is the pattern we use currently.  Using TSS to identify the implicit runtime context has certain benefits and costs:

benefits:
* sticking with the status quo means no backward incompatibility for existing C-extension code
* easier to distinguish the context-sensitive functions from the runtime-dependent ones
* (debatable) callers don't have to track, nor pass through, an extra argument

costs:
* extra complexity in keeping TSS correct
* makes the C-API bigger (extra macros, etc.)


Alternative
=============

For every context-sensitive function we could add a new first parameter, "context", that provides the runtime context to use.  That would be something like this:

struct {
    PyThreadState *tstate;
    ...
} PyRuntimeContext;

The interpreter state and global runtime state would still be accessible via the same indirection we have now.

Taking this alternative would eliminate the previous costs.  Having a consistent "PyRuntimeContext *context" first parameter would maintain the easy distinction from runtime-dependent functions.  Asking callers to pass in the context explicitly is probably better regardless.  As to backward compatibility, we could maintain a shim to bridge between the old way and the new.


About the C-global _PyRuntime
==============================

Currently the global runtime state (_PyRuntimeState) is stored in a static global C variable, _PyRuntime.  I added it at the time I consolidated many of the existing C globals into a single struct.  Having a C global makes it easy to do the wrong thing, so it may be good to do something else.

That would mean allocating a _PyRuntimeState on the heap early in startup and pass that around where needed.  I expect that would not have any meaningful performance penalty.  It would probably also simplify some of the code we currently use to manage _PyRuntime correctly.

As a bonus, this would be important if we decided that multiple-runtimes-per-process were a desirable thing.  That's a neat idea, though I don't see a need currently.  So on its own it's not really a justification for dropping a static _PyRuntime. :)  However, I think the other reasons are enough.


Conclusions
====================

This issue has a specific objective that I think is premature.  We have an existing pattern and we should stick with that until we decide to change to a new pattern.  That said, a few things should get corrected and we should investigate alternative patterns for the context-sensitive C-API.

As to getting rid of the _PyRuntime global variable in favor of putting it on the heap, I'm not opposed.  However, doing so should probably be handled in a separate issue.

Here are my thoughts on actionable items:

1. look for a better pattern for the context-sensitive C-API
2. clearly document which of the 3 groups each C-API function belongs to
3. add a "runtime" field to the PyInterpreterState pointing to the parent _PyRuntimeState
4. (maybe) add a _PyRuntimeState_GET() macro, a la PyThreadState_GET()
5. for context-sensitive C-API that uses the global runtime state, get it from the current PyInterpreterState
6. for runtime-dependent C-API that targets the global runtime state, ensure the _PyRuntimeState is always an explicit parameter
7. (maybe) drop _PyRuntime and create a _PyRuntimeState on the heap during startup to pass around
History
Date User Action Args
2019-04-26 21:46:45eric.snowsetrecipients: + eric.snow, ncoghlan, vstinner, jdemeyer, steve.dower
2019-04-26 21:46:45eric.snowsetmessageid: <1556315205.38.0.290739874884.issue36710@roundup.psfhosted.org>
2019-04-26 21:46:45eric.snowlinkissue36710 messages
2019-04-26 21:46:44eric.snowcreate