gh-134939: Add the concurrent.interpreters Module (gh-133958)

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.)
This commit is contained in:
Eric Snow 2025-06-11 17:35:48 -06:00 committed by GitHub
parent b706ff003c
commit 62143736b6
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
**/*interpreteridobject.* @ericsnowcurrently
**/*crossinterp* @ericsnowcurrently
Lib/test/support/interpreters/ @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/concurrent/futures/interpreter.py @ericsnowcurrently
# Android
**/*Android* @mhsmith @freakboy3742

View File

@ -18,6 +18,7 @@ multitasking). Here's an overview:
multiprocessing.shared_memory.rst
concurrent.rst
concurrent.futures.rst
concurrent.interpreters.rst
subprocess.rst
sched.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
==============================
Currently, there is only one module in this package:
This package contains the following modules:
* :mod:`concurrent.futures` -- Launching parallel tasks
* :mod:`concurrent.interpreters` -- Multiple interpreters in the same process

View File

@ -27,3 +27,8 @@ overview:
inspect.rst
annotationlib.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.
* :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 750: Template strings <whatsnew314-pep750>`
* :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
============
.. _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:
PEP 750: Template strings
@ -1109,6 +1205,8 @@ calendar
concurrent.futures
------------------
.. _whatsnew314-concurrent-futures-interp-pool:
* Add :class:`~concurrent.futures.InterpreterPoolExecutor`,
which exposes "subinterpreters" (multiple Python interpreters in the
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:
continue
except ModuleNotFoundError:
# interpreters.queues doesn't exist, which means
# interpreters._queues doesn't exist, which means
# QueueEmpty doesn't. Act as though it does.
continue
else:

View File

@ -9,6 +9,10 @@ from _interpreters import (
InterpreterError, InterpreterNotFoundError, NotShareableError,
is_shareable,
)
from ._queues import (
create as create_queue,
Queue, QueueEmpty, QueueFull,
)
__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 = """
{superstr}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2514,7 +2514,7 @@ XMLLIBSUBDIRS= xml xml/dom xml/etree xml/parsers xml/sax
LIBSUBDIRS= asyncio \
collections \
compression compression/_common compression/zstd \
concurrent concurrent/futures \
concurrent concurrent/futures concurrent/interpreters \
csv \
ctypes ctypes/macholib \
curses \
@ -2573,7 +2573,6 @@ TESTSUBDIRS= idlelib/idle_test \
test/subprocessdata \
test/support \
test/support/_hypothesis_stubs \
test/support/interpreters \
test/test_asyncio \
test/test_capi \
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;
}
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 *************************************************************/
@ -2742,15 +2758,9 @@ _get_current_channelend_type(int end)
}
if (cls == NULL) {
// Force the module to be loaded, to register the type.
PyObject *highlevel = PyImport_ImportModule("interpreters.channels");
if (highlevel == NULL) {
PyErr_Clear();
highlevel = PyImport_ImportModule("test.support.interpreters.channels");
if (highlevel == NULL) {
return NULL;
}
if (ensure_highlevel_module_loaded() < 0) {
return NULL;
}
Py_DECREF(highlevel);
if (end == CHANNEL_SEND) {
cls = state->send_channel_type;
}

View File

@ -136,13 +136,10 @@ idarg_int64_converter(PyObject *arg, void *ptr)
static int
ensure_highlevel_module_loaded(void)
{
PyObject *highlevel = PyImport_ImportModule("interpreters.queues");
PyObject *highlevel =
PyImport_ImportModule("concurrent.interpreters._queues");
if (highlevel == NULL) {
PyErr_Clear();
highlevel = PyImport_ImportModule("test.support.interpreters.queues");
if (highlevel == NULL) {
return -1;
}
return -1;
}
Py_DECREF(highlevel);
return 0;
@ -299,7 +296,7 @@ add_QueueError(PyObject *mod)
{
module_state *state = get_module_state(mod);
#define PREFIX "test.support.interpreters."
#define PREFIX "concurrent.interpreters."
#define ADD_EXCTYPE(NAME, BASE, DOC) \
assert(state->NAME == NULL); \
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.
Generally you can use test.support.interpreters,
Generally you can use the interpreters module,
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 *
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 = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "interpreters.InterpreterError",
.tp_name = "concurrent.interpreters.InterpreterError",
.tp_doc = PyDoc_STR("A cross-interpreter operation failed"),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
@ -37,7 +37,7 @@ PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError;
static PyTypeObject _PyExc_InterpreterNotFoundError = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "interpreters.InterpreterNotFoundError",
.tp_name = "concurrent.interpreters.InterpreterNotFoundError",
.tp_doc = PyDoc_STR("An interpreter was not found"),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
@ -51,7 +51,7 @@ PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFou
static int
_init_notshareableerror(exceptions_t *state)
{
const char *name = "interpreters.NotShareableError";
const char *name = "concurrent.interpreters.NotShareableError";
PyObject *base = PyExc_TypeError;
PyObject *ns = NULL;
PyObject *exctype = PyErr_NewException(name, base, ns);