Content-Length: 599782 | pFad | http://github.com/python/cpython/pull/117490/commits/f73b6f329c042f4714149c4d93c3520d10d02478

0A gh-76785: Handle Legacy Interpreters Properly by ericsnowcurrently · Pull Request #117490 · python/cpython · GitHub
Skip to content

gh-76785: Handle Legacy Interpreters Properly #117490

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
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Interpreter.owned -> Interpreter.whence
  • Loading branch information
ericsnowcurrently committed Apr 11, 2024
commit f73b6f329c042f4714149c4d93c3520d10d02478
57 changes: 35 additions & 22 deletions Lib/test/support/interpreters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,26 @@ def __str__(self):
def create():
"""Return a new (idle) Python interpreter."""
id = _interpreters.create(reqrefs=True)
return Interpreter(id, _owned=True)
return Interpreter(id, _ownsref=True)


def list_all():
"""Return all existing interpreters."""
return [Interpreter(id, _owned=owned)
for id, owned in _interpreters.list_all()]
return [Interpreter(id, _whence=whence)
for id, whence in _interpreters.list_all()]


def get_current():
"""Return the currently running interpreter."""
id, owned = _interpreters.get_current()
return Interpreter(id, _owned=owned)
id, whence = _interpreters.get_current()
return Interpreter(id, _whence=whence)


def get_main():
"""Return the main interpreter."""
id, owned = _interpreters.get_main()
assert owned is False
return Interpreter(id, _owned=owned)
id, whence = _interpreters.get_main()
assert whence == _interpreters.WHENCE_RUNTIME, repr(whence)
return Interpreter(id, _whence=whence)


_known = weakref.WeakValueDictionary()
Expand All @@ -104,31 +104,45 @@ class Interpreter:
Attributes:

"id" - the unique process-global ID number for the interpreter
"owned" - indicates whether or not the interpreter was created
by interpreters.create()
"whence" - indicates where the interpreter was created

If interp.owned is false then any method that modifies the
interpreter will fail, i.e. .close(), .prepare_main(), .exec(),
and .call()
If the interpreter wasn't created by this module
then any method that modifies the interpreter will fail,
i.e. .close(), .prepare_main(), .exec(), and .call()
"""

def __new__(cls, id, /, _owned=None):
_WHENCE_TO_STR = {
_interpreters.WHENCE_UNKNOWN: 'unknown',
_interpreters.WHENCE_RUNTIME: 'runtime init',
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
_interpreters.WHENCE_CAPI: 'C-API',
_interpreters.WHENCE_XI: 'cross-interpreter C-API',
_interpreters.WHENCE_STDLIB: '_interpreters module',
}

def __new__(cls, id, /, _whence=None, _ownsref=None):
# There is only one instance for any given ID.
if not isinstance(id, int):
raise TypeError(f'id must be an int, got {id!r}')
id = int(id)
if _owned is None:
_owned = _interpreters.is_owned(id)
if _whence is None:
if _ownsref:
_whence = _interpreters.WHENCE_STDLIB
else:
_whence = _interpreters.whence(id)
assert _whence in cls._WHENCE_TO_STR, repr(_whence)
if _ownsref is None:
_ownsref = (_whence == _interpreters.WHENCE_STDLIB)
try:
self = _known[id]
assert hasattr(self, '_ownsref')
except KeyError:
self = super().__new__(cls)
_known[id] = self
self._id = id
self._owned = _owned
self._ownsref = _owned
if _owned:
self._whence = _whence
self._ownsref = _ownsref
if _ownsref:
# This may raise InterpreterNotFoundError:
_interpreters.incref(id)
return self
Expand Down Expand Up @@ -163,10 +177,9 @@ def _decref(self):
def id(self):
return self._id

# XXX Is this the right name?
@property
def owned(self):
return self._owned
def whence(self):
return self._WHENCE_TO_STR[self._whence]

def is_running(self):
"""Return whether or not the identified interpreter is running."""
Expand Down
112 changes: 62 additions & 50 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
)


WHENCE_STR_UNKNOWN = 'unknown'
WHENCE_STR_RUNTIME = 'runtime init'
WHENCE_STR_LEGACY_CAPI = 'legacy C-API'
WHENCE_STR_CAPI = 'C-API'
WHENCE_STR_XI = 'cross-interpreter C-API'
WHENCE_STR_STDLIB = '_interpreters module'


class ModuleTests(TestBase):

def test_queue_aliases(self):
Expand Down Expand Up @@ -172,11 +180,11 @@ def test_created_with_capi(self):
text = self.run_temp_from_capi(f"""
import {interpreters.__name__} as interpreters
interp = interpreters.get_current()
print((interp.id, interp.owned))
print((interp.id, interp.whence))
""")
interpid, owned = eval(text)
interpid, whence = eval(text)
self.assertEqual(interpid, expected)
self.assertFalse(owned)
self.assertEqual(whence, WHENCE_STR_CAPI)


class ListAllTests(TestBase):
Expand Down Expand Up @@ -228,22 +236,22 @@ def test_created_with_capi(self):
interpid4 = interpid3 + 1
interpid5 = interpid4 + 1
expected = [
(mainid, False),
(interpid1, True),
(interpid2, True),
(interpid3, True),
(interpid4, False),
(interpid5, True),
(mainid, WHENCE_STR_RUNTIME),
(interpid1, WHENCE_STR_STDLIB),
(interpid2, WHENCE_STR_STDLIB),
(interpid3, WHENCE_STR_STDLIB),
(interpid4, WHENCE_STR_CAPI),
(interpid5, WHENCE_STR_STDLIB),
]
expected2 = expected[:-2]
text = self.run_temp_from_capi(f"""
import {interpreters.__name__} as interpreters
interp = interpreters.create()
print(
[(i.id, i.owned) for i in interpreters.list_all()])
[(i.id, i.whence) for i in interpreters.list_all()])
""")
res = eval(text)
res2 = [(i.id, i.owned) for i in interpreters.list_all()]
res2 = [(i.id, i.whence) for i in interpreters.list_all()]
self.assertEqual(res, expected)
self.assertEqual(res2, expected2)

Expand Down Expand Up @@ -299,31 +307,37 @@ def test_id_readonly(self):
with self.assertRaises(AttributeError):
interp.id = 1_000_000

def test_owned(self):
def test_whence(self):
main = interpreters.get_main()
interp = interpreters.create()

with self.subTest('main'):
self.assertFalse(main.owned)
self.assertEqual(main.whence, WHENCE_STR_RUNTIME)

with self.subTest('from _interpreters'):
self.assertTrue(interp.owned)
self.assertEqual(interp.whence, WHENCE_STR_STDLIB)

with self.subTest('from C-API'):
text = self.run_temp_from_capi(f"""
import {interpreters.__name__} as interpreters
interp = interpreters.get_current()
print(interp.owned)
print(repr(interp.whence))
""")
owned = eval(text)
self.assertFalse(owned)
whence = eval(text)
self.assertEqual(whence, WHENCE_STR_CAPI)

with self.subTest('readonly'):
for value in (True, False):
for value in [
None,
WHENCE_STR_UNKNOWN,
WHENCE_STR_RUNTIME,
WHENCE_STR_STDLIB,
WHENCE_STR_CAPI,
]:
with self.assertRaises(AttributeError):
interp.owned = value
interp.whence = value
with self.assertRaises(AttributeError):
main.owned = value
main.whence = value

def test_hashable(self):
interp = interpreters.create()
Expand Down Expand Up @@ -612,7 +626,7 @@ def test_created_with_capi(self):
self.interp_exists(interpid))

# The rest would be skipped until we deal with running threads when
# interp.close() is called. However, the "owned" restrictions
# interp.close() is called. However, the "whence" restrictions
# trigger first.

with self.subTest('running, but not __main__ (from other)'):
Expand Down Expand Up @@ -1263,59 +1277,56 @@ def test_new_config(self):
_interpreters.new_config(gil=value)

def test_get_main(self):
interpid, owned = _interpreters.get_main()
interpid, whence = _interpreters.get_main()
self.assertEqual(interpid, 0)
self.assertFalse(owned)
self.assertFalse(
_interpreters.is_owned(interpid))
self.assertEqual(whence, _interpreters.WHENCE_RUNTIME)
self.assertEqual(
_interpreters.whence(interpid),
_interpreters.WHENCE_RUNTIME)

def test_get_current(self):
with self.subTest('main'):
main, *_ = _interpreters.get_main()
interpid, owned = _interpreters.get_current()
interpid, whence = _interpreters.get_current()
self.assertEqual(interpid, main)
self.assertFalse(owned)
self.assertEqual(whence, _interpreters.WHENCE_RUNTIME)

script = f"""
import {_interpreters.__name__} as _interpreters
interpid, owned = _interpreters.get_current()
print(interpid, owned)
interpid, whence = _interpreters.get_current()
print((interpid, whence))
"""
def parse_stdout(text):
parts = text.split()
assert len(parts) == 2, parts
interpid, owned = parts
interpid = int(interpid)
owned = eval(owned)
return interpid, owned
interpid, whence = eval(text)
return interpid, whence

with self.subTest('from _interpreters'):
orig = _interpreters.create()
text = self.run_and_capture(orig, script)
interpid, owned = parse_stdout(text)
interpid, whence = parse_stdout(text)
self.assertEqual(interpid, orig)
self.assertTrue(owned)
self.assertEqual(whence, _interpreters.WHENCE_STDLIB)

with self.subTest('from C-API'):
last = 0
for id, *_ in _interpreters.list_all():
last = max(last, id)
expected = last + 1
text = self.run_temp_from_capi(script)
interpid, owned = parse_stdout(text)
interpid, whence = parse_stdout(text)
self.assertEqual(interpid, expected)
self.assertFalse(owned)
self.assertEqual(whence, _interpreters.WHENCE_CAPI)

def test_list_all(self):
mainid, *_ = _interpreters.get_main()
interpid1 = _interpreters.create()
interpid2 = _interpreters.create()
interpid3 = _interpreters.create()
expected = [
(mainid, False),
(interpid1, True),
(interpid2, True),
(interpid3, True),
(mainid, _interpreters.WHENCE_RUNTIME),
(interpid1, _interpreters.WHENCE_STDLIB),
(interpid2, _interpreters.WHENCE_STDLIB),
(interpid3, _interpreters.WHENCE_STDLIB),
]

with self.subTest('main'):
Expand All @@ -1336,11 +1347,11 @@ def test_list_all(self):
interpid4 = interpid3 + 1
interpid5 = interpid4 + 1
expected2 = expected + [
(interpid4, False),
(interpid5, True),
(interpid4, _interpreters.WHENCE_CAPI),
(interpid5, _interpreters.WHENCE_STDLIB),
]
expected3 = expected + [
(interpid5, True),
(interpid5, _interpreters.WHENCE_STDLIB),
]
text = self.run_temp_from_capi(f"""
import {_interpreters.__name__} as _interpreters
Expand Down Expand Up @@ -1404,10 +1415,11 @@ def test_create(self):
with self.assertRaises(ValueError):
_interpreters.create(orig)

with self.subTest('is owned'):
with self.subTest('whence'):
interpid = _interpreters.create()
self.assertTrue(
_interpreters.is_owned(interpid))
self.assertEqual(
_interpreters.whence(interpid),
_interpreters.WHENCE_STDLIB)

@requires_test_modules
def test_destroy(self):
Expand Down Expand Up @@ -1479,7 +1491,7 @@ def test_whence(self):
with self.subTest('stdlib'):
interpid = _interpreters.create()
whence = _interpreters.whence(interpid)
self.assertEqual(whence, _interpreters.WHENCE_XI)
self.assertEqual(whence, _interpreters.WHENCE_STDLIB)

for orig, name in {
# XXX Also check WHENCE_UNKNOWN.
Expand Down
8 changes: 6 additions & 2 deletions Lib/test/test_interpreters/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def run_and_capture(self, interp, script):

def interp_exists(self, interpid):
try:
_interpreters.is_owned(interpid)
_interpreters.whence(interpid)
except _interpreters.InterpreterNotFoundError:
return False
else:
Expand Down Expand Up @@ -553,7 +553,11 @@ def interpreter_from_capi(self, config=None, whence=None):
@contextlib.contextmanager
def interpreter_obj_from_capi(self, config='legacy'):
with self.interpreter_from_capi(config) as interpid:
interp = interpreters.Interpreter(interpid, _owned=False)
interp = interpreters.Interpreter(
interpid,
_whence=_interpreters.WHENCE_CAPI,
_ownsref=False,
)
yield interp, interpid

@contextlib.contextmanager
Expand Down
Loading








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/117490/commits/f73b6f329c042f4714149c4d93c3520d10d02478

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy