[3.14] gh-134939: Add the concurrent.interpreters Module (gh-135414)

PEP-734 has been accepted (for 3.14).

(FTR, I'm opposed to putting this under the concurrent package, but
doing so is the SC condition under which the module can land in 3.14.)

(cherry picked from commit 62143736b, AKA gh-133958)
This commit is contained in:
Eric Snow 2025-06-12 08:19:26 -06:00 committed by GitHub
parent 8cb7d9a810
commit 04273adae0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 389 additions and 85 deletions

6
.github/CODEOWNERS vendored
View File

@ -281,9 +281,13 @@ Doc/howto/clinic.rst @erlend-aasland
# Subinterpreters # Subinterpreters
**/*interpreteridobject.* @ericsnowcurrently **/*interpreteridobject.* @ericsnowcurrently
**/*crossinterp* @ericsnowcurrently **/*crossinterp* @ericsnowcurrently
Lib/test/support/interpreters/ @ericsnowcurrently
Modules/_interp*module.c @ericsnowcurrently Modules/_interp*module.c @ericsnowcurrently
Lib/test/test__interp*.py @ericsnowcurrently
Lib/concurrent/interpreters/ @ericsnowcurrently
Lib/test/support/channels.py @ericsnowcurrently
Doc/library/concurrent.interpreters.rst @ericsnowcurrently
Lib/test/test_interpreters/ @ericsnowcurrently Lib/test/test_interpreters/ @ericsnowcurrently
Lib/concurrent/futures/interpreter.py @ericsnowcurrently
# Android # Android
**/*Android* @mhsmith @freakboy3742 **/*Android* @mhsmith @freakboy3742

View File

@ -18,6 +18,7 @@ multitasking). Here's an overview:
multiprocessing.shared_memory.rst multiprocessing.shared_memory.rst
concurrent.rst concurrent.rst
concurrent.futures.rst concurrent.futures.rst
concurrent.interpreters.rst
subprocess.rst subprocess.rst
sched.rst sched.rst
queue.rst queue.rst

View File

@ -0,0 +1,198 @@
:mod:`!concurrent.interpreters` --- Multiple interpreters in the same process
=============================================================================
.. module:: concurrent.interpreters
:synopsis: Multiple interpreters in the same process
.. moduleauthor:: Eric Snow <ericsnowcurrently@gmail.com>
.. sectionauthor:: Eric Snow <ericsnowcurrently@gmail.com>
.. versionadded:: 3.14
**Source code:** :source:`Lib/concurrent/interpreters.py`
--------------
Introduction
------------
The :mod:`!concurrent.interpreters` module constructs higher-level
interfaces on top of the lower level :mod:`!_interpreters` module.
.. XXX Add references to the upcoming HOWTO docs in the seealso block.
.. seealso::
:ref:`isolating-extensions-howto`
how to update an extension module to support multiple interpreters
:pep:`554`
:pep:`734`
:pep:`684`
.. XXX Why do we disallow multiple interpreters on WASM?
.. include:: ../includes/wasm-notavail.rst
Key details
-----------
Before we dive into examples, there are a small number of details
to keep in mind about using multiple interpreters:
* isolated, by default
* no implicit threads
* not all PyPI packages support use in multiple interpreters yet
.. XXX Are there other relevant details to list?
In the context of multiple interpreters, "isolated" means that
different interpreters do not share any state. In practice, there is some
process-global data they all share, but that is managed by the runtime.
Reference
---------
This module defines the following functions:
.. function:: list_all()
Return a :class:`list` of :class:`Interpreter` objects,
one for each existing interpreter.
.. function:: get_current()
Return an :class:`Interpreter` object for the currently running
interpreter.
.. function:: get_main()
Return an :class:`Interpreter` object for the main interpreter.
.. function:: create()
Initialize a new (idle) Python interpreter
and return a :class:`Interpreter` object for it.
Interpreter objects
^^^^^^^^^^^^^^^^^^^
.. class:: Interpreter(id)
A single interpreter in the current process.
Generally, :class:`Interpreter` shouldn't be called directly.
Instead, use :func:`create` or one of the other module functions.
.. attribute:: id
(read-only)
The interpreter's ID.
.. attribute:: whence
(read-only)
A string describing where the interpreter came from.
.. method:: is_running()
Return ``True`` if the interpreter is currently executing code
in its :mod:`!__main__` module and ``False`` otherwise.
.. method:: close()
Finalize and destroy the interpreter.
.. method:: prepare_main(ns=None, **kwargs)
Bind "shareable" objects in the interpreter's
:mod:`!__main__` module.
.. method:: exec(code, /, dedent=True)
Run the given source code in the interpreter (in the current thread).
.. method:: call(callable, /, *args, **kwargs)
Return the result of calling running the given function in the
interpreter (in the current thread).
.. method:: call_in_thread(callable, /, *args, **kwargs)
Run the given function in the interpreter (in a new thread).
Exceptions
^^^^^^^^^^
.. exception:: InterpreterError
This exception, a subclass of :exc:`Exception`, is raised when
an interpreter-related error happens.
.. exception:: InterpreterNotFoundError
This exception, a subclass of :exc:`InterpreterError`, is raised when
the targeted interpreter no longer exists.
.. exception:: ExecutionFailed
This exception, a subclass of :exc:`InterpreterError`, is raised when
the running code raised an uncaught exception.
.. attribute:: excinfo
A basic snapshot of the exception raised in the other interpreter.
.. XXX Document the excinfoattrs?
.. exception:: NotShareableError
This exception, a subclass of :exc:`TypeError`, is raised when
an object cannot be sent to another interpreter.
.. XXX Add functions for communicating between interpreters.
Basic usage
-----------
Creating an interpreter and running code in it::
from concurrent import interpreters
interp = interpreters.create()
# Run in the current OS thread.
interp.exec('print("spam!")')
interp.exec("""if True:
print('spam!')
""")
from textwrap import dedent
interp.exec(dedent("""
print('spam!')
"""))
def run():
print('spam!')
interp.call(run)
# Run in new OS thread.
t = interp.call_in_thread(run)
t.join()
.. XXX Explain about object "sharing".

View File

@ -1,6 +1,7 @@
The :mod:`!concurrent` package The :mod:`!concurrent` package
============================== ==============================
Currently, there is only one module in this package: This package contains the following modules:
* :mod:`concurrent.futures` -- Launching parallel tasks * :mod:`concurrent.futures` -- Launching parallel tasks
* :mod:`concurrent.interpreters` -- Multiple interpreters in the same process

View File

@ -27,3 +27,8 @@ overview:
inspect.rst inspect.rst
annotationlib.rst annotationlib.rst
site.rst site.rst
.. seealso::
* See the :mod:`concurrent.interpreters` module, which similarly
exposes core runtime functionality.

View File

@ -83,6 +83,7 @@ and improvements in user-friendliness and correctness.
.. PEP-sized items next. .. PEP-sized items next.
* :ref:`PEP 649 and 749: deferred evaluation of annotations <whatsnew314-pep649>` * :ref:`PEP 649 and 749: deferred evaluation of annotations <whatsnew314-pep649>`
* :ref:`PEP 734: Multiple Interpreters in the Stdlib <whatsnew314-pep734>`
* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>` * :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
* :ref:`PEP 750: Template strings <whatsnew314-pep750>` * :ref:`PEP 750: Template strings <whatsnew314-pep750>`
* :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>` * :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>`
@ -123,6 +124,101 @@ of Python. See :ref:`below <whatsnew314-refcount>` for details.
New features New features
============ ============
.. _whatsnew314-pep734:
PEP 734: Multiple Interpreters in the Stdlib
--------------------------------------------
The CPython runtime supports running multiple copies of Python in the
same process simultaneously and has done so for over 20 years.
Each of these separate copies is called an "interpreter".
However, the feature had been available only through the C-API.
That limitation is removed in the 3.14 release,
with the new :mod:`concurrent.interpreters` module.
There are at least two notable reasons why using multiple interpreters
is worth considering:
* they support a new (to Python), human-friendly concurrency model
* true multi-core parallelism
For some use cases, concurrency in software enables efficiency and
can simplify software, at a high level. At the same time, implementing
and maintaining all but the simplest concurrency is often a struggle
for the human brain. That especially applies to plain threads
(for example, :mod:`threading`), where all memory is shared between all threads.
With multiple isolated interpreters, you can take advantage of a class
of concurrency models, like CSP or the actor model, that have found
success in other programming languages, like Smalltalk, Erlang,
Haskell, and Go. Think of multiple interpreters like threads
but with opt-in sharing.
Regarding multi-core parallelism: as of the 3.12 release, interpreters
are now sufficiently isolated from one another to be used in parallel.
(See :pep:`684`.) This unlocks a variety of CPU-intensive use cases
for Python that were limited by the :term:`GIL`.
Using multiple interpreters is similar in many ways to
:mod:`multiprocessing`, in that they both provide isolated logical
"processes" that can run in parallel, with no sharing by default.
However, when using multiple interpreters, an application will use
fewer system resources and will operate more efficiently (since it
stays within the same process). Think of multiple interpreters as
having the isolation of processes with the efficiency of threads.
.. XXX Add an example or two.
.. XXX Link to the not-yet-added HOWTO doc.
While the feature has been around for decades, multiple interpreters
have not been used widely, due to low awareness and the lack of a stdlib
module. Consequently, they currently have several notable limitations,
which will improve significantly now that the feature is finally
going mainstream.
Current limitations:
* starting each interpreter has not been optimized yet
* each interpreter uses more memory than necessary
(we will be working next on extensive internal sharing between
interpreters)
* there aren't many options *yet* for truly sharing objects or other
data between interpreters (other than :type:`memoryview`)
* many extension modules on PyPI are not compatible with multiple
interpreters yet (stdlib extension modules *are* compatible)
* the approach to writing applications that use multiple isolated
interpreters is mostly unfamiliar to Python users, for now
The impact of these limitations will depend on future CPython
improvements, how interpreters are used, and what the community solves
through PyPI packages. Depending on the use case, the limitations may
not have much impact, so try it out!
Furthermore, future CPython releases will reduce or eliminate overhead
and provide utilities that are less appropriate on PyPI. In the
meantime, most of the limitations can also be addressed through
extension modules, meaning PyPI packages can fill any gap for 3.14, and
even back to 3.12 where interpreters were finally properly isolated and
stopped sharing the :term:`GIL`. Likewise, we expect to slowly see
libraries on PyPI for high-level abstractions on top of interpreters.
Regarding extension modules, work is in progress to update some PyPI
projects, as well as tools like Cython, pybind11, nanobind, and PyO3.
The steps for isolating an extension module are found at
:ref:`isolating-extensions-howto`. Isolating a module has a lot of
overlap with what is required to support
:ref:`free-threading <whatsnew314-free-threaded-cpython>`,
so the ongoing work in the community in that area will help accelerate
support for multiple interpreters.
Also added in 3.14: :ref:`concurrent.futures.InterpreterPoolExecutor
<whatsnew314-concurrent-futures-interp-pool>`.
.. seealso::
:pep:`734`.
.. _whatsnew314-pep750: .. _whatsnew314-pep750:
PEP 750: Template strings PEP 750: Template strings
@ -1109,6 +1205,8 @@ calendar
concurrent.futures concurrent.futures
------------------ ------------------
.. _whatsnew314-concurrent-futures-interp-pool:
* Add :class:`~concurrent.futures.InterpreterPoolExecutor`, * Add :class:`~concurrent.futures.InterpreterPoolExecutor`,
which exposes "subinterpreters" (multiple Python interpreters in the which exposes "subinterpreters" (multiple Python interpreters in the
same process) to Python code. This is separate from the proposed API same process) to Python code. This is separate from the proposed API

View File

@ -167,7 +167,7 @@ class WorkerContext(_thread.WorkerContext):
except _interpqueues.QueueError: except _interpqueues.QueueError:
continue continue
except ModuleNotFoundError: except ModuleNotFoundError:
# interpreters.queues doesn't exist, which means # interpreters._queues doesn't exist, which means
# QueueEmpty doesn't. Act as though it does. # QueueEmpty doesn't. Act as though it does.
continue continue
else: else:

View File

@ -9,6 +9,10 @@ from _interpreters import (
InterpreterError, InterpreterNotFoundError, NotShareableError, InterpreterError, InterpreterNotFoundError, NotShareableError,
is_shareable, is_shareable,
) )
from ._queues import (
create as create_queue,
Queue, QueueEmpty, QueueFull,
)
__all__ = [ __all__ = [
@ -20,21 +24,6 @@ __all__ = [
] ]
_queuemod = None
def __getattr__(name):
if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'):
global create_queue, Queue, QueueEmpty, QueueFull
ns = globals()
from .queues import (
create as create_queue,
Queue, QueueEmpty, QueueFull,
)
return ns[name]
else:
raise AttributeError(name)
_EXEC_FAILURE_STR = """ _EXEC_FAILURE_STR = """
{superstr} {superstr}

View File

@ -61,7 +61,7 @@ class UnboundItem:
def __repr__(self): def __repr__(self):
return f'{self._MODULE}.{self._NAME}' return f'{self._MODULE}.{self._NAME}'
# return f'interpreters.queues.UNBOUND' # return f'interpreters._queues.UNBOUND'
UNBOUND = object.__new__(UnboundItem) UNBOUND = object.__new__(UnboundItem)

View File

@ -2,14 +2,14 @@
import time import time
import _interpchannels as _channels import _interpchannels as _channels
from . import _crossinterp from concurrent.interpreters import _crossinterp
# aliases: # aliases:
from _interpchannels import ( from _interpchannels import (
ChannelError, ChannelNotFoundError, ChannelClosedError, ChannelError, ChannelNotFoundError, ChannelClosedError,
ChannelEmptyError, ChannelNotEmptyError, ChannelEmptyError, ChannelNotEmptyError,
) )
from ._crossinterp import ( from concurrent.interpreters._crossinterp import (
UNBOUND_ERROR, UNBOUND_REMOVE, UNBOUND_ERROR, UNBOUND_REMOVE,
) )

View File

@ -9,7 +9,7 @@ import unittest
from test.support import import_helper, skip_if_sanitizer from test.support import import_helper, skip_if_sanitizer
_channels = import_helper.import_module('_interpchannels') _channels = import_helper.import_module('_interpchannels')
from test.support.interpreters import _crossinterp from concurrent.interpreters import _crossinterp
from test.test__interpreters import ( from test.test__interpreters import (
_interpreters, _interpreters,
_run_output, _run_output,

View File

@ -8,10 +8,10 @@ import unittest
from concurrent.futures.interpreter import ( from concurrent.futures.interpreter import (
ExecutionFailed, BrokenInterpreterPool, ExecutionFailed, BrokenInterpreterPool,
) )
from concurrent.interpreters import _queues as queues
import _interpreters import _interpreters
from test import support from test import support
import test.test_asyncio.utils as testasyncio_utils import test.test_asyncio.utils as testasyncio_utils
from test.support.interpreters import queues
from .executor import ExecutorTest, mul from .executor import ExecutorTest, mul
from .util import BaseTestCase, InterpreterPoolMixin, setup_module from .util import BaseTestCase, InterpreterPoolMixin, setup_module

View File

@ -13,11 +13,11 @@ from test.support import script_helper
from test.support import import_helper from test.support import import_helper
# Raise SkipTest if subinterpreters not supported. # Raise SkipTest if subinterpreters not supported.
_interpreters = import_helper.import_module('_interpreters') _interpreters = import_helper.import_module('_interpreters')
from concurrent import interpreters
from test.support import Py_GIL_DISABLED from test.support import Py_GIL_DISABLED
from test.support import interpreters
from test.support import force_not_colorized from test.support import force_not_colorized
import test._crossinterp_definitions as defs import test._crossinterp_definitions as defs
from test.support.interpreters import ( from concurrent.interpreters import (
InterpreterError, InterpreterNotFoundError, ExecutionFailed, InterpreterError, InterpreterNotFoundError, ExecutionFailed,
) )
from .utils import ( from .utils import (
@ -133,7 +133,7 @@ class CreateTests(TestBase):
main, = interpreters.list_all() main, = interpreters.list_all()
interp = interpreters.create() interp = interpreters.create()
out = _run_output(interp, dedent(""" out = _run_output(interp, dedent("""
from test.support import interpreters from concurrent import interpreters
interp = interpreters.create() interp = interpreters.create()
print(interp.id) print(interp.id)
""")) """))
@ -196,7 +196,7 @@ class GetCurrentTests(TestBase):
main = interpreters.get_main() main = interpreters.get_main()
interp = interpreters.create() interp = interpreters.create()
out = _run_output(interp, dedent(""" out = _run_output(interp, dedent("""
from test.support import interpreters from concurrent import interpreters
cur = interpreters.get_current() cur = interpreters.get_current()
print(cur.id) print(cur.id)
""")) """))
@ -213,7 +213,7 @@ class GetCurrentTests(TestBase):
with self.subTest('subinterpreter'): with self.subTest('subinterpreter'):
interp = interpreters.create() interp = interpreters.create()
out = _run_output(interp, dedent(""" out = _run_output(interp, dedent("""
from test.support import interpreters from concurrent import interpreters
cur = interpreters.get_current() cur = interpreters.get_current()
print(id(cur)) print(id(cur))
cur = interpreters.get_current() cur = interpreters.get_current()
@ -225,7 +225,7 @@ class GetCurrentTests(TestBase):
with self.subTest('per-interpreter'): with self.subTest('per-interpreter'):
interp = interpreters.create() interp = interpreters.create()
out = _run_output(interp, dedent(""" out = _run_output(interp, dedent("""
from test.support import interpreters from concurrent import interpreters
cur = interpreters.get_current() cur = interpreters.get_current()
print(id(cur)) print(id(cur))
""")) """))
@ -582,7 +582,7 @@ class TestInterpreterClose(TestBase):
main, = interpreters.list_all() main, = interpreters.list_all()
interp = interpreters.create() interp = interpreters.create()
out = _run_output(interp, dedent(f""" out = _run_output(interp, dedent(f"""
from test.support import interpreters from concurrent import interpreters
interp = interpreters.Interpreter({interp.id}) interp = interpreters.Interpreter({interp.id})
try: try:
interp.close() interp.close()
@ -599,7 +599,7 @@ class TestInterpreterClose(TestBase):
self.assertEqual(set(interpreters.list_all()), self.assertEqual(set(interpreters.list_all()),
{main, interp1, interp2}) {main, interp1, interp2})
interp1.exec(dedent(f""" interp1.exec(dedent(f"""
from test.support import interpreters from concurrent import interpreters
interp2 = interpreters.Interpreter({interp2.id}) interp2 = interpreters.Interpreter({interp2.id})
interp2.close() interp2.close()
interp3 = interpreters.create() interp3 = interpreters.create()
@ -806,7 +806,7 @@ class TestInterpreterExec(TestBase):
ham() ham()
""") """)
scriptfile = self.make_script('script.py', tempdir, text=""" scriptfile = self.make_script('script.py', tempdir, text="""
from test.support import interpreters from concurrent import interpreters
def script(): def script():
import spam import spam
@ -827,7 +827,7 @@ class TestInterpreterExec(TestBase):
~~~~~~~~~~~^^^^^^^^ ~~~~~~~~~~~^^^^^^^^
{interpmod_line.strip()} {interpmod_line.strip()}
raise ExecutionFailed(excinfo) raise ExecutionFailed(excinfo)
test.support.interpreters.ExecutionFailed: RuntimeError: uh-oh! concurrent.interpreters.ExecutionFailed: RuntimeError: uh-oh!
Uncaught in the interpreter: Uncaught in the interpreter:
@ -1281,7 +1281,7 @@ class TestInterpreterCall(TestBase):
# no module indirection # no module indirection
with self.subTest('no indirection'): with self.subTest('no indirection'):
text = run(f""" text = run(f"""
from test.support import interpreters from concurrent import interpreters
def spam(): def spam():
# This a global var... # This a global var...
@ -1301,7 +1301,7 @@ class TestInterpreterCall(TestBase):
""") """)
with self.subTest('indirect as func, direct interp'): with self.subTest('indirect as func, direct interp'):
text = run(f""" text = run(f"""
from test.support import interpreters from concurrent import interpreters
import mymod import mymod
def spam(): def spam():
@ -1317,7 +1317,7 @@ class TestInterpreterCall(TestBase):
# indirect as func, indirect interp # indirect as func, indirect interp
new_mod('mymod', f""" new_mod('mymod', f"""
from test.support import interpreters from concurrent import interpreters
def run(func): def run(func):
interp = interpreters.create() interp = interpreters.create()
return interp.call(func) return interp.call(func)

View File

@ -8,8 +8,8 @@ import time
from test.support import import_helper from test.support import import_helper
# Raise SkipTest if subinterpreters not supported. # Raise SkipTest if subinterpreters not supported.
_channels = import_helper.import_module('_interpchannels') _channels = import_helper.import_module('_interpchannels')
from test.support import interpreters from concurrent import interpreters
from test.support.interpreters import channels from test.support import channels
from .utils import _run_output, TestBase from .utils import _run_output, TestBase
@ -171,7 +171,7 @@ class TestSendRecv(TestBase):
def test_send_recv_same_interpreter(self): def test_send_recv_same_interpreter(self):
interp = interpreters.create() interp = interpreters.create()
interp.exec(dedent(""" interp.exec(dedent("""
from test.support.interpreters import channels from test.support import channels
r, s = channels.create() r, s = channels.create()
orig = b'spam' orig = b'spam'
s.send_nowait(orig) s.send_nowait(orig)
@ -244,7 +244,7 @@ class TestSendRecv(TestBase):
def test_send_recv_nowait_same_interpreter(self): def test_send_recv_nowait_same_interpreter(self):
interp = interpreters.create() interp = interpreters.create()
interp.exec(dedent(""" interp.exec(dedent("""
from test.support.interpreters import channels from test.support import channels
r, s = channels.create() r, s = channels.create()
orig = b'spam' orig = b'spam'
s.send_nowait(orig) s.send_nowait(orig)
@ -387,7 +387,7 @@ class TestSendRecv(TestBase):
interp = interpreters.create() interp = interpreters.create()
_run_output(interp, dedent(f""" _run_output(interp, dedent(f"""
from test.support.interpreters import channels from test.support import channels
sch = channels.SendChannel({sch.id}) sch = channels.SendChannel({sch.id})
obj1 = b'spam' obj1 = b'spam'
obj2 = b'eggs' obj2 = b'eggs'
@ -482,7 +482,7 @@ class TestSendRecv(TestBase):
self.assertEqual(_channels.get_count(rch.id), 0) self.assertEqual(_channels.get_count(rch.id), 0)
_run_output(interp, dedent(f""" _run_output(interp, dedent(f"""
from test.support.interpreters import channels from test.support import channels
sch = channels.SendChannel({sch.id}) sch = channels.SendChannel({sch.id})
sch.send_nowait(1, unbounditems=channels.UNBOUND) sch.send_nowait(1, unbounditems=channels.UNBOUND)
sch.send_nowait(2, unbounditems=channels.UNBOUND_ERROR) sch.send_nowait(2, unbounditems=channels.UNBOUND_ERROR)
@ -518,7 +518,7 @@ class TestSendRecv(TestBase):
sch.send_nowait(1) sch.send_nowait(1)
_run_output(interp1, dedent(f""" _run_output(interp1, dedent(f"""
from test.support.interpreters import channels from test.support import channels
rch = channels.RecvChannel({rch.id}) rch = channels.RecvChannel({rch.id})
sch = channels.SendChannel({sch.id}) sch = channels.SendChannel({sch.id})
obj1 = rch.recv() obj1 = rch.recv()
@ -526,7 +526,7 @@ class TestSendRecv(TestBase):
sch.send_nowait(obj1, unbounditems=channels.UNBOUND_REMOVE) sch.send_nowait(obj1, unbounditems=channels.UNBOUND_REMOVE)
""")) """))
_run_output(interp2, dedent(f""" _run_output(interp2, dedent(f"""
from test.support.interpreters import channels from test.support import channels
rch = channels.RecvChannel({rch.id}) rch = channels.RecvChannel({rch.id})
sch = channels.SendChannel({sch.id}) sch = channels.SendChannel({sch.id})
obj2 = rch.recv() obj2 = rch.recv()

View File

@ -119,7 +119,7 @@ class StartupTests(TestBase):
# The main interpreter's sys.path[0] should be used by subinterpreters. # The main interpreter's sys.path[0] should be used by subinterpreters.
script = ''' script = '''
import sys import sys
from test.support import interpreters from concurrent import interpreters
orig = sys.path[0] orig = sys.path[0]
@ -170,7 +170,7 @@ class FinalizationTests(TestBase):
# is reported, even when subinterpreters get cleaned up at the end. # is reported, even when subinterpreters get cleaned up at the end.
import subprocess import subprocess
argv = [sys.executable, '-c', '''if True: argv = [sys.executable, '-c', '''if True:
from test.support import interpreters from concurrent import interpreters
interp = interpreters.create() interp = interpreters.create()
raise Exception raise Exception
'''] ''']

View File

@ -7,8 +7,8 @@ import unittest
from test.support import import_helper, Py_DEBUG from test.support import import_helper, Py_DEBUG
# Raise SkipTest if subinterpreters not supported. # Raise SkipTest if subinterpreters not supported.
_queues = import_helper.import_module('_interpqueues') _queues = import_helper.import_module('_interpqueues')
from test.support import interpreters from concurrent import interpreters
from test.support.interpreters import queues, _crossinterp from concurrent.interpreters import _queues as queues, _crossinterp
import test._crossinterp_definitions as defs import test._crossinterp_definitions as defs
from .utils import _run_output, TestBase as _TestBase from .utils import _run_output, TestBase as _TestBase
@ -127,7 +127,7 @@ class QueueTests(TestBase):
interp = interpreters.create() interp = interpreters.create()
interp.exec(dedent(f""" interp.exec(dedent(f"""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue1 = queues.Queue({queue1.id}) queue1 = queues.Queue({queue1.id})
""")); """));
@ -325,7 +325,7 @@ class TestQueueOps(TestBase):
def test_put_get_same_interpreter(self): def test_put_get_same_interpreter(self):
interp = interpreters.create() interp = interpreters.create()
interp.exec(dedent(""" interp.exec(dedent("""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue = queues.create() queue = queues.create()
""")) """))
for methname in ('get', 'get_nowait'): for methname in ('get', 'get_nowait'):
@ -352,7 +352,7 @@ class TestQueueOps(TestBase):
out = _run_output( out = _run_output(
interp, interp,
dedent(f""" dedent(f"""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue1 = queues.Queue({queue1.id}) queue1 = queues.Queue({queue1.id})
queue2 = queues.Queue({queue2.id}) queue2 = queues.Queue({queue2.id})
assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1' assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1'
@ -391,7 +391,7 @@ class TestQueueOps(TestBase):
interp = interpreters.create() interp = interpreters.create()
_run_output(interp, dedent(f""" _run_output(interp, dedent(f"""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id}) queue = queues.Queue({queue.id})
obj1 = b'spam' obj1 = b'spam'
obj2 = b'eggs' obj2 = b'eggs'
@ -469,7 +469,7 @@ class TestQueueOps(TestBase):
queue = queues.create() queue = queues.create()
interp = interpreters.create() interp = interpreters.create()
_run_output(interp, dedent(f""" _run_output(interp, dedent(f"""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id}) queue = queues.Queue({queue.id})
queue.put(1, unbounditems=queues.UNBOUND) queue.put(1, unbounditems=queues.UNBOUND)
queue.put(2, unbounditems=queues.UNBOUND_ERROR) queue.put(2, unbounditems=queues.UNBOUND_ERROR)
@ -505,14 +505,14 @@ class TestQueueOps(TestBase):
queue.put(1) queue.put(1)
_run_output(interp1, dedent(f""" _run_output(interp1, dedent(f"""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id}) queue = queues.Queue({queue.id})
obj1 = queue.get() obj1 = queue.get()
queue.put(2, unbounditems=queues.UNBOUND) queue.put(2, unbounditems=queues.UNBOUND)
queue.put(obj1, unbounditems=queues.UNBOUND_REMOVE) queue.put(obj1, unbounditems=queues.UNBOUND_REMOVE)
""")) """))
_run_output(interp2, dedent(f""" _run_output(interp2, dedent(f"""
from test.support.interpreters import queues from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id}) queue = queues.Queue({queue.id})
obj2 = queue.get() obj2 = queue.get()
obj1 = queue.get() obj1 = queue.get()

View File

@ -6,7 +6,7 @@ from test.support import import_helper
from test.support import threading_helper from test.support import threading_helper
# Raise SkipTest if subinterpreters not supported. # Raise SkipTest if subinterpreters not supported.
import_helper.import_module('_interpreters') import_helper.import_module('_interpreters')
from test.support import interpreters from concurrent import interpreters
from .utils import TestBase from .utils import TestBase

View File

@ -22,7 +22,7 @@ try:
import _interpreters import _interpreters
except ImportError as exc: except ImportError as exc:
raise unittest.SkipTest(str(exc)) raise unittest.SkipTest(str(exc))
from test.support import interpreters from concurrent import interpreters
try: try:

View File

@ -24,7 +24,7 @@ from test.support import import_helper
from test.support import force_not_colorized from test.support import force_not_colorized
from test.support import SHORT_TIMEOUT from test.support import SHORT_TIMEOUT
try: try:
from test.support import interpreters from concurrent import interpreters
except ImportError: except ImportError:
interpreters = None interpreters = None
import textwrap import textwrap

View File

@ -28,7 +28,7 @@ from test import lock_tests
from test import support from test import support
try: try:
from test.support import interpreters from concurrent import interpreters
except ImportError: except ImportError:
interpreters = None interpreters = None

View File

@ -2513,15 +2513,16 @@ class SubinterpreterTests(unittest.TestCase):
def setUpClass(cls): def setUpClass(cls):
global interpreters global interpreters
try: try:
from test.support import interpreters from concurrent import interpreters
except ModuleNotFoundError: except ModuleNotFoundError:
raise unittest.SkipTest('subinterpreters required') raise unittest.SkipTest('subinterpreters required')
import test.support.interpreters.channels from test.support import channels # noqa: F401
cls.create_channel = staticmethod(channels.create)
@cpython_only @cpython_only
@no_rerun('channels (and queues) might have a refleak; see gh-122199') @no_rerun('channels (and queues) might have a refleak; see gh-122199')
def test_static_types_inherited_slots(self): def test_static_types_inherited_slots(self):
rch, sch = interpreters.channels.create() rch, sch = self.create_channel()
script = textwrap.dedent(""" script = textwrap.dedent("""
import test.support import test.support
@ -2547,7 +2548,7 @@ class SubinterpreterTests(unittest.TestCase):
main_results = collate_results(raw) main_results = collate_results(raw)
interp = interpreters.create() interp = interpreters.create()
interp.exec('from test.support import interpreters') interp.exec('from concurrent import interpreters')
interp.prepare_main(sch=sch) interp.prepare_main(sch=sch)
interp.exec(script) interp.exec(script)
raw = rch.recv_nowait() raw = rch.recv_nowait()

View File

@ -2514,7 +2514,7 @@ XMLLIBSUBDIRS= xml xml/dom xml/etree xml/parsers xml/sax
LIBSUBDIRS= asyncio \ LIBSUBDIRS= asyncio \
collections \ collections \
compression compression/_common compression/zstd \ compression compression/_common compression/zstd \
concurrent concurrent/futures \ concurrent concurrent/futures concurrent/interpreters \
csv \ csv \
ctypes ctypes/macholib \ ctypes ctypes/macholib \
curses \ curses \
@ -2573,7 +2573,6 @@ TESTSUBDIRS= idlelib/idle_test \
test/subprocessdata \ test/subprocessdata \
test/support \ test/support \
test/support/_hypothesis_stubs \ test/support/_hypothesis_stubs \
test/support/interpreters \
test/test_asyncio \ test/test_asyncio \
test/test_capi \ test/test_capi \
test/test_cext \ test/test_cext \

View File

@ -0,0 +1 @@
Add the :mod:`concurrent.interpreters` module. See :pep:`734`.

View File

@ -220,6 +220,22 @@ wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout)
return 0; return 0;
} }
static int
ensure_highlevel_module_loaded(void)
{
PyObject *highlevel =
PyImport_ImportModule("concurrent.interpreters._channels");
if (highlevel == NULL) {
PyErr_Clear();
highlevel = PyImport_ImportModule("test.support.channels");
if (highlevel == NULL) {
return -1;
}
}
Py_DECREF(highlevel);
return 0;
}
/* module state *************************************************************/ /* module state *************************************************************/
@ -2742,15 +2758,9 @@ _get_current_channelend_type(int end)
} }
if (cls == NULL) { if (cls == NULL) {
// Force the module to be loaded, to register the type. // Force the module to be loaded, to register the type.
PyObject *highlevel = PyImport_ImportModule("interpreters.channels"); if (ensure_highlevel_module_loaded() < 0) {
if (highlevel == NULL) { return NULL;
PyErr_Clear();
highlevel = PyImport_ImportModule("test.support.interpreters.channels");
if (highlevel == NULL) {
return NULL;
}
} }
Py_DECREF(highlevel);
if (end == CHANNEL_SEND) { if (end == CHANNEL_SEND) {
cls = state->send_channel_type; cls = state->send_channel_type;
} }

View File

@ -136,13 +136,10 @@ idarg_int64_converter(PyObject *arg, void *ptr)
static int static int
ensure_highlevel_module_loaded(void) ensure_highlevel_module_loaded(void)
{ {
PyObject *highlevel = PyImport_ImportModule("interpreters.queues"); PyObject *highlevel =
PyImport_ImportModule("concurrent.interpreters._queues");
if (highlevel == NULL) { if (highlevel == NULL) {
PyErr_Clear(); return -1;
highlevel = PyImport_ImportModule("test.support.interpreters.queues");
if (highlevel == NULL) {
return -1;
}
} }
Py_DECREF(highlevel); Py_DECREF(highlevel);
return 0; return 0;
@ -299,7 +296,7 @@ add_QueueError(PyObject *mod)
{ {
module_state *state = get_module_state(mod); module_state *state = get_module_state(mod);
#define PREFIX "test.support.interpreters." #define PREFIX "concurrent.interpreters."
#define ADD_EXCTYPE(NAME, BASE, DOC) \ #define ADD_EXCTYPE(NAME, BASE, DOC) \
assert(state->NAME == NULL); \ assert(state->NAME == NULL); \
if (add_exctype(mod, &state->NAME, PREFIX #NAME, DOC, BASE) < 0) { \ if (add_exctype(mod, &state->NAME, PREFIX #NAME, DOC, BASE) < 0) { \

View File

@ -1788,9 +1788,9 @@ finally:
/* To run some code in a sub-interpreter. /* To run some code in a sub-interpreter.
Generally you can use test.support.interpreters, Generally you can use the interpreters module,
but we keep this helper as a distinct implementation. but we keep this helper as a distinct implementation.
That's especially important for testing test.support.interpreters. That's especially important for testing the interpreters module.
*/ */
static PyObject * static PyObject *
run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)

View File

@ -24,7 +24,7 @@ _ensure_current_cause(PyThreadState *tstate, PyObject *cause)
static PyTypeObject _PyExc_InterpreterError = { static PyTypeObject _PyExc_InterpreterError = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "interpreters.InterpreterError", .tp_name = "concurrent.interpreters.InterpreterError",
.tp_doc = PyDoc_STR("A cross-interpreter operation failed"), .tp_doc = PyDoc_STR("A cross-interpreter operation failed"),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse, //.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
@ -37,7 +37,7 @@ PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError;
static PyTypeObject _PyExc_InterpreterNotFoundError = { static PyTypeObject _PyExc_InterpreterNotFoundError = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "interpreters.InterpreterNotFoundError", .tp_name = "concurrent.interpreters.InterpreterNotFoundError",
.tp_doc = PyDoc_STR("An interpreter was not found"), .tp_doc = PyDoc_STR("An interpreter was not found"),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse, //.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
@ -51,7 +51,7 @@ PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFou
static int static int
_init_notshareableerror(exceptions_t *state) _init_notshareableerror(exceptions_t *state)
{ {
const char *name = "interpreters.NotShareableError"; const char *name = "concurrent.interpreters.NotShareableError";
PyObject *base = PyExc_TypeError; PyObject *base = PyExc_TypeError;
PyObject *ns = NULL; PyObject *ns = NULL;
PyObject *exctype = PyErr_NewException(name, base, ns); PyObject *exctype = PyErr_NewException(name, base, ns);