<div dir="ltr">Sorry to burst your bubble, but I have not followed any of the discussion and I am actually very worried about this topic. I don't think I will be able to make time for this before the 3.7b1 feature freeze.<br></div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Dec 5, 2017 at 6:51 PM, Eric Snow <span dir="ltr"><<a href="mailto:ericsnowcurrently@gmail.com" target="_blank">ericsnowcurrently@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi all,<br>
<br>
I've finally updated PEP 554.  Feedback would be most welcome.  The<br>
PEP is in a pretty good place now and I hope to we're close to a<br>
decision to accept it. :)<br>
<br>
In addition to resolving the open questions, I've also made the<br>
following changes to the PEP:<br>
<br>
* put an API summary at the top and moved the full API description down<br>
* add the "is_shareable()" function to indicate if an object can be shared<br>
* added None as a shareable object<br>
<br>
Regarding the open questions:<br>
<br>
 * "Leaking exceptions across interpreters"<br>
<br>
I chose to go with an approach that effectively creates a<br>
traceback.TracebackException proxy of the original exception, wraps<br>
that in a RuntimeError, and raises that in the calling interpreter.<br>
Raising an exception that safely preserves the original exception and<br>
traceback seems like the most intuitive behavior (to me, as a user).<br>
The only alternative that made sense is to fully duplicate the<br>
exception and traceback (minus stack frames) in the calling<br>
interpreter, which is probably overkill and likely to be confusing.<br>
<br>
 * "Initial support for buffers in channels"<br>
<br>
I chose to add a "SendChannel.send_buffer(obj)" method for this.<br>
Supporting buffer objects from the beginning makes sense, opening good<br>
experimentation opportunities for a valuable set of users.  Supporting<br>
buffer objects separately and explicitly helps set clear expectations<br>
for users.  I decided not to go with a separate class (e.g.<br>
MemChannel) as it didn't seem like there's enough difference to<br>
warrant keeping them strictly separate.<br>
<br>
FWIW, I'm still strongly in favor of support for passing (copies of)<br>
bytes objects via channels.  Passing objects to SendChannel.send() is<br>
obvious.  Limiting it, for now, to bytes (and None) helps us avoid<br>
tying ourselves strongly to any particular implementation (it seems<br>
like all the reservations were relative to the implementation).  So I<br>
do not see a reason to wait.<br>
<br>
 * "Pass channels explicitly to run()?"<br>
<br>
I've applied the suggested solution (make "channels" an explicit<br>
keyword argument).<br>
<br>
-eric<br>
<br>
<br>
I've include the latest full text<br>
(<a href="https://www.python.org/dev/peps/pep-0554/" rel="noreferrer" target="_blank">https://www.python.org/dev/<wbr>peps/pep-0554/</a>) below:<br>
<br>
++++++++++++++++++++++++++++++<wbr>+++++++++++++++++++<br>
<br>
PEP: 554<br>
Title: Multiple Interpreters in the Stdlib<br>
Author: Eric Snow <<a href="mailto:ericsnowcurrently@gmail.com">ericsnowcurrently@gmail.com</a>><br>
Status: Draft<br>
Type: Standards Track<br>
Content-Type: text/x-rst<br>
Created: 2017-09-05<br>
Python-Version: 3.7<br>
Post-History: 07-Sep-2017, 08-Sep-2017, 13-Sep-2017, 05-Dec-2017<br>
<br>
<br>
Abstract<br>
========<br>
<br>
CPython has supported multiple interpreters in the same process (AKA<br>
"subinterpreters") since version 1.5.  The feature has been available<br>
via the C-API. [c-api]_ Subinterpreters operate in<br>
`relative isolation from one another <Interpreter Isolation_>`_, which<br>
provides the basis for an<br>
`alternative concurrency model <Concurrency_>`_.<br>
<br>
This proposal introduces the stdlib ``interpreters`` module.  The module<br>
will be `provisional <Provisional Status_>`_.  It exposes the basic<br>
functionality of subinterpreters already provided by the C-API, along<br>
with new functionality for sharing data between interpreters.<br>
<br>
<br>
Proposal<br>
========<br>
<br>
The ``interpreters`` module will be added to the stdlib.  It will<br>
provide a high-level interface to subinterpreters and wrap a new<br>
low-level ``_interpreters`` (in the same was as the ``threading``<br>
module).  See the `Examples`_ section for concrete usage and use cases.<br>
<br>
Along with exposing the existing (in CPython) subinterpreter support,<br>
the module will also provide a mechanism for sharing data between<br>
interpreters.  This mechanism centers around "channels", which are<br>
similar to queues and pipes.<br>
<br>
Note that *objects* are not shared between interpreters since they are<br>
tied to the interpreter in which they were created.  Instead, the<br>
objects' *data* is passed between interpreters.  See the `Shared data`_<br>
section for more details about sharing between interpreters.<br>
<br>
At first only the following types will be supported for sharing:<br>
<br>
* None<br>
* bytes<br>
* PEP 3118 buffer objects (via ``send_buffer()``)<br>
<br>
Support for other basic types (e.g. int, Ellipsis) will be added later.<br>
<br>
API summary for interpreters module<br>
------------------------------<wbr>-----<br>
<br>
Here is a summary of the API for the ``interpreters`` module.  For a<br>
more in-depth explanation of the proposed classes and functions, see<br>
the `"interpreters" Module API`_ section below.<br>
<br>
For creating and using interpreters:<br>
<br>
+-----------------------------<wbr>-+----------------------------<wbr>------------------+<br>
| signature                    | description                                  |<br>
+============================+<wbr>=+============================<wbr>==================+<br>
| list_all() -> [Intepreter]   | Get all existing interpreters.               |<br>
+-----------------------------<wbr>-+----------------------------<wbr>------------------+<br>
| get_current() -> Interpreter | Get the currently running interpreter.       |<br>
+-----------------------------<wbr>-+----------------------------<wbr>------------------+<br>
| create() -> Interpreter      | Initialize a new (idle) Python interpreter.  |<br>
+-----------------------------<wbr>-+----------------------------<wbr>------------------+<br>
<br>
|<br>
<br>
+-----------------------+-----<wbr>------------------------------<wbr>------------------+<br>
| signature             | description                                         |<br>
+=======================+=====<wbr>==============================<wbr>==================+<br>
| class Interpreter(id) | A single interpreter.                               |<br>
+-----------------------+-----<wbr>------------------------------<wbr>------------------+<br>
| .id                   | The interpreter's ID (read-only).                   |<br>
+-----------------------+-----<wbr>------------------------------<wbr>------------------+<br>
| .is_running() -> Bool | Is the interpreter currently executing code?        |<br>
+-----------------------+-----<wbr>------------------------------<wbr>------------------+<br>
| .destroy()            | Finalize and destroy the interpreter.               |<br>
+-----------------------+-----<wbr>------------------------------<wbr>------------------+<br>
| .run(src_str, /, \*,  | | Run the given source code in the interpreter.     |<br>
|      channels=None)   | | (This blocks the current thread until done.)      |<br>
+-----------------------+-----<wbr>------------------------------<wbr>------------------+<br>
<br>
For sharing data between interpreters:<br>
<br>
+-----------------------------<wbr>---+--------------------------<wbr>------------------+<br>
| signature                      | description                                |<br>
+=============================<wbr>===+==========================<wbr>==================+<br>
| is_shareable(obj) -> Bool      | | Can the object's data be shared          |<br>
|                                | | between interpreters?                    |<br>
+-----------------------------<wbr>---+--------------------------<wbr>------------------+<br>
| create_channel() ->            | | Create a new channel for passing         |<br>
|   (RecvChannel, SendChannel)   | | data between interpreters.               |<br>
+-----------------------------<wbr>---+--------------------------<wbr>------------------+<br>
| list_all_channels() ->         | Get all open channels.                     |<br>
|   [(RecvChannel, SendChannel)] |                                            |<br>
+-----------------------------<wbr>---+--------------------------<wbr>------------------+<br>
<br>
|<br>
<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
| signature                     | description<br>
         |<br>
+=============================<wbr>==+===========================<wbr>====================+<br>
| class RecvChannel(id)         | The receiving end of a channel.<br>
         |<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
| .id                           | The channel's unique ID.<br>
         |<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
| .interpreters                 | The list of associated interpreters.<br>
         |<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
| .recv() -> object             | | Get the next object from the<br>
channel,       |<br>
|                               | | and wait if none have been sent.<br>
         |<br>
|                               | | Associate the interpreter with the<br>
channel. |<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
| .recv_nowait(default=None) -> | | Like recv(), but return the<br>
default         |<br>
|   object                      | | instead of waiting.<br>
         |<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
| .close()                      | | No longer associate the current<br>
interpreter |<br>
|                               | | with the channel (on the receiving<br>
end).    |<br>
+-----------------------------<wbr>--+---------------------------<wbr>--------------------+<br>
<br>
|<br>
<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| signature                 | description                                     |<br>
+===========================+=<wbr>==============================<wbr>==================+<br>
| class SendChannel(id)     | The sending end of a channel.                   |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .id                       | The channel's unique ID.                        |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .interpreters             | The list of associated interpreters.            |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .send(obj)                | | Send the object (i.e. its data) to the        |<br>
|                           | | receiving end of the channel and wait.        |<br>
|                           | | Associate the interpreter with the channel.   |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .send_nowait(obj)         | | Like send(), but Fail if not received.        |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .send_buffer(obj)         | | Send the object's (PEP 3118) buffer to the    |<br>
|                           | | receiving end of the channel and wait.        |<br>
|                           | | Associate the interpreter with the channel.   |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .send_buffer_nowait(obj)  | | Like send_buffer(), but fail if not received. |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
| .close()                  | | No longer associate the current interpreter   |<br>
|                           | | with the channel (on the sending end).        |<br>
+---------------------------+-<wbr>------------------------------<wbr>------------------+<br>
<br>
<br>
Examples<br>
========<br>
<br>
Run isolated code<br>
-----------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   print('before')<br>
   interp.run('print("during")')<br>
   print('after')<br>
<br>
Run in a thread<br>
---------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   def run():<br>
       interp.run('print("during")')<br>
   t = threading.Thread(target=run)<br>
   print('before')<br>
   t.start()<br>
   print('after')<br>
<br>
Pre-populate an interpreter<br>
---------------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   interp.run(tw.dedent("""<br>
       import some_lib<br>
       import an_expensive_module<br>
       some_lib.set_up()<br>
       """))<br>
   wait_for_request()<br>
   interp.run(tw.dedent("""<br>
       some_lib.handle_request()<br>
       """))<br>
<br>
Handling an exception<br>
---------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   try:<br>
       interp.run(tw.dedent("""<br>
           raise KeyError<br>
           """))<br>
   except KeyError:<br>
       print("got the error from the subinterpreter")<br>
<br>
Synchronize using a channel<br>
---------------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   r, s = interpreters.create_channel()<br>
   def run():<br>
       interp.run(tw.dedent("""<br>
           reader.recv()<br>
           print("during")<br>
           reader.close()<br>
           """),<br>
           reader=r))<br>
   t = threading.Thread(target=run)<br>
   print('before')<br>
   t.start()<br>
   print('after')<br>
   s.send(b'')<br>
   s.close()<br>
<br>
Sharing a file descriptor<br>
-------------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   r1, s1 = interpreters.create_channel()<br>
   r2, s2 = interpreters.create_channel()<br>
   def run():<br>
       interp.run(tw.dedent("""<br>
           fd = int.from_bytes(<br>
                   reader.recv(), 'big')<br>
           for line in os.fdopen(fd):<br>
               print(line)<br>
           writer.send(b'')<br>
           """),<br>
           reader=r1, writer=s2)<br>
   t = threading.Thread(target=run)<br>
   t.start()<br>
   with open('spamspamspam') as infile:<br>
       fd = infile.fileno().to_bytes(1, 'big')<br>
       s.send(fd)<br>
       r.recv()<br>
<br>
Passing objects via marshal<br>
---------------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   r, s = interpreters.create_fifo()<br>
   interp.run(tw.dedent("""<br>
       import marshal<br>
       """),<br>
       reader=r)<br>
   def run():<br>
       interp.run(tw.dedent("""<br>
           data = reader.recv()<br>
           while data:<br>
               obj = marshal.loads(data)<br>
               do_something(obj)<br>
               data = reader.recv()<br>
           reader.close()<br>
           """),<br>
           reader=r)<br>
   t = threading.Thread(target=run)<br>
   t.start()<br>
   for obj in input:<br>
       data = marshal.dumps(obj)<br>
       s.send(data)<br>
   s.send(b'')<br>
<br>
Passing objects via pickle<br>
--------------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   r, s = interpreters.create_channel()<br>
   interp.run(tw.dedent("""<br>
       import pickle<br>
       """),<br>
       reader=r)<br>
   def run():<br>
       interp.run(tw.dedent("""<br>
           data = reader.recv()<br>
           while data:<br>
               obj = pickle.loads(data)<br>
               do_something(obj)<br>
               data = reader.recv()<br>
           reader.close()<br>
           """),<br>
           reader=r)<br>
   t = threading.Thread(target=run)<br>
   t.start()<br>
   for obj in input:<br>
       data = pickle.dumps(obj)<br>
       s.send(data)<br>
   s.send(b'')<br>
<br>
Running a module<br>
----------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   main_module = mod_name<br>
   interp.run(f'import runpy; runpy.run_module({main_module!<wbr>r})')<br>
<br>
Running as script (including zip archives & directories)<br>
------------------------------<wbr>--------------------------<br>
<br>
::<br>
<br>
   interp = interpreters.create()<br>
   main_script = path_name<br>
   interp.run(f"import runpy; runpy.run_path({main_script!r}<wbr>)")<br>
<br>
Running in a thread pool executor<br>
------------------------------<wbr>---<br>
<br>
::<br>
<br>
   interps = [interpreters.create() for i in range(5)]<br>
   with concurrent.futures.<wbr>ThreadPoolExecutor(max_<wbr>workers=len(interps)) as pool:<br>
       print('before')<br>
       for interp in interps:<br>
           pool.submit(interp.run, 'print("starting"); print("stopping")'<br>
       print('after')<br>
<br>
<br>
Rationale<br>
=========<br>
<br>
Running code in multiple interpreters provides a useful level of<br>
isolation within the same process.  This can be leveraged in a number<br>
of ways.  Furthermore, subinterpreters provide a well-defined framework<br>
in which such isolation may extended.<br>
<br>
Nick Coghlan explained some of the benefits through a comparison with<br>
multi-processing [benefits]_::<br>
<br>
   [I] expect that communicating between subinterpreters is going<br>
   to end up looking an awful lot like communicating between<br>
   subprocesses via shared memory.<br>
<br>
   The trade-off between the two models will then be that one still<br>
   just looks like a single process from the point of view of the<br>
   outside world, and hence doesn't place any extra demands on the<br>
   underlying OS beyond those required to run CPython with a single<br>
   interpreter, while the other gives much stricter isolation<br>
   (including isolating C globals in extension modules), but also<br>
   demands much more from the OS when it comes to its IPC<br>
   capabilities.<br>
<br>
   The security risk profiles of the two approaches will also be quite<br>
   different, since using subinterpreters won't require deliberately<br>
   poking holes in the process isolation that operating systems give<br>
   you by default.<br>
<br>
CPython has supported subinterpreters, with increasing levels of<br>
support, since version 1.5.  While the feature has the potential<br>
to be a powerful tool, subinterpreters have suffered from neglect<br>
because they are not available directly from Python.  Exposing the<br>
existing functionality in the stdlib will help reverse the situation.<br>
<br>
This proposal is focused on enabling the fundamental capability of<br>
multiple isolated interpreters in the same Python process.  This is a<br>
new area for Python so there is relative uncertainly about the best<br>
tools to provide as companions to subinterpreters.  Thus we minimize<br>
the functionality we add in the proposal as much as possible.<br>
<br>
Concerns<br>
--------<br>
<br>
* "subinterpreters are not worth the trouble"<br>
<br>
Some have argued that subinterpreters do not add sufficient benefit<br>
to justify making them an official part of Python.  Adding features<br>
to the language (or stdlib) has a cost in increasing the size of<br>
the language.  So an addition must pay for itself.  In this case,<br>
subinterpreters provide a novel concurrency model focused on isolated<br>
threads of execution.  Furthermore, they provide an opportunity for<br>
changes in CPython that will allow simulateous use of multiple CPU<br>
cores (currently prevented by the GIL).<br>
<br>
Alternatives to subinterpreters include threading, async, and<br>
multiprocessing.  Threading is limited by the GIL and async isn't<br>
the right solution for every problem (nor for every person).<br>
Multiprocessing is likewise valuable in some but not all situations.<br>
Direct IPC (rather than via the multiprocessing module) provides<br>
similar benefits but with the same caveat.<br>
<br>
Notably, subinterpreters are not intended as a replacement for any of<br>
the above.  Certainly they overlap in some areas, but the benefits of<br>
subinterpreters include isolation and (potentially) performance.  In<br>
particular, subinterpreters provide a direct route to an alternate<br>
concurrency model (e.g. CSP) which has found success elsewhere and<br>
will appeal to some Python users.  That is the core value that the<br>
``interpreters`` module will provide.<br>
<br>
* "stdlib support for subinterpreters adds extra burden<br>
  on C extension authors"<br>
<br>
In the `Interpreter Isolation`_ section below we identify ways in<br>
which isolation in CPython's subinterpreters is incomplete.  Most<br>
notable is extension modules that use C globals to store internal<br>
state.  PEP 3121 and PEP 489 provide a solution for most of the<br>
problem, but one still remains. [petr-c-ext]_  Until that is resolved,<br>
C extension authors will face extra difficulty to support<br>
subinterpreters.<br>
<br>
Consequently, projects that publish extension modules may face an<br>
increased maintenance burden as their users start using subinterpreters,<br>
where their modules may break.  This situation is limited to modules<br>
that use C globals (or use libraries that use C globals) to store<br>
internal state.  For numpy, the reported-bug rate is one every 6<br>
months. [bug-rate]_<br>
<br>
Ultimately this comes down to a question of how often it will be a<br>
problem in practice: how many projects would be affected, how often<br>
their users will be affected, what the additional maintenance burden<br>
will be for projects, and what the overall benefit of subinterpreters<br>
is to offset those costs.  The position of this PEP is that the actual<br>
extra maintenance burden will be small and well below the threshold at<br>
which subinterpreters are worth it.<br>
<br>
<br>
About Subinterpreters<br>
=====================<br>
<br>
Concurrency<br>
-----------<br>
<br>
Concurrency is a challenging area of software development.  Decades of<br>
research and practice have led to a wide variety of concurrency models,<br>
each with different goals.  Most center on correctness and usability.<br>
<br>
One class of concurrency models focuses on isolated threads of<br>
execution that interoperate through some message passing scheme.  A<br>
notable example is `Communicating Sequential Processes`_ (CSP), upon<br>
which Go's concurrency is based.  The isolation inherent to<br>
subinterpreters makes them well-suited to this approach.<br>
<br>
Shared data<br>
-----------<br>
<br>
Subinterpreters are inherently isolated (with caveats explained below),<br>
in contrast to threads.  So the same communicate-via-shared-memory<br>
approach doesn't work.  Without an alternative, effective use of<br>
concurrency via subinterpreters is significantly limited.<br>
<br>
The key challenge here is that sharing objects between interpreters<br>
faces complexity due to various constraints on object ownership,<br>
visibility, and mutability.  At a conceptual level it's easier to<br>
reason about concurrency when objects only exist in one interpreter<br>
at a time.  At a technical level, CPython's current memory model<br>
limits how Python *objects* may be shared safely between interpreters;<br>
effectively objects are bound to the interpreter in which they were<br>
created.  Furthermore the complexity of *object* sharing increases as<br>
subinterpreters become more isolated, e.g. after GIL removal.<br>
<br>
Consequently,the mechanism for sharing needs to be carefully considered.<br>
There are a number of valid solutions, several of which may be<br>
appropriate to support in Python.  This proposal provides a single basic<br>
solution: "channels".  Ultimately, any other solution will look similar<br>
to the proposed one, which will set the precedent.  Note that the<br>
implementation of ``Interpreter.run()`` can be done in a way that allows<br>
for multiple solutions to coexist, but doing so is not technically<br>
a part of the proposal here.<br>
<br>
Regarding the proposed solution, "channels", it is a basic, opt-in data<br>
sharing mechanism that draws inspiration from pipes, queues, and CSP's<br>
channels. [fifo]_<br>
<br>
As simply described earlier by the API summary,<br>
channels have two operations: send and receive.  A key characteristic<br>
of those operations is that channels transmit data derived from Python<br>
objects rather than the objects themselves.  When objects are sent,<br>
their data is extracted.  When the "object" is received in the other<br>
interpreter, the data is converted back into an object.<br>
<br>
To make this work, the mutable shared state will be managed by the<br>
Python runtime, not by any of the interpreters.  Initially we will<br>
support only one type of objects for shared state: the channels provided<br>
by ``create_channel()``.  Channels, in turn, will carefully manage<br>
passing objects between interpreters.<br>
<br>
This approach, including keeping the API minimal, helps us avoid further<br>
exposing any underlying complexity to Python users.  Along those same<br>
lines, we will initially restrict the types that may be passed through<br>
channels to the following:<br>
<br>
* None<br>
* bytes<br>
* PEP 3118 buffer objects (via ``send_buffer()``)<br>
<br>
Limiting the initial shareable types is a practical matter, reducing<br>
the potential complexity of the initial implementation.  There are a<br>
number of strategies we may pursue in the future to expand supported<br>
objects and object sharing strategies.<br>
<br>
Interpreter Isolation<br>
---------------------<br>
<br>
CPython's interpreters are intended to be strictly isolated from each<br>
other.  Each interpreter has its own copy of all modules, classes,<br>
functions, and variables.  The same applies to state in C, including in<br>
extension modules.  The CPython C-API docs explain more. [caveats]_<br>
<br>
However, there are ways in which interpreters share some state.  First<br>
of all, some process-global state remains shared:<br>
<br>
* file descriptors<br>
* builtin types (e.g. dict, bytes)<br>
* singletons (e.g. None)<br>
* underlying static module data (e.g. functions) for<br>
  builtin/extension/frozen modules<br>
<br>
There are no plans to change this.<br>
<br>
Second, some isolation is faulty due to bugs or implementations that did<br>
not take subinterpreters into account.  This includes things like<br>
extension modules that rely on C globals. [cryptography]_  In these<br>
cases bugs should be opened (some are already):<br>
<br>
* readline module hook functions (<a href="http://bugs.python.org/issue4202" rel="noreferrer" target="_blank">http://bugs.python.org/<wbr>issue4202</a>)<br>
* memory leaks on re-init (<a href="http://bugs.python.org/issue21387" rel="noreferrer" target="_blank">http://bugs.python.org/<wbr>issue21387</a>)<br>
<br>
Finally, some potential isolation is missing due to the current design<br>
of CPython.  Improvements are currently going on to address gaps in this<br>
area:<br>
<br>
* interpreters share the GIL<br>
* interpreters share memory management (e.g. allocators, gc)<br>
* GC is not run per-interpreter [global-gc]_<br>
* at-exit handlers are not run per-interpreter [global-atexit]_<br>
* extensions using the ``PyGILState_*`` API are incompatible [gilstate]_<br>
<br>
Existing Usage<br>
--------------<br>
<br>
Subinterpreters are not a widely used feature.  In fact, the only<br>
documented cases of wide-spread usage are<br>
`mod_wsgi <<a href="https://github.com/GrahamDumpleton/mod_wsgi" rel="noreferrer" target="_blank">https://github.com/<wbr>GrahamDumpleton/mod_wsgi</a>>`_and<br>
`JEP <<a href="https://github.com/ninia/jep" rel="noreferrer" target="_blank">https://github.com/ninia/jep</a>><wbr>`_.  On the one hand, this case<br>
provides confidence that existing subinterpreter support is relatively<br>
stable.  On the other hand, there isn't much of a sample size from which<br>
to judge the utility of the feature.<br>
<br>
<br>
Provisional Status<br>
==================<br>
<br>
The new ``interpreters`` module will be added with "provisional" status<br>
(see PEP 411).  This allows Python users to experiment with the feature<br>
and provide feedback while still allowing us to adjust to that feedback.<br>
The module will be provisional in Python 3.7 and we will make a decision<br>
before the 3.8 release whether to keep it provisional, graduate it, or<br>
remove it.<br>
<br>
<br>
Alternate Python Implementations<br>
==============================<wbr>==<br>
<br>
I'll be soliciting feedback from the different Python implementors about<br>
subinterpreter support.<br>
<br>
Multiple-interpter support in the major Python implementations:<br>
<br>
TBD<br>
<br>
* jython: yes [jython]_<br>
* ironpython: yes?<br>
* pypy: maybe not? [pypy]_<br>
* micropython: ???<br>
<br>
<br>
"interpreters" Module API<br>
=========================<br>
<br>
The module provides the following functions:<br>
<br>
``list_all()``::<br>
<br>
   Return a list of all existing interpreters.<br>
<br>
``get_current()``::<br>
<br>
   Return the currently running interpreter.<br>
<br>
``create()``::<br>
<br>
   Initialize a new Python interpreter and return it.  The<br>
   interpreter will be created in the current thread and will remain<br>
   idle until something is run in it.  The interpreter may be used<br>
   in any thread and will run in whichever thread calls<br>
   ``interp.run()``.<br>
<br>
<br>
The module also provides the following class:<br>
<br>
``Interpreter(id)``::<br>
<br>
   id:<br>
<br>
      The interpreter's ID (read-only).<br>
<br>
   is_running():<br>
<br>
      Return whether or not the interpreter is currently executing code.<br>
      Calling this on the current interpreter will always return True.<br>
<br>
   destroy():<br>
<br>
      Finalize and destroy the interpreter.<br>
<br>
      This may not be called on an already running interpreter.  Doing<br>
      so results in a RuntimeError.<br>
<br>
   run(source_str, /, *, channels=None):<br>
<br>
      Run the provided Python source code in the interpreter.  If the<br>
      "channels" keyword argument is provided (and is a mapping of<br>
      attribute names to channels) then it is added to the interpreter's<br>
      execution namespace (the interpreter's "__main__" module).  If any<br>
      of the values are not are not RecvChannel or SendChannel instances<br>
      then ValueError gets raised.<br>
<br>
      This may not be called on an already running interpreter.  Doing<br>
      so results in a RuntimeError.<br>
<br>
      A "run()" call is similar to a function call.  Once it completes,<br>
      the code that called "run()" continues executing (in the original<br>
      interpreter).  Likewise, if there is any uncaught exception then<br>
      it effectively (see below) propagates into the code where<br>
      ``run()`` was called.  However, unlike function calls (but like<br>
      threads), there is no return value.  If any value is needed, pass<br>
      it out via a channel.<br>
<br>
      The big difference is that "run()" executes the code in an<br>
      entirely different interpreter, with entirely separate state.<br>
      The state of the current interpreter in the current OS thread<br>
      is swapped out with the state of the target interpreter (the one<br>
      that will execute the code).  When the target finishes executing,<br>
      the original interpreter gets swapped back in and its execution<br>
      resumes.<br>
<br>
      So calling "run()" will effectively cause the current Python<br>
      thread to pause.  Sometimes you won't want that pause, in which<br>
      case you should make the "run()" call in another thread.  To do<br>
      so, add a function that calls "run()" and then run that function<br>
      in a normal "threading.Thread".<br>
<br>
      Note that the interpreter's state is never reset, neither before<br>
      "run()" executes the code nor after.  Thus the interpreter<br>
      state is preserved between calls to "run()".  This includes<br>
      "sys.modules", the "builtins" module, and the internal state<br>
      of C extension modules.<br>
<br>
      Also note that "run()" executes in the namespace of the "__main__"<br>
      module, just like scripts, the REPL, "-m", and "-c".  Just as<br>
      the interpreter's state is not ever reset, the "__main__" module<br>
      is never reset.  You can imagine concatenating the code from each<br>
      "run()" call into one long script.  This is the same as how the<br>
      REPL operates.<br>
<br>
      Regarding uncaught exceptions, we noted that they are<br>
      "effectively" propagated into the code where ``run()`` was called.<br>
      To prevent leaking exceptions (and tracebacks) between<br>
      interpreters, we create a surrogate of the exception and its<br>
      traceback (see ``traceback.<wbr>TracebackException``), wrap it in a<br>
      RuntimeError, and raise that.<br>
<br>
      Supported code: source text.<br>
<br>
<br>
API for sharing data<br>
--------------------<br>
<br>
Subinterpreters are less useful without a mechanism for sharing data<br>
between them.  Sharing actual Python objects between interpreters,<br>
however, has enough potential problems that we are avoiding support<br>
for that here.  Instead, only mimimum set of types will be supported.<br>
Initially this will include ``bytes`` and channels.  Further types may<br>
be supported later.<br>
<br>
The ``interpreters`` module provides a way for users to determine<br>
whether an object is shareable or not:<br>
<br>
``is_shareable(obj)``::<br>
<br>
   Return True if the object may be shared between interpreters.  This<br>
   does not necessarily mean that the actual objects will be shared.<br>
   Insead, it means that the objects' underlying data will be shared in<br>
   a cross-interpreter way, whether via a proxy, a copy, or some other<br>
   means.<br>
<br>
This proposal provides two ways to do share such objects between<br>
interpreters.<br>
<br>
First, shareable objects may be passed to ``run()`` as keyword arguments,<br>
where they are effectively injected into the target interpreter's<br>
``__main__`` module.  This is mainly intended for sharing meta-objects<br>
(e.g. channels) between interpreters, as it is less useful to pass other<br>
objects (like ``bytes``) to ``run``.<br>
<br>
Second, the main mechanism for sharing objects (i.e. their data) between<br>
interpreters is through channels.  A channel is a simplex FIFO similar<br>
to a pipe.  The main difference is that channels can be associated with<br>
zero or more interpreters on either end.  Unlike queues, which are also<br>
many-to-many, channels have no buffer.<br>
<br>
``create_channel()``::<br>
<br>
   Create a new channel and return (recv, send), the RecvChannel and<br>
   SendChannel corresponding to the ends of the channel.  The channel<br>
   is not closed and destroyed (i.e. garbage-collected) until the number<br>
   of associated interpreters returns to 0.<br>
<br>
   An interpreter gets associated with a channel by calling its "send()"<br>
   or "recv()" method.  That association gets dropped by calling<br>
   "close()" on the channel.<br>
<br>
   Both ends of the channel are supported "shared" objects (i.e. may be<br>
   safely shared by different interpreters.  Thus they may be passed as<br>
   keyword arguments to "Interpreter.run()".<br>
<br>
``list_all_channels()``::<br>
<br>
   Return a list of all open (RecvChannel, SendChannel) pairs.<br>
<br>
<br>
``RecvChannel(id)``::<br>
<br>
   The receiving end of a channel.  An interpreter may use this to<br>
   receive objects from another interpreter.  At first only bytes will<br>
   be supported.<br>
<br>
   id:<br>
<br>
      The channel's unique ID.<br>
<br>
   interpreters:<br>
<br>
      The list of associated interpreters: those that have called<br>
      the "recv()" or "__next__()" methods and haven't called "close()".<br>
<br>
   recv():<br>
<br>
      Return the next object (i.e. the data from the sent object) from<br>
      the channel.  If none have been sent then wait until the next<br>
      send.  This associates the current interpreter with the channel.<br>
<br>
      If the channel is already closed (see the close() method)<br>
      then raise EOFError.  If the channel isn't closed, but the current<br>
      interpreter already called the "close()" method (which drops its<br>
      association with the channel) then raise ValueError.<br>
<br>
   recv_nowait(default=None):<br>
<br>
      Return the next object from the channel.  If none have been sent<br>
      then return the default.  Otherwise, this is the same as the<br>
      "recv()" method.<br>
<br>
   close():<br>
<br>
      No longer associate the current interpreter with the channel (on<br>
      the receiving end) and block future association (via the "recv()"<br>
      method.  If the interpreter was never associated with the channel<br>
      then still block future association.  Once an interpreter is no<br>
      longer associated with the channel, subsequent (or current) send()<br>
      and recv() calls from that interpreter will raise ValueError<br>
      (or EOFError if the channel is actually marked as closed).<br>
<br>
      Once the number of associated interpreters on both ends drops<br>
      to 0, the channel is actually marked as closed.  The Python<br>
      runtime will garbage collect all closed channels, though it may<br>
      not be immediately.  Note that "close()" is automatically called<br>
      in behalf of the current interpreter when the channel is no longer<br>
      used (i.e. has no references) in that interpreter.<br>
<br>
      This operation is idempotent.  Return True if "close()" has not<br>
      been called before by the current interpreter.<br>
<br>
<br>
``SendChannel(id)``::<br>
<br>
   The sending end of a channel.  An interpreter may use this to send<br>
   objects to another interpreter.  At first only bytes will be<br>
   supported.<br>
<br>
   id:<br>
<br>
      The channel's unique ID.<br>
<br>
   interpreters:<br>
<br>
      The list of associated interpreters (those that have called<br>
      the "send()" method).<br>
<br>
   send(obj):<br>
<br>
      Send the object (i.e. its data) to the receiving end of the<br>
      channel.  Wait until the object is received.  If the the<br>
      object is not shareable then ValueError is raised.  Currently<br>
      only bytes are supported.<br>
<br>
      If the channel is already closed (see the close() method)<br>
      then raise EOFError.  If the channel isn't closed, but the current<br>
      interpreter already called the "close()" method (which drops its<br>
      association with the channel) then raise ValueError.<br>
<br>
   send_nowait(obj):<br>
<br>
      Send the object to the receiving end of the channel.  If the other<br>
      end is not currently receiving then raise RuntimeError.  Otherwise<br>
      this is the same as "send()".<br>
<br>
   send_buffer(obj):<br>
<br>
      Send a MemoryView of the object rather than the object.  Otherwise<br>
      this is the same as send().  Note that the object must implement<br>
      the PEP 3118 buffer protocol.<br>
<br>
   send_buffer_nowait(obj):<br>
<br>
      Send a MemoryView of the object rather than the object.  If the<br>
      other end is not currently receiving then raise RuntimeError.<br>
      Otherwise this is the same as "send_buffer()".<br>
<br>
   close():<br>
<br>
      This is the same as "RecvChannel.close(), but applied to the<br>
      sending end of the channel.<br>
<br>
Note that ``send_buffer()`` is similar to how<br>
``multiprocessing.Connection`` works. [mp-conn]_<br>
<br>
<br>
Open Questions<br>
==============<br>
<br>
None<br>
<br>
<br>
Open Implementation Questions<br>
=============================<br>
<br>
Does every interpreter think that their thread is the "main" thread?<br>
------------------------------<wbr>------------------------------<wbr>--------<br>
<br>
(This is more of an implementation detail that an issue for the PEP.)<br>
<br>
CPython's interpreter implementation identifies the OS thread in which<br>
it was started as the "main" thread.  The interpreter the has slightly<br>
different behavior depending on if the current thread is the main one<br>
or not.  This presents a problem in cases where "main thread" is meant<br>
to imply "main thread in the main interpreter" [main-thread]_, where<br>
the main interpreter is the initial one.<br>
<br>
Disallow subinterpreters in the main thread?<br>
------------------------------<wbr>--------------<br>
<br>
(This is more of an implementation detail that an issue for the PEP.)<br>
<br>
This is a specific case of the above issue.  Currently in CPython,<br>
"we need a main \*thread\* in order to sensibly manage the way signal<br>
handling works across different platforms".  [main-thread]_<br>
<br>
Since signal handlers are part of the interpreter state, running a<br>
subinterpreter in the main thread means that the main interpreter<br>
can no longer properly handle signals (since it's effectively paused).<br>
<br>
Furthermore, running a subinterpreter in the main thread would<br>
conceivably allow setting signal handlers on that interpreter, which<br>
would likewise impact signal handling when that interpreter isn't<br>
running or is running in a different thread.<br>
<br>
Ultimately, running subinterpreters in the main OS thread introduces<br>
complications to the signal handling implementation.  So it may make<br>
the most sense to disallow running subinterpreters in the main thread.<br>
Support for it could be considered later.  The downside is that folks<br>
wanting to try out subinterpreters would be required to take the extra<br>
step of using threads.  This could slow adoption and experimentation,<br>
whereas without the restriction there's less of an obstacle.<br>
<br>
<br>
Deferred Functionality<br>
======================<br>
<br>
In the interest of keeping this proposal minimal, the following<br>
functionality has been left out for future consideration.  Note that<br>
this is not a judgement against any of said capability, but rather a<br>
deferment.  That said, each is arguably valid.<br>
<br>
Interpreter.call()<br>
------------------<br>
<br>
It would be convenient to run existing functions in subinterpreters<br>
directly.  ``Interpreter.run()`` could be adjusted to support this or<br>
a ``call()`` method could be added::<br>
<br>
   Interpreter.call(f, *args, **kwargs)<br>
<br>
This suffers from the same problem as sharing objects between<br>
interpreters via queues.  The minimal solution (running a source string)<br>
is sufficient for us to get the feature out where it can be explored.<br>
<br>
timeout arg to recv() and send()<br>
------------------------------<wbr>--<br>
<br>
Typically functions that have a ``block`` argument also have a<br>
``timeout`` argument.  It sometimes makes sense to do likewise for<br>
functions that otherwise block, like the channel ``recv()`` and<br>
``send()`` methods.  We can add it later if needed.<br>
<br>
get_main()<br>
----------<br>
<br>
CPython has a concept of a "main" interpreter.  This is the initial<br>
interpreter created during CPython's runtime initialization.  It may<br>
be useful to identify the main interpreter.  For instance, the main<br>
interpreter should not be destroyed.  However, for the basic<br>
functionality of a high-level API a ``get_main()`` function is not<br>
necessary.  Furthermore, there is no requirement that a Python<br>
implementation have a concept of a main interpreter.  So until there's<br>
a clear need we'll leave ``get_main()`` out.<br>
<br>
Interpreter.run_in_thread()<br>
---------------------------<br>
<br>
This method would make a ``run()`` call for you in a thread.  Doing this<br>
using only ``threading.Thread`` and ``run()`` is relatively trivial so<br>
we've left it out.<br>
<br>
Synchronization Primitives<br>
--------------------------<br>
<br>
The ``threading`` module provides a number of synchronization primitives<br>
for coordinating concurrent operations.  This is especially necessary<br>
due to the shared-state nature of threading.  In contrast,<br>
subinterpreters do not share state.  Data sharing is restricted to<br>
channels, which do away with the need for explicit synchronization.  If<br>
any sort of opt-in shared state support is added to subinterpreters in<br>
the future, that same effort can introduce synchronization primitives<br>
to meet that need.<br>
<br>
CSP Library<br>
-----------<br>
<br>
A ``csp`` module would not be a large step away from the functionality<br>
provided by this PEP.  However, adding such a module is outside the<br>
minimalist goals of this proposal.<br>
<br>
Syntactic Support<br>
-----------------<br>
<br>
The ``Go`` language provides a concurrency model based on CSP, so<br>
it's similar to the concurrency model that subinterpreters support.<br>
``Go`` provides syntactic support, as well several builtin concurrency<br>
primitives, to make concurrency a first-class feature.  Conceivably,<br>
similar syntactic (and builtin) support could be added to Python using<br>
subinterpreters.  However, that is *way* outside the scope of this PEP!<br>
<br>
Multiprocessing<br>
---------------<br>
<br>
The ``multiprocessing`` module could support subinterpreters in the same<br>
way it supports threads and processes.  In fact, the module's<br>
maintainer, Davin Potts, has indicated this is a reasonable feature<br>
request.  However, it is outside the narrow scope of this PEP.<br>
<br>
C-extension opt-in/opt-out<br>
--------------------------<br>
<br>
By using the ``PyModuleDef_Slot`` introduced by PEP 489, we could easily<br>
add a mechanism by which C-extension modules could opt out of support<br>
for subinterpreters.  Then the import machinery, when operating in<br>
a subinterpreter, would need to check the module for support.  It would<br>
raise an ImportError if unsupported.<br>
<br>
Alternately we could support opting in to subinterpreter support.<br>
However, that would probably exclude many more modules (unnecessarily)<br>
than the opt-out approach.<br>
<br>
The scope of adding the ModuleDef slot and fixing up the import<br>
machinery is non-trivial, but could be worth it.  It all depends on<br>
how many extension modules break under subinterpreters.  Given the<br>
relatively few cases we know of through mod_wsgi, we can leave this<br>
for later.<br>
<br>
Poisoning channels<br>
------------------<br>
<br>
CSP has the concept of poisoning a channel.  Once a channel has been<br>
poisoned, and ``send()`` or ``recv()`` call on it will raise a special<br>
exception, effectively ending execution in the interpreter that tried<br>
to use the poisoned channel.<br>
<br>
This could be accomplished by adding a ``poison()`` method to both ends<br>
of the channel.  The ``close()`` method could work if it had a ``force``<br>
option to force the channel closed.  Regardless, these semantics are<br>
relatively specialized and can wait.<br>
<br>
Sending channels over channels<br>
------------------------------<br>
<br>
Some advanced usage of subinterpreters could take advantage of the<br>
ability to send channels over channels, in addition to bytes.  Given<br>
that channels will already be multi-interpreter safe, supporting then<br>
in ``RecvChannel.recv()`` wouldn't be a big change.  However, this can<br>
wait until the basic functionality has been ironed out.<br>
<br>
Reseting __main__<br>
-----------------<br>
<br>
As proposed, every call to ``Interpreter.run()`` will execute in the<br>
namespace of the interpreter's existing ``__main__`` module.  This means<br>
that data persists there between ``run()`` calls.  Sometimes this isn't<br>
desireable and you want to execute in a fresh ``__main__``.  Also,<br>
you don't necessarily want to leak objects there that you aren't using<br>
any more.<br>
<br>
Note that the following won't work right because it will clear too much<br>
(e.g. ``__name__`` and the other "__dunder__" attributes::<br>
<br>
   interp.run('globals().clear()'<wbr>)<br>
<br>
Possible solutions include:<br>
<br>
* a ``create()`` arg to indicate resetting ``__main__`` after each<br>
  ``run`` call<br>
* an ``Interpreter.reset_main`` flag to support opting in or out<br>
  after the fact<br>
* an ``Interpreter.reset_main()`` method to opt in when desired<br>
* ``importlib.util.reset_<wbr>globals()`` [reset_globals]_<br>
<br>
Also note that reseting ``__main__`` does nothing about state stored<br>
in other modules.  So any solution would have to be clear about the<br>
scope of what is being reset.  Conceivably we could invent a mechanism<br>
by which any (or every) module could be reset, unlike ``reload()``<br>
which does not clear the module before loading into it.  Regardless,<br>
since ``__main__`` is the execution namespace of the interpreter,<br>
resetting it has a much more direct correlation to interpreters and<br>
their dynamic state than does resetting other modules.  So a more<br>
generic module reset mechanism may prove unnecessary.<br>
<br>
This isn't a critical feature initially.  It can wait until later<br>
if desirable.<br>
<br>
Support passing ints in channels<br>
------------------------------<wbr>--<br>
<br>
Passing ints around should be fine and ultimately is probably<br>
desirable.  However, we can get by with serializing them as bytes<br>
for now.  The goal is a minimal API for the sake of basic<br>
functionality at first.<br>
<br>
File descriptors and sockets in channels<br>
------------------------------<wbr>----------<br>
<br>
Given that file descriptors and sockets are process-global resources,<br>
support for passing them through channels is a reasonable idea.  They<br>
would be a good candidate for the first effort at expanding the types<br>
that channels support.  They aren't strictly necessary for the initial<br>
API.<br>
<br>
Integration with async<br>
----------------------<br>
<br>
Per Antoine Pitrou [async]_::<br>
<br>
   Has any thought been given to how FIFOs could integrate with async<br>
   code driven by an event loop (e.g. asyncio)?  I think the model of<br>
   executing several asyncio (or Tornado) applications each in their<br>
   own subinterpreter may prove quite interesting to reconcile multi-<br>
   core concurrency with ease of programming.  That would require the<br>
   FIFOs to be able to synchronize on something an event loop can wait<br>
   on (probably a file descriptor?).<br>
<br>
A possible solution is to provide async implementations of the blocking<br>
channel methods (``__next__()``, ``recv()``, and ``send()``).  However,<br>
the basic functionality of subinterpreters does not depend on async and<br>
can be added later.<br>
<br>
Support for iteration<br>
---------------------<br>
<br>
Supporting iteration on ``RecvChannel`` (via ``__iter__()`` or<br>
``_next__()``) may be useful.  A trivial implementation would use the<br>
``recv()`` method, similar to how files do iteration.  Since this isn't<br>
a fundamental capability and has a simple analog, adding iteration<br>
support can wait until later.<br>
<br>
Channel context managers<br>
------------------------<br>
<br>
Context manager support on ``RecvChannel`` and ``SendChannel`` may be<br>
helpful.  The implementation would be simple, wrapping a call to<br>
``close()`` like files do.  As with iteration, this can wait.<br>
<br>
Pipes and Queues<br>
----------------<br>
<br>
With the proposed object passing machanism of "channels", other similar<br>
basic types aren't required to achieve the minimal useful functionality<br>
of subinterpreters.  Such types include pipes (like channels, but<br>
one-to-one) and queues (like channels, but buffered).  See below in<br>
`Rejected Ideas` for more information.<br>
<br>
Even though these types aren't part of this proposal, they may still<br>
be useful in the context of concurrency.  Adding them later is entirely<br>
reasonable.  The could be trivially implemented as wrappers around<br>
channels.  Alternatively they could be implemented for efficiency at the<br>
same low level as channels.<br>
<br>
interpreters.RunFailedError<br>
---------------------------<br>
<br>
As currently proposed, ``Interpreter.run()`` offers you no way to<br>
distinguish an error coming from the subinterpreter from any other<br>
error in the current interpreter.  Your only option would be to<br>
explicitly wrap your ``run()`` call in a<br>
``try: ... except RuntimeError:`` (since we wrap a proxy of the original<br>
exception in a RuntimeError and raise that).<br>
<br>
If this is a problem in practice then would could add something like<br>
``interpreters.RunFailedError`<wbr>` (subclassing RuntimeError) and raise that<br>
in ``run()``.<br>
<br>
Return a lock from send()<br>
-------------------------<br>
<br>
When sending an object through a channel, you don't have a way of knowing<br>
when the object gets received on the other end.  One way to work around<br>
this is to return a locked ``threading.Lock`` from ``SendChannel.send()``<br>
that unlocks once the object is received.<br>
<br>
This matters for buffered channels (i.e. queues).  For unbuffered<br>
channels it is a non-issue.  So this can be dealt with once channels<br>
support buffering.<br>
<br>
<br>
Rejected Ideas<br>
==============<br>
<br>
Explicit channel association<br>
----------------------------<br>
<br>
Interpreters are implicitly associated with channels upon ``recv()`` and<br>
``send()`` calls.  They are de-associated with ``close()`` calls.  The<br>
alternative would be explicit methods.  It would be either<br>
``add_channel()`` and ``remove_channel()`` methods on ``Interpreter``<br>
objects or something similar on channel objects.<br>
<br>
In practice, this level of management shouldn't be necessary for users.<br>
So adding more explicit support would only add clutter to the API.<br>
<br>
Use pipes instead of channels<br>
-----------------------------<br>
<br>
A pipe would be a simplex FIFO between exactly two interpreters.  For<br>
most use cases this would be sufficient.  It could potentially simplify<br>
the implementation as well.  However, it isn't a big step to supporting<br>
a many-to-many simplex FIFO via channels.  Also, with pipes the API<br>
ends up being slightly more complicated, requiring naming the pipes.<br>
<br>
Use queues instead of channels<br>
------------------------------<br>
<br>
The main difference between queues and channels is that queues support<br>
buffering.  This would complicate the blocking semantics of ``recv()``<br>
and ``send()``.  Also, queues can be built on top of channels.<br>
<br>
"enumerate"<br>
-----------<br>
<br>
The ``list_all()`` function provides the list of all interpreters.<br>
In the threading module, which partly inspired the proposed API, the<br>
function is called ``enumerate()``.  The name is different here to<br>
avoid confusing Python users that are not already familiar with the<br>
threading API.  For them "enumerate" is rather unclear, whereas<br>
"list_all" is clear.<br>
<br>
Alternate solutions to prevent leaking exceptions across interpreters<br>
------------------------------<wbr>------------------------------<wbr>---------<br>
<br>
In function calls, uncaught exceptions propagate to the calling frame.<br>
The same approach could be taken with ``run()``.  However, this would<br>
mean that exception objects would leak across the inter-interpreter<br>
boundary.  Likewise, the frames in the traceback would potentially leak.<br>
<br>
While that might not be a problem currently, it would be a problem once<br>
interpreters get better isolation relative to memory management (which<br>
is necessary to stop sharing the GIL between interpreters).  We've<br>
resolved the semantics of how the exceptions propagate by raising a<br>
RuntimeError instead, which wraps a safe proxy for the original<br>
exception and traceback.<br>
<br>
Rejected possible solutions:<br>
<br>
* set the RuntimeError's __cause__ to the proxy of the original<br>
  exception<br>
* reproduce the exception and traceback in the original interpreter<br>
  and raise that.<br>
* convert at the boundary (a la ``subprocess.<wbr>CalledProcessError``)<br>
  (requires a cross-interpreter representation)<br>
* support customization via ``Interpreter.excepthook``<br>
  (requires a cross-interpreter representation)<br>
* wrap in a proxy at the boundary (including with support for<br>
  something like ``err.raise()`` to propagate the traceback).<br>
* return the exception (or its proxy) from ``run()`` instead of<br>
  raising it<br>
* return a result object (like ``subprocess`` does) [result-object]_<br>
  (unecessary complexity?)<br>
* throw the exception away and expect users to deal with unhandled<br>
  exceptions explicitly in the script they pass to ``run()``<br>
  (they can pass error info out via channels); with threads you have<br>
  to do something similar<br>
<br>
<br>
References<br>
==========<br>
<br>
.. [c-api]<br>
   <a href="https://docs.python.org/3/c-api/init.html#sub-interpreter-support" rel="noreferrer" target="_blank">https://docs.python.org/3/c-<wbr>api/init.html#sub-interpreter-<wbr>support</a><br>
<br>
.. _Communicating Sequential Processes:<br>
<br>
.. [CSP]<br>
   <a href="https://en.wikipedia.org/wiki/Communicating_sequential_processes" rel="noreferrer" target="_blank">https://en.wikipedia.org/wiki/<wbr>Communicating_sequential_<wbr>processes</a><br>
   <a href="https://github.com/futurecore/python-csp" rel="noreferrer" target="_blank">https://github.com/futurecore/<wbr>python-csp</a><br>
<br>
.. [fifo]<br>
   <a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Pipe" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/multiprocessing.html#<wbr>multiprocessing.Pipe</a><br>
   <a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/multiprocessing.html#<wbr>multiprocessing.Queue</a><br>
   <a href="https://docs.python.org/3/library/queue.html#module-queue" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/queue.html#module-<wbr>queue</a><br>
   <a href="http://stackless.readthedocs.io/en/2.7-slp/library/stackless/channels.html" rel="noreferrer" target="_blank">http://stackless.readthedocs.<wbr>io/en/2.7-slp/library/<wbr>stackless/channels.html</a><br>
   <a href="https://golang.org/doc/effective_go.html#sharing" rel="noreferrer" target="_blank">https://golang.org/doc/<wbr>effective_go.html#sharing</a><br>
   <a href="http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/" rel="noreferrer" target="_blank">http://www.jtolds.com/writing/<wbr>2016/03/go-channels-are-bad-<wbr>and-you-should-feel-bad/</a><br>
<br>
.. [caveats]<br>
   <a href="https://docs.python.org/3/c-api/init.html#bugs-and-caveats" rel="noreferrer" target="_blank">https://docs.python.org/3/c-<wbr>api/init.html#bugs-and-caveats</a><br>
<br>
.. [petr-c-ext]<br>
   <a href="https://mail.python.org/pipermail/import-sig/2016-June/001062.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/import-sig/2016-<wbr>June/001062.html</a><br>
   <a href="https://mail.python.org/pipermail/python-ideas/2016-April/039748.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2016-<wbr>April/039748.html</a><br>
<br>
.. [cryptography]<br>
   <a href="https://github.com/pyca/cryptography/issues/2299" rel="noreferrer" target="_blank">https://github.com/pyca/<wbr>cryptography/issues/2299</a><br>
<br>
.. [global-gc]<br>
   <a href="http://bugs.python.org/issue24554" rel="noreferrer" target="_blank">http://bugs.python.org/<wbr>issue24554</a><br>
<br>
.. [gilstate]<br>
   <a href="https://bugs.python.org/issue10915" rel="noreferrer" target="_blank">https://bugs.python.org/<wbr>issue10915</a><br>
   <a href="http://bugs.python.org/issue15751" rel="noreferrer" target="_blank">http://bugs.python.org/<wbr>issue15751</a><br>
<br>
.. [global-atexit]<br>
   <a href="https://bugs.python.org/issue6531" rel="noreferrer" target="_blank">https://bugs.python.org/<wbr>issue6531</a><br>
<br>
.. [mp-conn]<br>
   <a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Connection" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/multiprocessing.html#<wbr>multiprocessing.Connection</a><br>
<br>
.. [bug-rate]<br>
   <a href="https://mail.python.org/pipermail/python-ideas/2017-September/047094.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2017-<wbr>September/047094.html</a><br>
<br>
.. [benefits]<br>
   <a href="https://mail.python.org/pipermail/python-ideas/2017-September/047122.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2017-<wbr>September/047122.html</a><br>
<br>
.. [main-thread]<br>
   <a href="https://mail.python.org/pipermail/python-ideas/2017-September/047144.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2017-<wbr>September/047144.html</a><br>
   <a href="https://mail.python.org/pipermail/python-dev/2017-September/149566.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-dev/2017-<wbr>September/149566.html</a><br>
<br>
.. [reset_globals]<br>
   <a href="https://mail.python.org/pipermail/python-dev/2017-September/149545.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-dev/2017-<wbr>September/149545.html</a><br>
<br>
.. [async]<br>
   <a href="https://mail.python.org/pipermail/python-dev/2017-September/149420.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-dev/2017-<wbr>September/149420.html</a><br>
   <a href="https://mail.python.org/pipermail/python-dev/2017-September/149585.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-dev/2017-<wbr>September/149585.html</a><br>
<br>
.. [result-object]<br>
   <a href="https://mail.python.org/pipermail/python-dev/2017-September/149562.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-dev/2017-<wbr>September/149562.html</a><br>
<br>
.. [jython]<br>
   <a href="https://mail.python.org/pipermail/python-ideas/2017-May/045771.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2017-<wbr>May/045771.html</a><br>
<br>
.. [pypy]<br>
   <a href="https://mail.python.org/pipermail/python-ideas/2017-September/046973.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/python-ideas/2017-<wbr>September/046973.html</a><br>
<br>
<br>
Copyright<br>
=========<br>
<br>
This document has been placed in the public domain.<br>
______________________________<wbr>_________________<br>
Python-Dev mailing list<br>
<a href="mailto:Python-Dev@python.org">Python-Dev@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-dev" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/python-dev</a><br>
Unsubscribe: <a href="https://mail.python.org/mailman/options/python-dev/guido%40python.org" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/options/python-dev/<wbr>guido%40python.org</a><br>
</blockquote></div><br><br clear="all"><br>-- <br><div class="gmail_signature" data-smartmail="gmail_signature">--Guido van Rossum (<a href="http://python.org/~guido" target="_blank">python.org/~guido</a>)</div>
</div>