Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 8 additions & 54 deletions Lib/concurrent/interpreters/_crossinterp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,23 @@ class ItemInterpreterDestroyed(Exception):
"""Raised when trying to get an item whose interpreter was destroyed."""


class classonly:
"""A non-data descriptor that makes a value only visible on the class.

This is like the "classmethod" builtin, but does not show up on
instances of the class. It may be used as a decorator.
"""

def __init__(self, value):
self.value = value
self.getter = classmethod(value).__get__
self.name = None

def __set_name__(self, cls, name):
if self.name is not None:
raise TypeError('already used')
self.name = name

def __get__(self, obj, cls):
if obj is not None:
raise AttributeError(self.name)
# called on the class
return self.getter(None, cls)


class UnboundItem:
"""Represents a cross-interpreter item no longer bound to an interpreter.

An item is unbound when the interpreter that added it to the
cross-interpreter container is destroyed.
"""

__slots__ = ()

@classonly
def singleton(cls, kind, module, name='UNBOUND'):
doc = cls.__doc__
if doc:
doc = doc.replace(
'cross-interpreter container', kind,
).replace(
'cross-interpreter', kind,
)
subclass = type(
f'Unbound{kind.capitalize()}Item',
(cls,),
{
"_MODULE": module,
"_NAME": name,
"__doc__": doc,
},
)
return object.__new__(subclass)

_MODULE = __name__
_NAME = 'UNBOUND'

def __new__(cls):
raise Exception(f'use {cls._MODULE}.{cls._NAME}')
def __init__(self, kind, module, name='UNBOUND'):
self._kind = kind
self._module = module
self._name = name

def __repr__(self):
return f'{self._MODULE}.{self._NAME}'
# return f'interpreters._queues.UNBOUND'
return f'{self._module}.{self._name}'


UNBOUND = object.__new__(UnboundItem)
UNBOUND = UnboundItem('cross-interpreter', __name__)
UNBOUND_ERROR = object()
UNBOUND_REMOVE = object()

Expand All @@ -84,6 +36,8 @@ def __repr__(self):

def serialize_unbound(unbound):
op = unbound
if isinstance(op, UnboundItem):
return _UNBOUND_CONSTANT_TO_FLAG[UNBOUND],
try:
flag = _UNBOUND_CONSTANT_TO_FLAG[op]
except KeyError:
Expand Down
4 changes: 1 addition & 3 deletions Lib/concurrent/interpreters/_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,10 @@ class ItemInterpreterDestroyed(QueueError,
_PICKLED = 1


UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
UNBOUND = _crossinterp.UnboundItem('queue', __name__)


def _serialize_unbound(unbound):
if unbound is UNBOUND:
unbound = _crossinterp.UNBOUND
return _crossinterp.serialize_unbound(unbound)


Expand Down
4 changes: 1 addition & 3 deletions Lib/test/support/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ class ItemInterpreterDestroyed(ChannelError,
"""Raised from get() and get_nowait()."""


UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
UNBOUND = _crossinterp.UnboundItem('queue', __name__)


def _serialize_unbound(unbound):
if unbound is UNBOUND:
unbound = _crossinterp.UNBOUND
return _crossinterp.serialize_unbound(unbound)


Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_interpreters/test_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Raise SkipTest if subinterpreters not supported.
_queues = import_helper.import_module('_interpqueues')
from concurrent import interpreters
from concurrent.futures import InterpreterPoolExecutor
from concurrent.interpreters import _queues as queues, _crossinterp
from .utils import _run_output, TestBase as _TestBase

Expand Down Expand Up @@ -93,6 +94,14 @@ def test_bind_release(self):
with self.assertRaises(queues.QueueError):
_queues.release(qid)

def test_interpreter_pool_executor_after_reload(self):
# Regression test for gh-142414 (KeyError in serialize_unbound).
importlib.reload(queues)
code = "import struct"
with InterpreterPoolExecutor(max_workers=1) as executor:
results = executor.map(exec, [code] * 1)
self.assertEqual(list(results), [None] * 1)


class QueueTests(TestBase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix spurious KeyError when concurrent.interpreters is reloaded after import.
Loading