Free-Threading Trove Classifier?

There has probably already been a discussion about this, but I couldn’t find it.

Is there a standard trove classifier for specifying that a package supports the new free-threading builds?
I cannot see any in Classifiers · PyPI

Or is it tacitly intended that the user should check the available wheels and see if there’s one for the free-threading ABI?

Long-term I don’t think we want a classifier for this, but as a transitional feature it may help.

Of course, I’m pretty sure once we add one it can’t be removed again, right? So do we want to have a wart like this around forever?

(Bear in mind that the plan is either for free threading to become the default/mandatory or it will be removed entirely. There’s no world where we have two parallel builds forever.)

3 Likes

🧵 Free-Threaded Wheels works without a classifier, so I think checking the download file names is clear enough.

You already have to do that to distinguish pure Python wheels from those with platform specific binaries.

1 Like

FWIW it may be helpful to expand PyPI’s “friendly names” for ABI tags to show something more user-friendly for free-threaded wheels.

For example, looking at numpy · PyPI they’re currently rendered as “CPython 3.13t”.

If anyone is interested in contributing that, warehouse/warehouse/utils/wheel.py at main · pypi/warehouse · GitHub is where the relevant code lives.

4 Likes

I just wanted to clarify that this note is about the separation of the free-threaded build being definitely temporary.

Even in the future where that build becomes the only build, it’s plausible that it will retain its conservative “single core fallback mode” indefinitely (so importing an extension module that relies on the GIL will just turn off the free threading support rather than failing entirely).

1 Like

trove-classifiers maintains a list of deprecated entries, see here:

and preventing them from being used for new uploads:

Edit to add: This is not advocating one way or another, rather stating factually about the mechanics that exist today.

2 Likes

Although checking a classifier would be easier than this sort of thing:

I’d be mildly in favour if adding the classifier allowed the checking code to be simplified, but instead it would just need to deal with additional states without eliminating any.

Status quo:

  • free threaded wheel build exists
  • pure Python wheel build exists
  • no wheels at all

Adding a classifier means all of those come in two variants (with or without the classifier) rather than allowing the file list to be ignored completely.

That said, the existence of a dedicated free threaded wheel just means “importable on free threaded builds”, it doesn’t say anything about whether the package actually runs in free threaded mode without enabling the GIL.

So maybe there would be value in a classifier after all, to distinguish “it works properly without enabling the GIL” from “it can be installed from a wheel on a free threaded client” (the summary site would then need a 2nd pie chart for the classifier check).

2 Likes

Yes, I can see value in this. It’s similar to how I view Python version classifiers: if I see a 3.13 classifier on a package, I know the maintainer has tested on 3.13 and is confident enough to slap the classifier on it.

Likewise, a free-threading classifier could indicate the maintainer believes this package can be used on a free-threading build, whether it has free-threaded or pure Python wheels.

5 Likes

I hadn’t really considered this point. If I am distributing wheels with extension modules in do I need to do something to make it so that my package “actually runs in free threaded mode”?

I added a CI job to build and run tests under free-threading and I checked that cibuildwheel could produce the wheels if I added it to the build matrix although I decided not to distribute the wheels yet so then removed it from the matrix.

Since the CI job passes I was assuming that I had tested that the package works in free-threaded mode but maybe it is actually just running in a free-threaded build and disabling the free-threading mode…

Yeah, the default state turns off free threading: C API Extension Support for Free Threading — Python 3.13.0rc1 documentation

It’s an import time warning so test runners will often miss it, even if they’re otherwise set up to treat unexpected warnings as errors.

It’s possible to force the interpreter to stay in free threaded mode via an environment variable: PEP 703 – Making the Global Interpreter Lock Optional in CPython | peps.python.org

There’s not yet a way to turn the GIL warnings into errors (I haven’t checked if there is a proposal to add one anywhere)

Okay, I see it now:

RuntimeWarning: The global interpreter lock (GIL) has been enabled to load module ‘flint.pyflint’, which has not declared that it can run safely without the GIL. To override this behavior and keep the GIL disabled (at your own risk), run with PYTHON_GIL=0 or -Xgil=0.

1 Like

Thanks for this. While this page is very informative it has not directly answered my immediate questions like “what should I do to test proper free-threading?”.

My extension modules are all generated from Cython code and so the descriptions in that document relate to things that I don’t control directly. It would be great if there was some regularly updated advice page somewhere for extension module authors because this seems like a fast moving target.

1 Like

Maybe you’ll find Porting Extension Modules to Support Free-Threading - py-free-threading more useful?

5 Likes

That is exactly what I was looking for. Thanks!

1 Like

To recap:

  • the current way to know whether a package supports free-threading, is to check the available wheels, or to know that it is a pure Python package
  • a free-threading trove classifier may be added
    • but we may wish to deprecate it later, when free-treading becomes the default build, or it is rejected
  • the availability of a 3.13t tagged wheel may be inconclusive
    • the package may be imported but the interpreter still fallback to enable the GIL (*)
    • even if the GIL stays disabled, the package may still be broken, possibly providing such wheels for experimentation purposes
  • if a package is marked with a free-threading classifier, the publisher is expressing that it is their intention that users of the free-threading build can in fact use it

(*) this sounds to me like an unlikely case: if you’re building a package for free-threading with a separate build step, are you really not going to make the effort to mark the module with Py_MOD_GIL_NOT_USED?


I thought that the issue could also be simply solved with a GitHub-style badge (kudos to the ppl working on the py-free-threading guide! :open_book:).
You cannot use it to filter search results on PyPI of course, but it may just do the trick.

1 Like

Firstly it might happen by accident. I was briefly building python-flint wheels like this in CI and could have easily pushed the trigger to upload them to PyPI but decided not to distribute them yet. (Probably I would have done at least a bit of local testing if I was actually going to distribute them.)

Secondly, it is not about the “effort” of marking the module with Py_MOD_GIL_NOT_USED. The purpose of Py_MOD_GIL_NOT_USED is to distinguish whether or not the package actually is thread-safe without the GIL. Packages that are not will build but then not set the flag. The “effort” is making the package thread-safe which is many orders of magnitude harder than just setting a build flag and might even be structurally impossible for some packages.

Thirdly, people are going to want to install packages into a free-threaded build regardless of whether importing them disables free-threading. You can install my GIL-using extension modules into your free-threading build and use them sometimes but otherwise not:

# (hypothetically) uses GIL:
python my_script_using_python_flint.py

# benefits from free-threading:
python my_other_script.py

Note that it makes total sense to do this for another reason: python-flint already uses threads internally without any GIL so it can already give you that benefit if you just tell it how many threads to use:

from flint import ctx
ctx.threads = 10
# From here any python-flint operation can use
# up to 10 threads. This is done transparently
# without the user needing to manage threads.

Apparently I haven’t tested it properly yet so in principle it might be difficult for python-flint to run safely without the GIL. Hypothetically setting Py_MOD_GIL_NOT_USED might mean that import flint explodes your computer in a mushroom cloud of segfaults. If that is the case then there will certainly be no point in uploading extension modules marked with Py_MOD_GIL_NOT_USED.

Hypothetically it might be so difficult to fix the thread-safety issues that it would be years before the Py_MOD_GIL_NOT_USED flag could be set. There will however be people who will want to use python-flint with their free-threaded build of CPython regardless of whether it brings the GIL back on a per-process basis. Those people would say:

Why don’t you upload cp313t wheels without setting Py_MOD_GIL_NOT_USED? We don’t mind if python-flint uses the GIL but we have free-threaded builds for other reasons and yet we still want to use python-flint sometimes.

If it seems odd to you that people will want that then remember that if you are like me and just spin up Python versions via pyenv any time you feel like then you are in a small minority of the overall Python userbase. I can happily switch between a free-threaded and a non-free-threaded Python any time I like but most Python users want to have one installation of Python.

As soon as Python 3.13 is released thousands upon thousands of people are going to download the Windows and MacOS installers and tick the “free-threading please” button. Those people are going to be very disappointed when they find that none of their favourite packages are available.

So yes, there may well be packages that upload wheels matching the free-threading ABI but that do not set the Py_MOD_GIL_NOT_USED flag so that the GIL is then disabled at runtime. There are valid reasons why the flag cannot be set and there are also valid reasons to want to install a package that does not set the flag.

The warning about enabling the GIL that is printed now is useful while free-threading is considered experimental but longer term (e.g. Python 3.14) will likely need to be removed if the expectation is that users will use the free-threading build by default.

For clarification given my comments about python-flint I don’t expect that any of this applies in python-flint’s case. I think that the situation is going to be that there are some less often used operations that are not thread-safe and I am just going to document as such:

python-flint is thread-safe in a free-threaded build provided that you never mutate an object that is shared between multiple threads. Very few operations mutate objects so a complete list is …

That approach will work for python-flint because the unsafe operations are ones that most people will not use anyway. It means that we will upload wheels with the cp313t ABI and Py_MOD_GIL_NOT_USED flag set. If you happen to mutate a polynomial that is shared between threads then you may get a segfault or other corrupted data. If I was going to improve on this then I would firstly consider just making all objects immutable (basically deprecate non-thread-safe features).

1 Like

This is actually an area that helps explain why the “free-threading or subinterpreters?” parallel execution question ended up being resolved as “Let’s pursue both ideas, since their respective strengths can be used to compensate for the other’s weaknesses”

The relevant capability for this discussion is the face a free-threaded main interpreter can spawn a GIL-protected subinterpreter to manage the problematic modules, or else a GIL-protected main interpreter (in the free threaded build) can push the CPU bound computation threads off to a dedicated subinterpreter that runs in free threaded mode and either limits itself to thread-safe dependencies, or else adds some form of external thread safety protection.

Hello @oscarbenjamin :wave:
Your case seems interesting.
About a possible free-threading trove classifier, what are your thoughts for python-flint?

In my head, your situation sounds like an experimental phase in which you wouldn’t want to publish your package with a free-threading classifier, even though you want to provide cp313t-tagged wheels.
So that you would be making it available to try, but you’re not

Would you agree?


My phrasing was misleading:

I meant that setting the slot was intended but overlooked, which is why it sounds to me unlikely to be stumbled upon.

I very rarely look at or update the trove classifiers for the projects that I maintain and I never look at the trove classifiers for other projects. From my perspective trove classifiers are basically always irrelevant except that it annoys me that they exist for projects that I maintain.

I certainly would not set a trove classifier for this. What I would do is document somewhere what the situation is with text that explains what is safe and what is not.

I expect that to begin with we will provide free-threaded wheels that are thread-safe but only with caveats around exactly how they are used. In other words if you use certain features in multiple threads without GIL then you may get segfaults. The documentation will explain which features are safe or not using text that does not fit in a trove classifier.

1 Like