Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
Next Next commit
Identify unmanaged interpreters.
  • Loading branch information
ericsnowcurrently committed Apr 11, 2024
commit 5b68f4d9d7be36eec8e834d1889d1a7320d773fa
50 changes: 31 additions & 19 deletions Lib/test/support/interpreters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,51 +74,59 @@ def __str__(self):
def create():
"""Return a new (idle) Python interpreter."""
id = _interpreters.create(reqrefs=True)
return Interpreter(id)
return Interpreter(id, _owned=True)


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


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


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


_known = weakref.WeakValueDictionary()

class Interpreter:
"""A single Python interpreter."""
"""A single Python interpreter.

def __new__(cls, id, /):
Attributes:

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

def __new__(cls, id, /, _owned=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)
try:
self = _known[id]
assert hasattr(self, '_ownsref')
except KeyError:
# This may raise InterpreterNotFoundError:
_interpreters.incref(id)
try:
self = super().__new__(cls)
self._id = id
self._ownsref = True
except BaseException:
_interpreters.decref(id)
raise
self = super().__new__(cls)
_known[id] = self
self._id = id
self._owned = _owned
self._ownsref = _owned
if _owned:
# This may raise InterpreterNotFoundError:
_interpreters.incref(id)
return self

def __repr__(self):
Expand All @@ -143,14 +151,18 @@ def _decref(self):
return
self._ownsref = False
try:
_interpreters.decref(self.id)
_interpreters.decref(self._id)
except InterpreterNotFoundError:
pass

@property
def id(self):
return self._id

@property
def owned(self):
return self._owned

def is_running(self):
"""Return whether or not the identified interpreter is running."""
return _interpreters.is_running(self._id)
Expand Down
97 changes: 68 additions & 29 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,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)
print((interp.id, interp.owned))
""")
interpid = eval(text)
interpid, owned = eval(text)
self.assertEqual(interpid, expected)
self.assertFalse(owned)


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

Expand Down Expand Up @@ -298,6 +299,32 @@ def test_id_readonly(self):
with self.assertRaises(AttributeError):
interp.id = 1_000_000

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

with self.subTest('main'):
self.assertFalse(main.owned)

with self.subTest('from _interpreters'):
self.assertTrue(interp.owned)

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)
""")
owned = eval(text)
self.assertFalse(owned)

with self.subTest('readonly'):
for value in (True, False):
with self.assertRaises(AttributeError):
interp.owned = value
with self.assertRaises(AttributeError):
main.owned = value

def test_hashable(self):
interp = interpreters.create()
expected = hash(interp.id)
Expand Down Expand Up @@ -1115,7 +1142,7 @@ class LowLevelTests(TestBase):

def interp_exists(self, interpid):
try:
_interpreters.is_running(interpid)
_interpreters.is_owned(interpid)
except InterpreterNotFoundError:
return False
else:
Expand Down Expand Up @@ -1244,59 +1271,66 @@ def test_new_config(self):
_interpreters.new_config(gil=value)

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

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

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

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

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, = parse_stdout(text)
interpid, owned = parse_stdout(text)
self.assertEqual(interpid, expected)
self.assertFalse(owned)

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

with self.subTest('main'):
res = _interpreters.list_all()
self.assertEqual(res, expected)

with self.subTest('from _interpreters'):
with self.subTest('via interp from _interpreters'):
text = self.run_and_capture(interpid2, f"""
import {_interpreters.__name__} as _interpreters
print(
Expand All @@ -1306,15 +1340,15 @@ def test_list_all(self):
res = eval(text)
self.assertEqual(res, expected)

with self.subTest('from C-API'):
with self.subTest('via interp from C-API'):
interpid4 = interpid3 + 1
interpid5 = interpid4 + 1
expected2 = expected + [
(interpid4,),
(interpid5,),
(interpid4, False),
(interpid5, True),
]
expected3 = expected + [
(interpid5,),
(interpid5, True),
]
text = self.run_temp_from_capi(f"""
import {_interpreters.__name__} as _interpreters
Expand Down Expand Up @@ -1378,6 +1412,11 @@ def test_create(self):
with self.assertRaises(ValueError):
_interpreters.create(orig)

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

@requires_test_modules
def test_destroy(self):
with self.subTest('from _interpreters'):
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_interpreters/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,8 @@ 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:
yield interpreters.Interpreter(interpid), interpid
interp = interpreters.Interpreter(interpid, _owned=False)
yield interp, interpid

@contextlib.contextmanager
def capturing(self, script):
Expand Down
Loading