Exposing atomic operations tooling in the public C API

With the advent of free threading, we will need to provide a public API for dealing with atomic operations (ones which cannot be interrupted by other threads).

CPython already has quite an extensive set of helpers for this in the https://github.com/python/cpython/blob/main/Include/cpython/pyatomic.h header file.

However, all of these are marked as private.

Here’s a use case that caught my eye: unicode: make ucnhash_capi initialization thread-safe in `--disable-gil` builds · Issue #111972 · python/cpython · GitHub. Such cases will become more and more frequent as we approach free threading, not only in the interpreter itself, but also in C extensions.

Thoughts ?

1 Like

I would disagree. The C API is an API to interact with CPython, not to provide generic C utilities.

Third-party extensions should use whatever facilities their language of choice provides (whether C++, C or Rust, for example).

1 Like

This API is new in Python 3.13. I would prefer to wait for at least one Python release to see how it goes first in Python itself. In the past, we got compiler issues (especially with C++) with the previous “atomic” C API. This API was moved to the internal C API to fix compilation issues.

I think we can do better than that. The tooling is being developed for exactly the same reason C extension writers would want to use them: portable code.

E.g. look at the ticket I linked to - it’s dealing with making importing C API Capsules thread-safe. C extensions will have to do the same if they want to e.g. import the capsule for the Unicode name database or, probably more common, the datetime module C API.

That’s fair.

I just think we need to bring up these questions early to not leave C extension writers who want to prepare for free threaded CPython having to build their own atomic operation APIs.

Hmm… First, sorry for not looking at this issue before. But I think that begs the question: what API do you want to see exactly?

My take:

  1. PyCapsule_Import should be made thread-safe (if not already so), so that “normal” extension code does not have anything to change.
  2. For some reason, the interpreter state keeps a direct pointer to the unicodedata C API through _Py_unicode_state. I suppose that’s for micro-optimization, but I don’t think this is a common (and useful) pattern in third-party code. I also think that we should strive for the fast path in PyCapsule_Import to be as fast possible, so that third-party code isn’t motivated to developer similar micro-optimizations.
  3. Interacting with the CPython C API, even in free-threaded mode, should not require using atomic operations explicitly. Everything should be hidden behind higher-level APIs (potentially inlineable).
  4. I stand by my point that I don’t think it’s CPython’s business to expose a portable API for low-level atomic operations on standard C types. If you want a portable C API for such things, there are easily-vendorable, dedicated libraries such as portable-snippets.
1 Like

I agree with this, but I’d like to add another reason. Keeping to higher-level APIs that belong to the interpreter for interacting with the interpreter prevents the issue of macros that don’t necessarily work well for non C native extensions that could otherwise use the C API. (yes, everything could be rewritten as a function, but there are a lot of macros to consider, vs only doing it for the things that actually need to be exposed)

1 Like

The API closely mirrors C11 atomics. And those are also the main implementation, though there are GCC and MSVC fallbacks/specializations.

As far as I can see, MSVC added C11 atomics last year as an experimental feature. I assume that it’s likely to stop being experimental at a similar time scale as nogil.

IMO, users that need this should use what their compiler provides, rather than learn a Python-specific API (which we’d need to document and maintain forever).

2 Likes

This doesn’t help with writing portable code, though, and that’s the main reason we have those (and other similar abstractions) in the CPython code base.

Why should we require C extension writers to duplicate all this work in each of their extensions, when the code is there already, just not exposed ?

3 Likes

Would it be workable to provide a small package that would allow packagers to build against these headers without CPython committing to maintaining the API?

I’m thinking here of numpy, which provides a get_include(). Since these are purely headers, it need only be a build-time dependency.

I agree with @malemburg that we should make the atomics API public. I also think we should make some synchronization primitive, like PyMutex, public as well. That would would also require the atomics API to be public. I don’t think the ucnhash_capi changes are relevant here.

These APIs are useful for making C extensions thread-safe. Extensions written in Rust and C++ already have good APIs for locking and atomics, but C does not. This is important for extensions NumPy and Cython. The alternatives is that extensions either use this code anyways or write their own broken code.

I don’t think it’s worth waiting for another release. I’m not too worried about compiler issues, and not making these public doesn’t actually save us there – they’re already included in Python.h, they’re just labeled as “private” by the leading underscore.

3 Likes

Just want to chime in as a NumPy developer that it would be very nice to have better portable synchronization primitives provided by the C API.

I recently had to add a mutex to an extension type and doing so in a portable way in C was not straightforward. I ended up using a PyThread_type_lock mutex, which is not nearly as performant as PyMutex but allowed me to avoid attempting to implement or find a portable mutex in C. It’s also worth pointing out in this discussion that PyThread_type_lock is already a publicly exposed synchronization primitive in the C API.

There’s probably lots of reasons to write greenfield extensions in C++ or rust rather than C. However, there’s a ton of existing C (or cython code compiled to C) that will need to be retrofitted to avoid thread safety bugs. If CPython provides something in the C API to aid that process, that will make the effort of porting much easier IMO.

6 Likes