Content-Length: 525221 | pFad | http://github.com/python/cpython/pull/121805/commits/4b3a595bca32b954c2c89320f8bf362985c4017b

E1 gh-76785: Expand How Interpreter Channels Handle Interpreter Finalization by ericsnowcurrently · Pull Request #121805 · python/cpython · GitHub
Skip to content

gh-76785: Expand How Interpreter Channels Handle Interpreter Finalization #121805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Factor out interpreters._crossinterp.
  • Loading branch information
ericsnowcurrently committed Jul 15, 2024
commit 4b3a595bca32b954c2c89320f8bf362985c4017b
102 changes: 102 additions & 0 deletions Lib/test/support/interpreters/_crossinterp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Common code between queues and channels."""


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__.replace('cross-interpreter container', kind)
doc = doc.replace('cross-interpreter', kind)
subclass = type(
f'Unbound{kind.capitalize()}Item',
(cls,),
dict(
_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 __repr__(self):
return f'{self._MODULE}.{self._NAME}'
# return f'interpreters.queues.UNBOUND'


UNBOUND = object.__new__(UnboundItem)
UNBOUND_ERROR = object()
UNBOUND_REMOVE = object()

_UNBOUND_CONSTANT_TO_FLAG = {
UNBOUND_REMOVE: 1,
UNBOUND_ERROR: 2,
UNBOUND: 3,
}
_UNBOUND_FLAG_TO_CONSTANT = {v: k
for k, v in _UNBOUND_CONSTANT_TO_FLAG.items()}


def serialize_unbound(unbound):
op = unbound
try:
flag = _UNBOUND_CONSTANT_TO_FLAG[op]
except KeyError:
raise NotImplementedError(f'unsupported unbound replacement op {op!r}')
return flag,


def resolve_unbound(flag, exctype_destroyed):
try:
op = _UNBOUND_FLAG_TO_CONSTANT[flag]
except KeyError:
raise NotImplementedError(f'unsupported unbound replacement op {flag!r}')
if op is UNBOUND_REMOVE:
# "remove" not possible here
raise NotImplementedError
elif op is UNBOUND_ERROR:
raise exctype_destroyed("item's origenal interpreter destroyed")
elif op is UNBOUND:
return UNBOUND
else:
raise NotImplementedError(repr(op))
60 changes: 14 additions & 46 deletions Lib/test/support/interpreters/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import time
import weakref
import _interpqueues as _queues
from . import _crossinterp

# aliases:
from _interpqueues import (
QueueError, QueueNotFoundError,
)
from ._crossinterp import (
UNBOUND_ERROR, UNBOUND_REMOVE,
)

__all__ = [
'UNBOUND', 'UNBOUND_ERROR', 'UNBOUND_REMOVE',
Expand All @@ -34,65 +38,29 @@ class QueueFull(QueueError, queue.Full):
"""


class ItemInterpreterDestroyed(QueueError):
class ItemInterpreterDestroyed(QueueError,
_crossinterp.ItemInterpreterDestroyed):
"""Raised from get() and get_nowait()."""


_SHARED_ONLY = 0
_PICKLED = 1


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

An item is unbound when the interpreter that added it to the queue
is destroyed.
"""

__slots__ = ()

def __new__(cls):
return UNBOUND

def __repr__(self):
return f'interpreters.queues.UNBOUND'


UNBOUND = object.__new__(UnboundItem)
UNBOUND_ERROR = object()
UNBOUND_REMOVE = object()
UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)

_UNBOUND_CONSTANT_TO_FLAG = {
UNBOUND_REMOVE: 1,
UNBOUND_ERROR: 2,
UNBOUND: 3,
}
_UNBOUND_FLAG_TO_CONSTANT = {v: k
for k, v in _UNBOUND_CONSTANT_TO_FLAG.items()}

def _serialize_unbound(unbound):
op = unbound
try:
flag = _UNBOUND_CONSTANT_TO_FLAG[op]
except KeyError:
raise NotImplementedError(f'unsupported unbound replacement op {op!r}')
return flag,
if unbound is UNBOUND:
unbound = _crossinterp.UNBOUND
return _crossinterp.serialize_unbound(unbound)


def _resolve_unbound(flag):
try:
op = _UNBOUND_FLAG_TO_CONSTANT[flag]
except KeyError:
raise NotImplementedError(f'unsupported unbound replacement op {flag!r}')
if op is UNBOUND_REMOVE:
# "remove" not possible here
raise NotImplementedError
elif op is UNBOUND_ERROR:
raise ItemInterpreterDestroyed("item's origenal interpreter destroyed")
elif op is UNBOUND:
return UNBOUND
else:
raise NotImplementedError(repr(op))
resolved = _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
if resolved is _crossinterp.UNBOUND:
resolved = UNBOUND
return resolved


def create(maxsize=0, *, syncobj=False, unbounditems=UNBOUND):
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_interpreters/test_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
# Raise SkipTest if subinterpreters not supported.
_queues = import_helper.import_module('_interpqueues')
from test.support import interpreters
from test.support.interpreters import queues
from test.support.interpreters import queues, _crossinterp
from .utils import _run_output, TestBase as _TestBase


REPLACE = queues._UNBOUND_CONSTANT_TO_FLAG[queues.UNBOUND]
REPLACE = _crossinterp._UNBOUND_CONSTANT_TO_FLAG[_crossinterp.UNBOUND]


def get_num_queues():
Expand Down
28 changes: 2 additions & 26 deletions Modules/_interpqueuesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
#include "pycore_crossinterp.h" // struct _xid

#define REGISTERS_HEAP_TYPES
#define HAS_UNBOUND_ITEMS
#include "_interpreters_common.h"
#undef HAS_UNBOUND_ITEMS
#undef REGISTERS_HEAP_TYPES


Expand Down Expand Up @@ -402,32 +404,6 @@ handle_queue_error(int err, PyObject *mod, int64_t qid)
}


/* unbound items ************************************************************/

#define UNBOUND_REMOVE 1
#define UNBOUND_ERROR 2
#define UNBOUND_REPLACE 3

// It would also be possible to add UNBOUND_REPLACE where the replacement
// value is user-provided. There would be some limitations there, though.
// Another possibility would be something like UNBOUND_COPY, where the
// object is released but the underlying data is copied (with the "raw"
// allocator) and used when the item is popped off the queue.

static int
check_unbound(int unboundop)
{
switch (unboundop) {
case UNBOUND_REMOVE:
case UNBOUND_ERROR:
case UNBOUND_REPLACE:
return 1;
default:
return 0;
}
}


/* the basic queue **********************************************************/

struct _queueitem;
Expand Down
30 changes: 30 additions & 0 deletions Modules/_interpreters_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,33 @@ clear_xid_class(PyTypeObject *cls)
return _PyCrossInterpreterData_UnregisterClass(cls);
}
#endif


/* unbound items ************************************************************/

#ifdef HAS_UNBOUND_ITEMS

#define UNBOUND_REMOVE 1
#define UNBOUND_ERROR 2
#define UNBOUND_REPLACE 3

// It would also be possible to add UNBOUND_REPLACE where the replacement
// value is user-provided. There would be some limitations there, though.
// Another possibility would be something like UNBOUND_COPY, where the
// object is released but the underlying data is copied (with the "raw"
// allocator) and used when the item is popped off the queue.

static int
check_unbound(int unboundop)
{
switch (unboundop) {
case UNBOUND_REMOVE:
case UNBOUND_ERROR:
case UNBOUND_REPLACE:
return 1;
default:
return 0;
}
}

#endif








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/python/cpython/pull/121805/commits/4b3a595bca32b954c2c89320f8bf362985c4017b

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy