Skip to content

3.12 regression: interpreter shouldn't shut down when there are non-daemon threads left #115533

Closed as not planned
@intgr

Description

@intgr

Bug report

Bug description:

This is related to #113964, but trying to address the bigger problem of what "interpreter shutdown" is, and trying to make a stronger case for restoring the old behavior.

TL;DR: This was an undocumented change. The old behavior was useful. Documentation is contradictory. Users are struggling and are recommended to downgrade.

Undocumented change

In Python 3.11 and previous versions, daemon=False threads could spawn new threads even after the main thread has exited.

This changed in Python 3.12, which throws "RuntimeError: can't create new thread at interpreter shutdown" when a daemon=False thread tries to spawn new threads after the main thread has exited.

I could not find this change to be documented in 3.12 release notes, it appears to be an unintentional change.

Reproducer

"""Python 3.11: Successfully spawns 20 subthreads. Python 3.12: RuntimeError."""
from threading import Thread, current_thread
from time import sleep

def subthread():
    print(f"Thread {current_thread().name} spawned...")

def thread():
    for i in range(20):
        Thread(target=subthread, daemon=False).start()
        sleep(0.001)

Thread(target=thread, daemon=False).start()

Old behavior was useful

While not join()ing the launched threads is considered an antipattern, at the very least the old behavior was useful for throw-away scripts that launch a bunch of threads, and have Python exit automatically once all work was finished.

Documentation is contradictory

The documentation says this about daemon threads:

A thread can be flagged as a “daemon thread”. The significance of this flag is that the entire Python program exits when only daemon threads are left.

I think a useful interpretation of "exits when only daemon threads are left" also implies "the Python program starts shutting down when only daemon threads are left", and does not shut down before. Because what else could "exits" mean if not "shuts down"?

Although this is in conflict with the definition of "interpreter shutdown" that states:

The main reason for interpreter shutdown is that the __main__ module or the script being run has finished executing.

This interpetation is also not being respected by sys.is_finalizing() API. This is supposed to "Return True if the Python interpreter is shutting down", but it returns False in this situation even after the main thread has exited and thread spawning fails: #114570. That's another indication that this change was unintentional.

Users are struggling

Python has a reputation for breakage across version upgrades. I hope there is motivation among CPython developers to change that. I think such unintentional changes that break users, should be treated as regressions to be fixed.

There are three posts on StackOverflow [1] [2] [3], where downgrading Python is the dominant "solution". And when you have threads spawning more threads, the "proper fix" is often not a simple matter of adding a Thread.join() somewhere, but may require re-architecting the whole program to keep track of what all threads are doing.

Related issues: #113964, #114570.

CPython versions tested on:

3.12.2, 3.11.7

Operating systems tested on:

Linux, macOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)topic-subinterpreterstype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions