From 02086f767dd1f5cafb9d9de12b5f17023ea8e0e9 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 18:51:49 -0300 Subject: [PATCH 01/13] PEP 554 for use in test suite --- Doc/library/_interpreters.rst | 147 ++++++++ Lib/test/support/_interpreters.py | 235 ++++++++++++ Lib/test/test_interpreters.py | 569 +++++++++++++++++++++++++++++ Modules/_xxsubinterpretersmodule.c | 23 ++ 4 files changed, 974 insertions(+) create mode 100644 Doc/library/_interpreters.rst create mode 100644 Lib/test/support/_interpreters.py create mode 100644 Lib/test/test_interpreters.py diff --git a/Doc/library/_interpreters.rst b/Doc/library/_interpreters.rst new file mode 100644 index 00000000000000..f1840450799830 --- /dev/null +++ b/Doc/library/_interpreters.rst @@ -0,0 +1,147 @@ +:mod:`_interpreters` --- High-level Subinterpreters Module +========================================================== + +.. module:: _interpreters + :synopsis: High-level Subinterpreters Module. + +**Source code:** :source:`Lib/test/support/_interpreters.py` + +-------------- + +This module provides high-level tools for working with sub-interpreters, +such as creating them, running code in them, or sending data between them. +It is a wrapper around the low-level `_xxsubinterpreters` module. + +.. versionchanged:: added in 3.9 + +Interpreter Objects +------------------- + +The Interpreter object represents a single interpreter. +.. class:: Interpreter(id) + + The class implementing a subinterpreter object. + + .. method:: is_running() + + Return `True` if the identified interpreter is running. + + .. method:: close() + + Destroy the interpreter. Attempting to destroy the current + interpreter results in a `RuntimeError`. + + .. method:: run(self, src_str, /, *, channels=None): + + Run the given source code in the interpreter. This blocks + the current thread until done. `channels` should be in + the form : `(RecvChannel, SendChannel)`. + +RecvChannel Objects +------------------- + +The RecvChannel object represents a recieving channel. + +.. class:: RecvChannel(id) + + This class represents the receiving end of a channel. + + .. method:: recv() + + Get the next object from the channel, and wait if + none have been sent. Associate the interpreter + with the channel. + + .. method:: recv_nowait(default=None) + + Like ``recv()``, but return the default result + instead of waiting. + + +SendChannel Objects +-------------------- + +The ``SendChannel`` object represents a sending channel. + +.. class:: SendChannel(id) + + This class represents the sending end of a channel. + + .. method:: send(obj) + + Send the object ``obj`` to the receiving end of the channel + and wait. Associate the interpreter with the channel. + + .. method:: send_nowait(obj) + + Similar to ``send()``, but returns ``False`` if + *obj* is not immediately received instead of blocking. + + +This module defines the following global functions: + + +.. function:: is_shareable(obj) + + Return ``True`` if the object's data can be shared between + interpreters. + +.. function:: create_channel() + + Create a new channel for passing data between interpreters. + +.. function:: list_all_channels() + + Return all open channels. + +.. function:: create(*, isolated=True) + + Initialize a new (idle) Python interpreter. Get the currently + running interpreter. This method returns an ``Interpreter`` object. + +.. function:: get_current() + + Get the currently running interpreter. This method returns + an ``Interpreter`` object. + +.. function:: get_main() + + Get the main interpreter. This method returns + an ``Interpreter`` object. + +.. function:: list_all() + + Get all existing interpreters. Returns a list + of ``Interpreter`` objects. + +This module also defines the following exceptions. + +.. exception:: RunFailedError + + This exception, a subclass of :exc:`RuntimeError`, is raised when the + ``Interpreter.run()`` results in an uncaught exception. + +.. exception:: ChannelError + + This exception is a subclass of :exc:`Exception`, and is the base + class for all channel-related exceptions. + +.. exception:: ChannelNotFoundError + + This exception is a subclass of :exc:`ChannelError`, and is raised + when the the identified channel is not found. + +.. exception:: ChannelEmptyError + + This exception is a subclass of :exc:`ChannelError`, and is raised when + the channel is unexpectedly empty. + +.. exception:: ChannelNotEmptyError + + This exception is a subclass of :exc:`ChannelError`, and is raised when + the channel is unexpectedly not empty. + +.. exception:: NotReceivedError + + This exception is a subclass of :exc:`ChannelError`, and is raised when + nothing was waiting to receive a sent object. diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py new file mode 100644 index 00000000000000..865996fb7405d3 --- /dev/null +++ b/Lib/test/support/_interpreters.py @@ -0,0 +1,235 @@ +"""Subinterpreters High Level Module.""" + +import logging +import _xxsubinterpreters as _interpreters + +__all__ = ['Interpreter', 'SendChannel', 'RecvChannel', + 'is_shareable', 'create_channel', + 'list_all_channels', 'get_current', + 'get_current', 'create'] + + +def create(*, isolated=True): + """ create() -> Interpreter + + Initialize a new (idle) Python interpreter. + """ + id = _interpreters.create() + return Interpreter(id) + +def list_all(): + """ list_all() -> [Interpreter] + + Get all existing interpreters. + """ + return [Interpreter(id) for id in + _interpreters.list_all()] + +def get_current(): + """ get_current() -> Interpreter + + Get the currently running interpreter. + """ + id = _interpreters.get_current() + return Interpreter(id) + +def get_main(): + """ get_main() -> Interpreter + + Get the main interpreter. + """ + id = _interpreters.get_main() + return Interpreter(id) + + +class Interpreter: + + def __init__(self, id): + self._id = id + + @property + def id(self): + return self._id + + def is_running(self): + """is_running() -> bool + + Return whether or not the identified + interpreter is running. + """ + return _interpreters.is_running(self._id) + + def close(self): + """close() + + Finalize and destroy the interpreter. + + Attempting to destroy the current + interpreter results in a RuntimeError. + """ + return _interpreters.destroy(self._id) + + def run(self, src_str, /, *, channels=None): + """run(src_str, /, *, channels=None) + + Run the given source code in the interpreter. + This blocks the current thread until done. + """ + try: + _interpreters.run_string(self._id, src_str) + except RunFailedError as err: + logger.error(err) + raise + + +def is_shareable(obj): + """ is_shareable(obj) -> Bool + + Return `True` if the object's data can be + shared between interpreters and `False` otherwise. + """ + return _interpreters.is_shareable(obj) + +def create_channel(): + """ create_channel() -> (RecvChannel, SendChannel) + + Create a new channel for passing data between + interpreters. + """ + + cid = _interpreters.channel_create() + return (RecvChannel(cid), SendChannel(cid)) + +def list_all_channels(): + """ list_all_channels() -> [(RecvChannel, SendChannel)] + + Get all open channels. + """ + return [(RecvChannel(cid), SendChannel(cid)) + for cid in _interpreters.channel_list_all()] + +def wait(timeout): + #The implementation for wait + # will be non trivial to be useful + import time + time.sleep(timeout) + +class RecvChannel: + + def __init__(self, id): + self.id = id + + def recv(self): + """ channel_recv() -> obj + + Get the next object from the channel, + and wait if none have been sent. + Associate the interpreter with the channel. + """ + try: + obj = _interpreters.channel_recv(self.id) + if obj == None: + wait(2) + obj = _interpreters.channel_recv(self.id) + except _interpreters.ChannelEmptyError: + raise ChannelEmptyError + except _interpreters.ChannelNotFoundError: + raise ChannelNotFoundError + except _interpreters.ChannelClosedError: + raise ChannelClosedError + except _interpreters.RunFailedError: + raise RunFailedError + return obj + + def recv_nowait(self, default=None): + """recv_nowait(default=None) -> object + + Like recv(), but return the default + instead of waiting. + """ + try: + obj = _interpreters.channel_recv(self.id) + except _interpreters.ChannelEmptyError: + raise ChannelEmptyError + except _interpreters.ChannelNotFoundError: + raise ChannelNotFoundError + except _interpreters.ChannelClosedError: + raise ChannelClosedError + except _interpreters.RunFailedError: + raise RunFailedError + return obj + + +class SendChannel: + + def __init__(self, id): + self.id = id + + def send(self, obj): + """ send(obj) + + Send the object (i.e. its data) to the receiving + end of the channel and wait. Associate the interpreter + with the channel. + """ + try: + _interpreters.channel_send(self.id, obj) + wait(2) + except _interpreters.ChannelNotFoundError: + raise ChannelNotFoundError + except _interpreters.ChannelClosedError: + raise ChannelClosedError + except _interpreters.RunFailedError: + raise RunFailedError + + def send_nowait(self, obj): + """ send_nowait(obj) + + Like send(), but return False if not received. + """ + try: + _interpreters.channel_send(self.id, obj) + except _interpreters.ChannelNotFoundError: + raise ChannelNotFoundError + except _interpreters.ChannelClosedError: + raise ChannelClosedError + except _interpreters.RunFailedError: + raise RunFailedError + + recv_obj = _interpreters.channel_recv(self.id) + if recv_obj: + return obj + else: + return False + + +class ChannelError(Exception): + pass + + +class ChannelNotFoundError(ChannelError): + pass + + +class ChannelEmptyError(ChannelError): + pass + + +class ChannelNotEmptyError(ChannelError): + pass + + +class NotReceivedError(ChannelError): + pass + + +class ChannelClosedError(ChannelError): + pass + + +class ChannelReleasedError(ChannelClosedError): + pass + + +class RunFailedError(RuntimeError): + pass \ No newline at end of file diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py new file mode 100644 index 00000000000000..db658202659bbf --- /dev/null +++ b/Lib/test/test_interpreters.py @@ -0,0 +1,569 @@ +import contextlib +import os +import threading +from textwrap import dedent +import unittest +import time + +import _xxsubinterpreters as _interpreters +from test.support import _interpreters as interpreters + +def _captured_script(script): + r, w = os.pipe() + indented = script.replace('\n', '\n ') + wrapped = dedent(f""" + import contextlib + with open({w}, 'w') as spipe: + with contextlib.redirect_stdout(spipe): + {indented} + """) + return wrapped, open(r) + +def clean_up_interpreters(): + for interp in interpreters.list_all(): + if interp.id == 0: # main + continue + try: + interp.close() + except RuntimeError: + pass # already destroyed + +def _run_output(interp, request, shared=None): + script, rpipe = _captured_script(request) + with rpipe: + interp.run(script) + return rpipe.read() + +@contextlib.contextmanager +def _running(interp): + r, w = os.pipe() + def run(): + interp.run(dedent(f""" + # wait for "signal" + with open({r}) as rpipe: + rpipe.read() + """)) + + t = threading.Thread(target=run) + t.start() + + yield + + with open(w, 'w') as spipe: + spipe.write('done') + t.join() + + +class TestBase(unittest.TestCase): + + def tearDown(self): + clean_up_interpreters() + + +class CreateTests(TestBase): + + def test_in_main(self): + interp = interpreters.create() + lst = interpreters.list_all() + self.assertEqual(interp.id, lst[1].id) + + def test_in_thread(self): + lock = threading.Lock() + id = None + interp = interpreters.create() + lst = interpreters.list_all() + def f(): + nonlocal id + id = interp.id + lock.acquire() + lock.release() + + t = threading.Thread(target=f) + with lock: + t.start() + t.join() + self.assertEqual(interp.id, lst[1].id) + + def test_in_subinterpreter(self): + main, = interpreters.list_all() + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import _interpreters as interpreters + interp = interpreters.create() + print(interp) + """)) + interp2 = out.strip() + + self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2})) + + def test_in_threaded_subinterpreter(self): + main, = interpreters.list_all() + interp = interpreters.create() + interp2 = None + def f(): + nonlocal interp2 + out = _run_output(interp, dedent(""" + from test.support import _interpreters as interpreters + interp = interpreters.create() + print(interp) + """)) + interp2 = int(out.strip()) + + t = threading.Thread(target=f) + t.start() + t.join() + + self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2})) + + def test_after_destroy_all(self): + before = set(interpreters.list_all()) + # Create 3 subinterpreters. + interp_lst = [] + for _ in range(3): + interps = interpreters.create() + interp_lst.append(interps) + # Now destroy them. + for interp in interp_lst: + interp.close() + # Finally, create another. + interp = interpreters.create() + self.assertEqual(len(set(interpreters.list_all())), len(before | {interp})) + + def test_after_destroy_some(self): + before = set(interpreters.list_all()) + # Create 3 subinterpreters. + interp1 = interpreters.create() + interp2 = interpreters.create() + interp3 = interpreters.create() + # Now destroy 2 of them. + interp1.close() + interp2.close() + # Finally, create another. + interp = interpreters.create() + self.assertEqual(len(set(interpreters.list_all())), len(before | {interp3, interp})) + + +class GetCurrentTests(TestBase): + + def test_main(self): + main_interp_id = _interpreters.get_main() + cur_interp_id = interpreters.get_current().id + self.assertEqual(cur_interp_id, main_interp_id) + + def test_subinterpreter(self): + main = _interpreters.get_main() + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import _interpreters as interpreters + cur = interpreters.get_current() + print(cur) + """)) + cur = out.strip() + self.assertNotEqual(cur, main) + + +class ListAllTests(TestBase): + + def test_initial(self): + interps = interpreters.list_all() + self.assertEqual(1, len(interps)) + + def test_after_creating(self): + main = interpreters.get_current() + first = interpreters.create() + second = interpreters.create() + + ids = [] + for interp in interpreters.list_all(): + ids.append(interp.id) + + self.assertEqual(ids, [main.id, first.id, second.id]) + + def test_after_destroying(self): + main = interpreters.get_current() + first = interpreters.create() + second = interpreters.create() + first.close() + + ids = [] + for interp in interpreters.list_all(): + ids.append(interp.id) + + self.assertEqual(ids, [main.id, second.id]) + + +class TestInterpreterId(TestBase): + + def test_in_main(self): + main = interpreters.get_current() + self.assertEqual(0, main.id) + + def test_with_custom_num(self): + interp = interpreters.Interpreter(1) + self.assertEqual(1, interp.id) + + def test_for_readonly_property(self): + interp = interpreters.Interpreter(1) + with self.assertRaises(AttributeError): + interp.id = 2 + + +class TestInterpreterIsRunning(TestBase): + + def test_main(self): + main = interpreters.get_current() + self.assertTrue(main.is_running()) + + def test_subinterpreter(self): + interp = interpreters.create() + self.assertFalse(interp.is_running()) + + with _running(interp): + self.assertTrue(interp.is_running()) + self.assertFalse(interp.is_running()) + + def test_from_subinterpreter(self): + interp = interpreters.create() + out = _run_output(interp, dedent(f""" + import _xxsubinterpreters as _interpreters + if _interpreters.is_running({interp.id}): + print(True) + else: + print(False) + """)) + self.assertEqual(out.strip(), 'True') + + def test_already_destroyed(self): + interp = interpreters.create() + interp.close() + with self.assertRaises(RuntimeError): + interp.is_running() + + +class TestInterpreterDestroy(TestBase): + + def test_basic(self): + interp1 = interpreters.create() + interp2 = interpreters.create() + interp3 = interpreters.create() + self.assertEqual(4, len(interpreters.list_all())) + interp2.close() + self.assertEqual(3, len(interpreters.list_all())) + + def test_all(self): + before = set(interpreters.list_all()) + interps = set() + for _ in range(3): + interp = interpreters.create() + interps.add(interp) + self.assertEqual(len(set(interpreters.list_all())), len(before | interps)) + for interp in interps: + interp.close() + self.assertEqual(len(set(interpreters.list_all())), len(before)) + + def test_main(self): + main, = interpreters.list_all() + with self.assertRaises(RuntimeError): + main.close() + + def f(): + with self.assertRaises(RuntimeError): + main.close() + + t = threading.Thread(target=f) + t.start() + t.join() + + def test_already_destroyed(self): + interp = interpreters.create() + interp.close() + with self.assertRaises(RuntimeError): + interp.close() + + def test_from_current(self): + main, = interpreters.list_all() + interp = interpreters.create() + script = dedent(f""" + from test.support import _interpreters as interpreters + try: + main = interpreters.get_current() + main.close() + except RuntimeError: + pass + """) + + interp.run(script) + self.assertEqual(len(set(interpreters.list_all())), len({main, interp})) + + def test_from_sibling(self): + main, = interpreters.list_all() + interp1 = interpreters.create() + script = dedent(f""" + from test.support import _interpreters as interpreters + interp2 = interpreters.create() + interp2.close() + """) + interp1.run(script) + + self.assertEqual(len(set(interpreters.list_all())), len({main, interp1})) + + def test_from_other_thread(self): + interp = interpreters.create() + def f(): + interp.close() + + t = threading.Thread(target=f) + t.start() + t.join() + + def test_still_running(self): + main, = interpreters.list_all() + interp = interpreters.create() + with _running(interp): + with self.assertRaises(RuntimeError): + interp.close() + self.assertTrue(interp.is_running()) + + +class TestInterpreterRun(TestBase): + + SCRIPT = dedent(""" + with open('{}', 'w') as out: + out.write('{}') + """) + FILENAME = 'spam' + + def setUp(self): + super().setUp() + self.interp = interpreters.create() + self._fs = None + + def tearDown(self): + if self._fs is not None: + self._fs.close() + super().tearDown() + + @property + def fs(self): + if self._fs is None: + self._fs = FSFixture(self) + return self._fs + + def test_success(self): + script, file = _captured_script('print("it worked!", end="")') + with file: + self.interp.run(script) + out = file.read() + + self.assertEqual(out, 'it worked!') + + def test_in_thread(self): + script, file = _captured_script('print("it worked!", end="")') + with file: + def f(): + self.interp.run(script) + + t = threading.Thread(target=f) + t.start() + t.join() + out = file.read() + + self.assertEqual(out, 'it worked!') + + @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()") + def test_fork(self): + import tempfile + with tempfile.NamedTemporaryFile('w+') as file: + file.write('') + file.flush() + + expected = 'spam spam spam spam spam' + script = dedent(f""" + import os + try: + os.fork() + except RuntimeError: + with open('{file.name}', 'w') as out: + out.write('{expected}') + """) + self.interp.run(script) + + file.seek(0) + content = file.read() + self.assertEqual(content, expected) + + def test_already_running(self): + with _running(self.interp): + with self.assertRaises(RuntimeError): + self.interp.run('print("spam")') + + def test_bad_script(self): + with self.assertRaises(TypeError): + self.interp.run(10) + + def test_bytes_for_script(self): + with self.assertRaises(TypeError): + self.interp.run(b'print("spam")') + + +class TestIsShareable(TestBase): + + def test_default_shareables(self): + shareables = [ + # singletons + None, + # builtin objects + b'spam', + 'spam', + 10, + -10, + ] + for obj in shareables: + with self.subTest(obj): + self.assertTrue( + interpreters.is_shareable(obj)) + + def test_not_shareable(self): + class Cheese: + def __init__(self, name): + self.name = name + def __str__(self): + return self.name + + class SubBytes(bytes): + """A subclass of a shareable type.""" + + not_shareables = [ + # singletons + True, + False, + NotImplemented, + ..., + # builtin types and objects + type, + object, + object(), + Exception(), + 100.0, + # user-defined types and objects + Cheese, + Cheese('Wensleydale'), + SubBytes(b'spam'), + ] + for obj in not_shareables: + with self.subTest(repr(obj)): + self.assertFalse( + interpreters.is_shareable(obj)) + + +class TestChannel(TestBase): + + def test_create_cid(self): + r, s = interpreters.create_channel() + self.assertIsInstance(r, interpreters.RecvChannel) + self.assertIsInstance(s, interpreters.SendChannel) + + def test_sequential_ids(self): + before = interpreters.list_all_channels() + channels1 = interpreters.create_channel() + channels2 = interpreters.create_channel() + channels3 = interpreters.create_channel() + after = interpreters.list_all_channels() + + self.assertEqual(len(set(after) - set(before)), + len({channels1, channels2, channels3})) + +class TestSendRecv(TestBase): + + def test_send_recv_main(self): + r, s = interpreters.create_channel() + orig = b'spam' + s.send(orig) + obj = r.recv() + + self.assertEqual(obj, orig) + self.assertIsNot(obj, orig) + + def test_send_recv_same_interpreter(self): + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import _interpreters as interpreters + r, s = interpreters.create_channel() + orig = b'spam' + s.send(orig) + obj = r.recv() + assert obj is not orig + assert obj == orig + """)) + + def test_send_recv_different_threads(self): + r, s = interpreters.create_channel() + + def f(): + while True: + try: + obj = r.recv() + break + except interpreters.ChannelEmptyError: + time.sleep(0.1) + s.send(obj) + t = threading.Thread(target=f) + t.start() + + s.send(b'spam') + t.join() + obj = r.recv() + + self.assertEqual(obj, b'spam') + + def test_recv_empty(self): + r, s = interpreters.create_channel() + with self.assertRaises(interpreters.ChannelEmptyError): + r.recv() + + def test_send_recv_nowait_main(self): + r, s = interpreters.create_channel() + orig = b'spam' + s.send(orig) + obj = r.recv_nowait() + + self.assertEqual(obj, orig) + self.assertIsNot(obj, orig) + + def test_send_recv_nowait_same_interpreter(self): + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import _interpreters as interpreters + r, s = interpreters.create_channel() + orig = b'spam' + s.send(orig) + obj = r.recv_nowait() + assert obj is not orig + assert obj == orig + """)) + + def test_send_recv_nowait_different_threads(self): + r, s = interpreters.create_channel() + + def f(): + while True: + try: + obj = r.recv_nowait() + break + except interpreters.ChannelEmptyError: + time.sleep(0.1) + s.send(obj) + t = threading.Thread(target=f) + t.start() + + s.send(b'spam') + t.join() + obj = r.recv_nowait() + + self.assertEqual(obj, b'spam') + + def test_recv_nowait_empty(self): + r, s = interpreters.create_channel() + with self.assertRaises(interpreters.ChannelEmptyError): + r.recv_nowait() + \ No newline at end of file diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 9c5df16e156a1d..74a665eba8b741 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -1167,6 +1167,8 @@ static PyObject *ChannelNotFoundError; static PyObject *ChannelClosedError; static PyObject *ChannelEmptyError; static PyObject *ChannelNotEmptyError; +static PyObject *ChannelReleasedError; +static PyObject *NotReceivedError; static int channel_exceptions_init(PyObject *ns) @@ -1203,6 +1205,27 @@ channel_exceptions_init(PyObject *ns) return -1; } + // An operation tried to use a released channel. + ChannelReleasedError = PyErr_NewException( + "_interpreters.ChannelReleasedError", ChannelClosedError, NULL); + if (ChannelReleasedError == NULL) { + return -1; + } + if (PyDict_SetItemString(ns, "ChannelReleasedError", ChannelReleasedError) != 0) { + return -1; + } + + // An operation trying to send an object when Nothing was waiting + // to receive it + NotReceivedError = PyErr_NewException( + "_interpreters.NotReceivedError", ChannelError, NULL); + if (NotReceivedError == NULL) { + return -1; + } + if (PyDict_SetItemString(ns, "NotReceivedError", NotReceivedError) != 0) { + return -1; + } + // An operation tried to pop from an empty channel. ChannelEmptyError = PyErr_NewException( "_xxsubinterpreters.ChannelEmptyError", ChannelError, NULL); From b2fda3c22b0970733b3064eaadf43e0f78c81508 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 7 May 2020 22:00:17 +0000 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst diff --git a/Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst b/Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst new file mode 100644 index 00000000000000..1129cd7649b96a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst @@ -0,0 +1,2 @@ +PEP 554 for use in the test suite. +(Patch By Joannah Nanjekye) \ No newline at end of file From 36676262a370a9b836fd814d7bf89b7cddd2bb09 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 19:09:20 -0300 Subject: [PATCH 03/13] Fix space --- Lib/test/support/_interpreters.py | 2 +- Lib/test/test_interpreters.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py index 865996fb7405d3..8fbf7e484baaf1 100644 --- a/Lib/test/support/_interpreters.py +++ b/Lib/test/support/_interpreters.py @@ -232,4 +232,4 @@ class ChannelReleasedError(ChannelClosedError): class RunFailedError(RuntimeError): - pass \ No newline at end of file + pass diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py index db658202659bbf..663b4bbaab1a88 100644 --- a/Lib/test/test_interpreters.py +++ b/Lib/test/test_interpreters.py @@ -566,4 +566,3 @@ def test_recv_nowait_empty(self): r, s = interpreters.create_channel() with self.assertRaises(interpreters.ChannelEmptyError): r.recv_nowait() - \ No newline at end of file From cb594ddfdfed939392b41a0c1de3917234fdb437 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 19:31:36 -0300 Subject: [PATCH 04/13] Add doc to doc tree --- Doc/library/python.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/python.rst b/Doc/library/python.rst index f39613f572884f..6aea467dd3d2ec 100644 --- a/Doc/library/python.rst +++ b/Doc/library/python.rst @@ -25,3 +25,4 @@ overview: gc.rst inspect.rst site.rst + _interpreters.rst From 0448a8a71f9c6a7eda1ed06e8ed89bc18923bd9a Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 19:40:20 -0300 Subject: [PATCH 05/13] Move to modules doc tree --- Doc/library/index.rst | 1 + Doc/library/python.rst | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/index.rst b/Doc/library/index.rst index bebf7429b0e63e..d724238be7893c 100644 --- a/Doc/library/index.rst +++ b/Doc/library/index.rst @@ -77,3 +77,4 @@ the `Python Package Index `_. unix.rst superseded.rst undoc.rst + _interpreters.rst diff --git a/Doc/library/python.rst b/Doc/library/python.rst index 6aea467dd3d2ec..f39613f572884f 100644 --- a/Doc/library/python.rst +++ b/Doc/library/python.rst @@ -25,4 +25,3 @@ overview: gc.rst inspect.rst site.rst - _interpreters.rst From 85fde9f160acedba39a54ac059f41d05a7ca3a92 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 20:04:35 -0300 Subject: [PATCH 06/13] Fix suspicious doc errors --- Doc/library/_interpreters.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/_interpreters.rst b/Doc/library/_interpreters.rst index f1840450799830..cc17fbe9292122 100644 --- a/Doc/library/_interpreters.rst +++ b/Doc/library/_interpreters.rst @@ -18,6 +18,7 @@ Interpreter Objects ------------------- The Interpreter object represents a single interpreter. + .. class:: Interpreter(id) The class implementing a subinterpreter object. From f6af61c7ac8af86c934689c4c805acde89190974 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 20:23:15 -0300 Subject: [PATCH 07/13] Fix test__all --- Lib/test/support/_interpreters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py index 8fbf7e484baaf1..93780ec08da94a 100644 --- a/Lib/test/support/_interpreters.py +++ b/Lib/test/support/_interpreters.py @@ -6,7 +6,7 @@ __all__ = ['Interpreter', 'SendChannel', 'RecvChannel', 'is_shareable', 'create_channel', 'list_all_channels', 'get_current', - 'get_current', 'create'] + 'get_main', 'create'] def create(*, isolated=True): From 0decbeefeabef7f9167052133c89d6e6dc4a2ef4 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 7 May 2020 20:48:37 -0300 Subject: [PATCH 08/13] Docs docs docs --- Doc/library/_interpreters.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/_interpreters.rst b/Doc/library/_interpreters.rst index cc17fbe9292122..771030f0301300 100644 --- a/Doc/library/_interpreters.rst +++ b/Doc/library/_interpreters.rst @@ -10,7 +10,7 @@ This module provides high-level tools for working with sub-interpreters, such as creating them, running code in them, or sending data between them. -It is a wrapper around the low-level `_xxsubinterpreters` module. +It is a wrapper around the low-level ``__xxsubinterpreters`` module. .. versionchanged:: added in 3.9 @@ -25,7 +25,7 @@ The Interpreter object represents a single interpreter. .. method:: is_running() - Return `True` if the identified interpreter is running. + Return ``True`` if the identified interpreter is running. .. method:: close() @@ -35,7 +35,7 @@ The Interpreter object represents a single interpreter. .. method:: run(self, src_str, /, *, channels=None): Run the given source code in the interpreter. This blocks - the current thread until done. `channels` should be in + the current thread until done. ``channels`` should be in the form : `(RecvChannel, SendChannel)`. RecvChannel Objects From 3b35e1b08adfcc9a656009ed2e771241fb476ded Mon Sep 17 00:00:00 2001 From: Joannah Nanjekye Date: Fri, 15 May 2020 18:04:17 +0000 Subject: [PATCH 09/13] Support isolated and fix wait --- Doc/library/index.rst | 1 - Lib/test/support/_interpreters.py | 235 ------------------ Lib/test/support/interpreters.py | 176 +++++++++++++ .../test/support/interpreters.rst | 11 +- Lib/test/test_interpreters.py | 45 +--- 5 files changed, 191 insertions(+), 277 deletions(-) delete mode 100644 Lib/test/support/_interpreters.py create mode 100644 Lib/test/support/interpreters.py rename Doc/library/_interpreters.rst => Lib/test/support/interpreters.rst (92%) diff --git a/Doc/library/index.rst b/Doc/library/index.rst index d724238be7893c..bebf7429b0e63e 100644 --- a/Doc/library/index.rst +++ b/Doc/library/index.rst @@ -77,4 +77,3 @@ the `Python Package Index `_. unix.rst superseded.rst undoc.rst - _interpreters.rst diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py deleted file mode 100644 index 93780ec08da94a..00000000000000 --- a/Lib/test/support/_interpreters.py +++ /dev/null @@ -1,235 +0,0 @@ -"""Subinterpreters High Level Module.""" - -import logging -import _xxsubinterpreters as _interpreters - -__all__ = ['Interpreter', 'SendChannel', 'RecvChannel', - 'is_shareable', 'create_channel', - 'list_all_channels', 'get_current', - 'get_main', 'create'] - - -def create(*, isolated=True): - """ create() -> Interpreter - - Initialize a new (idle) Python interpreter. - """ - id = _interpreters.create() - return Interpreter(id) - -def list_all(): - """ list_all() -> [Interpreter] - - Get all existing interpreters. - """ - return [Interpreter(id) for id in - _interpreters.list_all()] - -def get_current(): - """ get_current() -> Interpreter - - Get the currently running interpreter. - """ - id = _interpreters.get_current() - return Interpreter(id) - -def get_main(): - """ get_main() -> Interpreter - - Get the main interpreter. - """ - id = _interpreters.get_main() - return Interpreter(id) - - -class Interpreter: - - def __init__(self, id): - self._id = id - - @property - def id(self): - return self._id - - def is_running(self): - """is_running() -> bool - - Return whether or not the identified - interpreter is running. - """ - return _interpreters.is_running(self._id) - - def close(self): - """close() - - Finalize and destroy the interpreter. - - Attempting to destroy the current - interpreter results in a RuntimeError. - """ - return _interpreters.destroy(self._id) - - def run(self, src_str, /, *, channels=None): - """run(src_str, /, *, channels=None) - - Run the given source code in the interpreter. - This blocks the current thread until done. - """ - try: - _interpreters.run_string(self._id, src_str) - except RunFailedError as err: - logger.error(err) - raise - - -def is_shareable(obj): - """ is_shareable(obj) -> Bool - - Return `True` if the object's data can be - shared between interpreters and `False` otherwise. - """ - return _interpreters.is_shareable(obj) - -def create_channel(): - """ create_channel() -> (RecvChannel, SendChannel) - - Create a new channel for passing data between - interpreters. - """ - - cid = _interpreters.channel_create() - return (RecvChannel(cid), SendChannel(cid)) - -def list_all_channels(): - """ list_all_channels() -> [(RecvChannel, SendChannel)] - - Get all open channels. - """ - return [(RecvChannel(cid), SendChannel(cid)) - for cid in _interpreters.channel_list_all()] - -def wait(timeout): - #The implementation for wait - # will be non trivial to be useful - import time - time.sleep(timeout) - -class RecvChannel: - - def __init__(self, id): - self.id = id - - def recv(self): - """ channel_recv() -> obj - - Get the next object from the channel, - and wait if none have been sent. - Associate the interpreter with the channel. - """ - try: - obj = _interpreters.channel_recv(self.id) - if obj == None: - wait(2) - obj = _interpreters.channel_recv(self.id) - except _interpreters.ChannelEmptyError: - raise ChannelEmptyError - except _interpreters.ChannelNotFoundError: - raise ChannelNotFoundError - except _interpreters.ChannelClosedError: - raise ChannelClosedError - except _interpreters.RunFailedError: - raise RunFailedError - return obj - - def recv_nowait(self, default=None): - """recv_nowait(default=None) -> object - - Like recv(), but return the default - instead of waiting. - """ - try: - obj = _interpreters.channel_recv(self.id) - except _interpreters.ChannelEmptyError: - raise ChannelEmptyError - except _interpreters.ChannelNotFoundError: - raise ChannelNotFoundError - except _interpreters.ChannelClosedError: - raise ChannelClosedError - except _interpreters.RunFailedError: - raise RunFailedError - return obj - - -class SendChannel: - - def __init__(self, id): - self.id = id - - def send(self, obj): - """ send(obj) - - Send the object (i.e. its data) to the receiving - end of the channel and wait. Associate the interpreter - with the channel. - """ - try: - _interpreters.channel_send(self.id, obj) - wait(2) - except _interpreters.ChannelNotFoundError: - raise ChannelNotFoundError - except _interpreters.ChannelClosedError: - raise ChannelClosedError - except _interpreters.RunFailedError: - raise RunFailedError - - def send_nowait(self, obj): - """ send_nowait(obj) - - Like send(), but return False if not received. - """ - try: - _interpreters.channel_send(self.id, obj) - except _interpreters.ChannelNotFoundError: - raise ChannelNotFoundError - except _interpreters.ChannelClosedError: - raise ChannelClosedError - except _interpreters.RunFailedError: - raise RunFailedError - - recv_obj = _interpreters.channel_recv(self.id) - if recv_obj: - return obj - else: - return False - - -class ChannelError(Exception): - pass - - -class ChannelNotFoundError(ChannelError): - pass - - -class ChannelEmptyError(ChannelError): - pass - - -class ChannelNotEmptyError(ChannelError): - pass - - -class NotReceivedError(ChannelError): - pass - - -class ChannelClosedError(ChannelError): - pass - - -class ChannelReleasedError(ChannelClosedError): - pass - - -class RunFailedError(RuntimeError): - pass diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py new file mode 100644 index 00000000000000..52bbf2d2a53d9f --- /dev/null +++ b/Lib/test/support/interpreters.py @@ -0,0 +1,176 @@ +"""Subinterpreters High Level Module.""" + +import _xxsubinterpreters as _interpreters + +__all__ = [ + 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', + 'SendChannel', 'RecvChannel', + 'create_channel', 'list_all_channels', 'is_shareable', + 'RunFailedError', + ] + + +def create(*, isolated=True): + """ + Initialize a new (idle) Python interpreter. + + """ + id = _interpreters.create(isolated=isolated) + return Interpreter(id, isolated=isolated) + +def list_all(): + """ + Get all existing interpreters. + """ + return [Interpreter(id) for id in + _interpreters.list_all()] + +def get_current(): + """ + Get the currently running interpreter. + """ + id = _interpreters.get_current() + return Interpreter(id) + +def get_main(): + """ + Get the main interpreter. + """ + id = _interpreters.get_main() + return Interpreter(id) + + +class Interpreter: + """ + The Interpreter object represents + a single interpreter. + """ + + def __init__(self, id, *, isolated=None): + self._id = id + self._isolated = isolated + + @property + def id(self): + return self._id + + @property + def isolated(self): + if self._isolated is None: + self._isolated = _interpreters.is_isolated(self._id) + return self._isolated + + def is_running(self): + """ + Return whether or not the identified + interpreter is running. + """ + return _interpreters.is_running(self._id) + + def close(self): + """ + Finalize and destroy the interpreter. + + Attempting to destroy the current + interpreter results in a RuntimeError. + """ + return _interpreters.destroy(self._id) + + def run(self, src_str, /, *, channels=None): + """ + Run the given source code in the interpreter. + This blocks the current Python thread until done. + """ + try: + _interpreters.run_string(self._id, src_str) + except _interpreters.RunFailedError as err: + raise + + +def is_shareable(obj): + """ + Return `True` if the object's data can be + shared between interpreters and `False` otherwise. + """ + return _interpreters.is_shareable(obj) + +def create_channel(): + """ + Create a new channel for passing data between + interpreters. + """ + + cid = _interpreters.channel_create() + return (RecvChannel(cid), SendChannel(cid)) + +def list_all_channels(): + """ + Get all open channels. + """ + return [(RecvChannel(cid), SendChannel(cid)) + for cid in _interpreters.channel_list_all()] + + +class RecvChannel: + """ + The RecvChannel object represents + a recieving channel. + """ + + def __init__(self, id): + self._id = id + + def recv(self, *, _delay=10 / 1000): # seconds + """ channel_recv() -> obj + Get the next object from the channel, + and wait if none have been sent. + Associate the interpreter with the channel. + """ + import time + sentinel = object() + obj = _interpreters.channel_recv(self._id, sentinel) + while obj is sentinel: + time.sleep(_delay) + obj = _interpreters.channel_recv(self._id, sentinel) + return obj + + _NOT_SET = object() + + def recv_nowait(self, default=None): + """ + Like recv(), but return the default + instead of waiting. + """ + + if default is None: + return _interpreters.channel_recv(self._id) + else: + return _interpreters.channel_recv(self._id, default) + + +class SendChannel: + """ + The SendChannel object represents + a sending channel. + """ + + def __init__(self, id): + self._id = id + + def send(self, obj): + """ + Send the object (i.e. its data) to the receiving + end of the channel and wait. Associate the interpreter + with the channel. + """ + import time + _interpreters.channel_send(self._id, obj) + time.sleep(2) + + def send_nowait(self, obj): + """ + Like send(), but return False if not received. + """ + + _interpreters.channel_send(self._id, obj) + return False diff --git a/Doc/library/_interpreters.rst b/Lib/test/support/interpreters.rst similarity index 92% rename from Doc/library/_interpreters.rst rename to Lib/test/support/interpreters.rst index 771030f0301300..9a05eb67520c8c 100644 --- a/Doc/library/_interpreters.rst +++ b/Lib/test/support/interpreters.rst @@ -1,8 +1,5 @@ -:mod:`_interpreters` --- High-level Subinterpreters Module -========================================================== - -.. module:: _interpreters - :synopsis: High-level Subinterpreters Module. +High-level implementation of Subinterpreters +============================================ **Source code:** :source:`Lib/test/support/_interpreters.py` @@ -17,7 +14,7 @@ It is a wrapper around the low-level ``__xxsubinterpreters`` module. Interpreter Objects ------------------- -The Interpreter object represents a single interpreter. +The ``Interpreter`` object represents a single interpreter. .. class:: Interpreter(id) @@ -41,7 +38,7 @@ The Interpreter object represents a single interpreter. RecvChannel Objects ------------------- -The RecvChannel object represents a recieving channel. +The ``RecvChannel`` object represents a recieving channel. .. class:: RecvChannel(id) diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py index 663b4bbaab1a88..d52d8270ec2e66 100644 --- a/Lib/test/test_interpreters.py +++ b/Lib/test/test_interpreters.py @@ -6,7 +6,7 @@ import time import _xxsubinterpreters as _interpreters -from test.support import _interpreters as interpreters +from test.support import interpreters def _captured_script(script): r, w = os.pipe() @@ -88,7 +88,7 @@ def test_in_subinterpreter(self): main, = interpreters.list_all() interp = interpreters.create() out = _run_output(interp, dedent(""" - from test.support import _interpreters as interpreters + from test.support import interpreters interp = interpreters.create() print(interp) """)) @@ -96,25 +96,6 @@ def test_in_subinterpreter(self): self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2})) - def test_in_threaded_subinterpreter(self): - main, = interpreters.list_all() - interp = interpreters.create() - interp2 = None - def f(): - nonlocal interp2 - out = _run_output(interp, dedent(""" - from test.support import _interpreters as interpreters - interp = interpreters.create() - print(interp) - """)) - interp2 = int(out.strip()) - - t = threading.Thread(target=f) - t.start() - t.join() - - self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2})) - def test_after_destroy_all(self): before = set(interpreters.list_all()) # Create 3 subinterpreters. @@ -154,7 +135,7 @@ def test_subinterpreter(self): main = _interpreters.get_main() interp = interpreters.create() out = _run_output(interp, dedent(""" - from test.support import _interpreters as interpreters + from test.support import interpreters cur = interpreters.get_current() print(cur) """)) @@ -284,7 +265,7 @@ def test_from_current(self): main, = interpreters.list_all() interp = interpreters.create() script = dedent(f""" - from test.support import _interpreters as interpreters + from test.support import interpreters try: main = interpreters.get_current() main.close() @@ -299,7 +280,7 @@ def test_from_sibling(self): main, = interpreters.list_all() interp1 = interpreters.create() script = dedent(f""" - from test.support import _interpreters as interpreters + from test.support import interpreters interp2 = interpreters.create() interp2.close() """) @@ -473,6 +454,7 @@ def test_sequential_ids(self): self.assertEqual(len(set(after) - set(before)), len({channels1, channels2, channels3})) + class TestSendRecv(TestBase): def test_send_recv_main(self): @@ -487,7 +469,7 @@ def test_send_recv_main(self): def test_send_recv_same_interpreter(self): interp = interpreters.create() out = _run_output(interp, dedent(""" - from test.support import _interpreters as interpreters + from test.support import interpreters r, s = interpreters.create_channel() orig = b'spam' s.send(orig) @@ -516,11 +498,6 @@ def f(): self.assertEqual(obj, b'spam') - def test_recv_empty(self): - r, s = interpreters.create_channel() - with self.assertRaises(interpreters.ChannelEmptyError): - r.recv() - def test_send_recv_nowait_main(self): r, s = interpreters.create_channel() orig = b'spam' @@ -533,7 +510,7 @@ def test_send_recv_nowait_main(self): def test_send_recv_nowait_same_interpreter(self): interp = interpreters.create() out = _run_output(interp, dedent(""" - from test.support import _interpreters as interpreters + from test.support import interpreters r, s = interpreters.create_channel() orig = b'spam' s.send(orig) @@ -542,7 +519,7 @@ def test_send_recv_nowait_same_interpreter(self): assert obj == orig """)) - def test_send_recv_nowait_different_threads(self): + r, s = interpreters.create_channel() def f(): @@ -550,7 +527,7 @@ def f(): try: obj = r.recv_nowait() break - except interpreters.ChannelEmptyError: + except _interpreters.ChannelEmptyError: time.sleep(0.1) s.send(obj) t = threading.Thread(target=f) @@ -564,5 +541,5 @@ def f(): def test_recv_nowait_empty(self): r, s = interpreters.create_channel() - with self.assertRaises(interpreters.ChannelEmptyError): + with self.assertRaises(_interpreters.ChannelEmptyError): r.recv_nowait() From af90b25c99d76be03d2c004c2ad32d32f8fa6764 Mon Sep 17 00:00:00 2001 From: Joannah Nanjekye Date: Fri, 15 May 2020 18:15:24 +0000 Subject: [PATCH 10/13] Fix white space --- Lib/test/support/interpreters.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index 52bbf2d2a53d9f..df37248ceb808b 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -11,7 +11,7 @@ def create(*, isolated=True): - """ + """ Initialize a new (idle) Python interpreter. """ @@ -19,14 +19,14 @@ def create(*, isolated=True): return Interpreter(id, isolated=isolated) def list_all(): - """ + """ Get all existing interpreters. """ return [Interpreter(id) for id in _interpreters.list_all()] def get_current(): - """ + """ Get the currently running interpreter. """ id = _interpreters.get_current() @@ -88,14 +88,14 @@ def run(self, src_str, /, *, channels=None): def is_shareable(obj): - """ + """ Return `True` if the object's data can be shared between interpreters and `False` otherwise. """ return _interpreters.is_shareable(obj) def create_channel(): - """ + """ Create a new channel for passing data between interpreters. """ @@ -104,7 +104,7 @@ def create_channel(): return (RecvChannel(cid), SendChannel(cid)) def list_all_channels(): - """ + """ Get all open channels. """ return [(RecvChannel(cid), SendChannel(cid)) @@ -133,7 +133,7 @@ def recv(self, *, _delay=10 / 1000): # seconds time.sleep(_delay) obj = _interpreters.channel_recv(self._id, sentinel) return obj - + _NOT_SET = object() def recv_nowait(self, default=None): @@ -158,7 +158,7 @@ def __init__(self, id): self._id = id def send(self, obj): - """ + """ Send the object (i.e. its data) to the receiving end of the channel and wait. Associate the interpreter with the channel. @@ -168,7 +168,7 @@ def send(self, obj): time.sleep(2) def send_nowait(self, obj): - """ + """ Like send(), but return False if not received. """ From 90ec54b0b4569ff5ad8e7e4fc598172f21ac5c48 Mon Sep 17 00:00:00 2001 From: Joannah Nanjekye Date: Fri, 15 May 2020 18:42:23 +0000 Subject: [PATCH 11/13] Remove undefined from __all__ --- Lib/test/support/interpreters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index df37248ceb808b..246905544337fb 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -6,7 +6,6 @@ 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', 'SendChannel', 'RecvChannel', 'create_channel', 'list_all_channels', 'is_shareable', - 'RunFailedError', ] From 049aa3a51dcccee4bfe319745edee6c6beb3c0f0 Mon Sep 17 00:00:00 2001 From: Joannah Nanjekye Date: Sat, 16 May 2020 17:28:02 +0000 Subject: [PATCH 12/13] Fix recv and add exceptions --- Lib/test/support/interpreters.py | 38 ++++++++++++++++++-------------- Lib/test/test_interpreters.py | 13 ----------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index 246905544337fb..9d3048848e4382 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -1,18 +1,26 @@ """Subinterpreters High Level Module.""" import _xxsubinterpreters as _interpreters +# aliases: +from _xxsubinterpreters import ( + ChannelError, ChannelNotFoundError, + ChannelEmptyError, ChannelNotEmptyError, NotReceivedError, + is_shareable, +) __all__ = [ 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', 'SendChannel', 'RecvChannel', 'create_channel', 'list_all_channels', 'is_shareable', + 'ChannelError', 'ChannelNotFoundError', + 'ChannelEmptyError', 'ChannelNotEmptyError', + 'NotReceivedError', ] def create(*, isolated=True): """ Initialize a new (idle) Python interpreter. - """ id = _interpreters.create(isolated=isolated) return Interpreter(id, isolated=isolated) @@ -80,19 +88,9 @@ def run(self, src_str, /, *, channels=None): Run the given source code in the interpreter. This blocks the current Python thread until done. """ - try: - _interpreters.run_string(self._id, src_str) - except _interpreters.RunFailedError as err: - raise + _interpreters.run_string(self._id, src_str) -def is_shareable(obj): - """ - Return `True` if the object's data can be - shared between interpreters and `False` otherwise. - """ - return _interpreters.is_shareable(obj) - def create_channel(): """ Create a new channel for passing data between @@ -119,8 +117,8 @@ class RecvChannel: def __init__(self, id): self._id = id - def recv(self, *, _delay=10 / 1000): # seconds - """ channel_recv() -> obj + def recv(self, *, _delay=10 / 1000): # 10 milliseconds + """ Get the next object from the channel, and wait if none have been sent. Associate the interpreter with the channel. @@ -133,19 +131,22 @@ def recv(self, *, _delay=10 / 1000): # seconds obj = _interpreters.channel_recv(self._id, sentinel) return obj - _NOT_SET = object() - def recv_nowait(self, default=None): """ Like recv(), but return the default instead of waiting. - """ + This function is blocked by a missing low-level + implementation of channel_recv_wait(). + """ if default is None: + default = _NOT_SET + if default is _NOT_SET: return _interpreters.channel_recv(self._id) else: return _interpreters.channel_recv(self._id, default) +_NOT_SET = object() class SendChannel: """ @@ -169,6 +170,9 @@ def send(self, obj): def send_nowait(self, obj): """ Like send(), but return False if not received. + + This function is blocked by a missing low-level + implementation of channel_send_wait(). """ _interpreters.channel_send(self._id, obj) diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py index d52d8270ec2e66..4f5bed0e420b62 100644 --- a/Lib/test/test_interpreters.py +++ b/Lib/test/test_interpreters.py @@ -530,16 +530,3 @@ def f(): except _interpreters.ChannelEmptyError: time.sleep(0.1) s.send(obj) - t = threading.Thread(target=f) - t.start() - - s.send(b'spam') - t.join() - obj = r.recv_nowait() - - self.assertEqual(obj, b'spam') - - def test_recv_nowait_empty(self): - r, s = interpreters.create_channel() - with self.assertRaises(_interpreters.ChannelEmptyError): - r.recv_nowait() From 9577395e4c0867514974ea546aaa757bc5608735 Mon Sep 17 00:00:00 2001 From: Joannah Nanjekye Date: Mon, 18 May 2020 16:26:00 +0000 Subject: [PATCH 13/13] Remove unused exceptions, fix pep 8 formatting errors and fix _NOT_SET in recv_nowait() --- Lib/test/support/interpreters.py | 20 ++++++++++++-------- Lib/test/test_interpreters.py | 5 ++++- Modules/_xxsubinterpretersmodule.c | 23 ----------------------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index 9d3048848e4382..ef9dcafb2a3860 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -1,20 +1,20 @@ """Subinterpreters High Level Module.""" import _xxsubinterpreters as _interpreters + # aliases: from _xxsubinterpreters import ( - ChannelError, ChannelNotFoundError, - ChannelEmptyError, ChannelNotEmptyError, NotReceivedError, + ChannelError, ChannelNotFoundError, ChannelEmptyError, is_shareable, ) + __all__ = [ 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', 'SendChannel', 'RecvChannel', 'create_channel', 'list_all_channels', 'is_shareable', 'ChannelError', 'ChannelNotFoundError', - 'ChannelEmptyError', 'ChannelNotEmptyError', - 'NotReceivedError', + 'ChannelEmptyError', ] @@ -25,6 +25,7 @@ def create(*, isolated=True): id = _interpreters.create(isolated=isolated) return Interpreter(id, isolated=isolated) + def list_all(): """ Get all existing interpreters. @@ -32,6 +33,7 @@ def list_all(): return [Interpreter(id) for id in _interpreters.list_all()] + def get_current(): """ Get the currently running interpreter. @@ -39,6 +41,7 @@ def get_current(): id = _interpreters.get_current() return Interpreter(id) + def get_main(): """ Get the main interpreter. @@ -100,6 +103,7 @@ def create_channel(): cid = _interpreters.channel_create() return (RecvChannel(cid), SendChannel(cid)) + def list_all_channels(): """ Get all open channels. @@ -108,6 +112,9 @@ def list_all_channels(): for cid in _interpreters.channel_list_all()] +_NOT_SET = object() + + class RecvChannel: """ The RecvChannel object represents @@ -131,7 +138,7 @@ def recv(self, *, _delay=10 / 1000): # 10 milliseconds obj = _interpreters.channel_recv(self._id, sentinel) return obj - def recv_nowait(self, default=None): + def recv_nowait(self, default=_NOT_SET): """ Like recv(), but return the default instead of waiting. @@ -139,14 +146,11 @@ def recv_nowait(self, default=None): This function is blocked by a missing low-level implementation of channel_recv_wait(). """ - if default is None: - default = _NOT_SET if default is _NOT_SET: return _interpreters.channel_recv(self._id) else: return _interpreters.channel_recv(self._id, default) -_NOT_SET = object() class SendChannel: """ diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py index 4f5bed0e420b62..3451a4c8759d8b 100644 --- a/Lib/test/test_interpreters.py +++ b/Lib/test/test_interpreters.py @@ -8,6 +8,7 @@ import _xxsubinterpreters as _interpreters from test.support import interpreters + def _captured_script(script): r, w = os.pipe() indented = script.replace('\n', '\n ') @@ -19,6 +20,7 @@ def _captured_script(script): """) return wrapped, open(r) + def clean_up_interpreters(): for interp in interpreters.list_all(): if interp.id == 0: # main @@ -28,12 +30,14 @@ def clean_up_interpreters(): except RuntimeError: pass # already destroyed + def _run_output(interp, request, shared=None): script, rpipe = _captured_script(request) with rpipe: interp.run(script) return rpipe.read() + @contextlib.contextmanager def _running(interp): r, w = os.pipe() @@ -519,7 +523,6 @@ def test_send_recv_nowait_same_interpreter(self): assert obj == orig """)) - r, s = interpreters.create_channel() def f(): diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 74a665eba8b741..9c5df16e156a1d 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -1167,8 +1167,6 @@ static PyObject *ChannelNotFoundError; static PyObject *ChannelClosedError; static PyObject *ChannelEmptyError; static PyObject *ChannelNotEmptyError; -static PyObject *ChannelReleasedError; -static PyObject *NotReceivedError; static int channel_exceptions_init(PyObject *ns) @@ -1205,27 +1203,6 @@ channel_exceptions_init(PyObject *ns) return -1; } - // An operation tried to use a released channel. - ChannelReleasedError = PyErr_NewException( - "_interpreters.ChannelReleasedError", ChannelClosedError, NULL); - if (ChannelReleasedError == NULL) { - return -1; - } - if (PyDict_SetItemString(ns, "ChannelReleasedError", ChannelReleasedError) != 0) { - return -1; - } - - // An operation trying to send an object when Nothing was waiting - // to receive it - NotReceivedError = PyErr_NewException( - "_interpreters.NotReceivedError", ChannelError, NULL); - if (NotReceivedError == NULL) { - return -1; - } - if (PyDict_SetItemString(ns, "NotReceivedError", NotReceivedError) != 0) { - return -1; - } - // An operation tried to pop from an empty channel. ChannelEmptyError = PyErr_NewException( "_xxsubinterpreters.ChannelEmptyError", ChannelError, NULL);