Show More
@@ -0,0 +1,149 b'' | |||||
|
1 | ======================================== | |||
|
2 | Design proposal for mod:`IPython.core` | |||
|
3 | ======================================== | |||
|
4 | ||||
|
5 | Currently mod:`IPython.core` is not well suited for use in GUI | |||
|
6 | applications. The purpose of this document is to describe a design that will | |||
|
7 | resolve this limitation. | |||
|
8 | ||||
|
9 | Process and thread model | |||
|
10 | ======================== | |||
|
11 | ||||
|
12 | The design described here is based on a two process model. These two processes | |||
|
13 | are: | |||
|
14 | ||||
|
15 | 1. The IPython engine/kernel. This process contains the user's namespace and is | |||
|
16 | responsible for executing user code. If user code uses | |||
|
17 | :mod:`enthought.traits` or uses a GUI toolkit to perform plotting, the GUI | |||
|
18 | event loop will run in this process. | |||
|
19 | ||||
|
20 | 2. The GUI application. The user facing GUI application will run in a second | |||
|
21 | process that communicates directly with the IPython engine using a suitable | |||
|
22 | RPC mechanism. The GUI application will not execute any user code. The | |||
|
23 | canonical example of a GUI application that talks to the IPython engine, | |||
|
24 | would be a GUI based IPython terminal. However, the GUI application could | |||
|
25 | provide a more sophisticated interface such as a notebook. | |||
|
26 | ||||
|
27 | We now describe the treading model of the IPython engine. Two threads will be | |||
|
28 | used to implement the IPython engine: a main thread that executes user code and | |||
|
29 | a networking thread that communicates with the outside world. This specific | |||
|
30 | design is required by a number of different factors. | |||
|
31 | ||||
|
32 | First, The IPython engine must run the GUI event loop if the user wants to | |||
|
33 | perform interactive plotting. Because of the design of most GUIs, this means | |||
|
34 | that the user code (which will make GUI calls) must live in the main thread. | |||
|
35 | ||||
|
36 | Second, networking code in the engine (Twisted or otherwise) must be able to | |||
|
37 | communicate with the outside world while user code runs. An example would be if | |||
|
38 | user code does the following:: | |||
|
39 | ||||
|
40 | import time | |||
|
41 | for i in range(10): | |||
|
42 | print i | |||
|
43 | time.sleep(2) | |||
|
44 | ||||
|
45 | We would like to result of each ``print i`` to be seen by the GUI application | |||
|
46 | before the entire code block completes. We call this asynchronous printing. | |||
|
47 | For this to be possible, the networking code has to be able to be able to | |||
|
48 | communicate the value of ``stdout`` to the GUI application while user code is | |||
|
49 | run. Another example is using :mod:`IPython.kernel.client` in user code to | |||
|
50 | perform a parallel computation by talking to an IPython controller and a set of | |||
|
51 | engines (these engines are separate from the one we are discussing here). This | |||
|
52 | module requires the Twisted event loop to be run in a different thread than | |||
|
53 | user code. | |||
|
54 | ||||
|
55 | For the GUI application, threads are optional. However, the GUI application | |||
|
56 | does need to be able to perform network communications asynchronously (without | |||
|
57 | blocking the GUI itself). With this in mind, there are two options: | |||
|
58 | ||||
|
59 | * Use Twisted (or another non-blocking socket library) in the same thread as | |||
|
60 | the GUI event loop. | |||
|
61 | ||||
|
62 | * Don't use Twisted, but instead run networking code in the GUI application | |||
|
63 | using blocking sockets in threads. This would require the usage of polling | |||
|
64 | and queues to manage the networking in the GUI application. | |||
|
65 | ||||
|
66 | Thus, for the GUI application, there is a choice between non-blocking sockets | |||
|
67 | (Twisted) or threads. | |||
|
68 | ||||
|
69 | Interprocess communication | |||
|
70 | ========================== | |||
|
71 | ||||
|
72 | The GUI application will use interprocess communication (IPC) to communicate | |||
|
73 | with the networking thread of the engine. Because this communication will | |||
|
74 | typically happen over localhost, a simple, one way, non-secure protocol like | |||
|
75 | XML-RPC or JSON-RPC can be used. These options will also make it easy to | |||
|
76 | implement the required networking in the GUI application using the standard | |||
|
77 | library. In applications where secure communications are required, Twisted and | |||
|
78 | Foolscap will probably be the best way to go for now. | |||
|
79 | ||||
|
80 | Using this communication channel, the GUI application will be able to perform | |||
|
81 | the following actions with the engine: | |||
|
82 | ||||
|
83 | * Pass code (as a string) to be executed by the engine in the user's namespace | |||
|
84 | as a string. | |||
|
85 | ||||
|
86 | * Get the current value of stdout and stderr. | |||
|
87 | ||||
|
88 | * Pass a string to the engine to be completed when the GUI application | |||
|
89 | receives a tab completion event. | |||
|
90 | ||||
|
91 | * Get a list of all variable names in the user's namespace. | |||
|
92 | ||||
|
93 | * Other similar actions. | |||
|
94 | ||||
|
95 | Engine details | |||
|
96 | ============== | |||
|
97 | ||||
|
98 | As discussed above, the engine will consist of two threads: a main thread and a | |||
|
99 | networking thread. These two threads will communicate using a pair of queues: | |||
|
100 | one for data and requests passing to the main thread (the main thread's "input | |||
|
101 | queue") and another for data and requests passing out of the main thread (the | |||
|
102 | main thread's "output queue"). Both threads will have an event loop that will | |||
|
103 | enqueue elements on one queue and dequeue elements on the other queue. | |||
|
104 | ||||
|
105 | The event loop of the main thread will be of a different nature depending on if | |||
|
106 | the user wants to perform interactive plotting. If they do want to perform | |||
|
107 | interactive plotting, the main threads event loop will simply be the GUI event | |||
|
108 | loop. In that case, GUI timers will be used to monitor the main threads input | |||
|
109 | queue. When elements appear on that queue, the main thread will respond | |||
|
110 | appropriately. For example, if the queue contains an element that consists of | |||
|
111 | user code to execute, the main thread will call the appropriate method of its | |||
|
112 | IPython instance. If the user does not want to perform interactive plotting, | |||
|
113 | the main thread will have a simpler event loop that will simply block on the | |||
|
114 | input queue. When something appears on that queue, the main thread will awake | |||
|
115 | and handle the request. | |||
|
116 | ||||
|
117 | The event loop of the networking thread will typically be the Twisted event | |||
|
118 | loop. While it is possible to implement the engine's networking without using | |||
|
119 | Twisted, at this point, Twisted provides the best solution. Note that the GUI | |||
|
120 | application does not need to use Twisted in this case. The Twisted event loop | |||
|
121 | will contain an XML-RPC or JSON-RPC server that takes requests over the network | |||
|
122 | and handles those requests by enqueing elements on the main thread's input | |||
|
123 | queue or dequeing elements on the main thread's output queue. | |||
|
124 | ||||
|
125 | Because of the asynchronous nature of the network communication, a single input | |||
|
126 | and output queue will be used to handle the interaction with the main | |||
|
127 | thread. It is also possible to use multiple queues to isolate the different | |||
|
128 | types of requests, but our feeling is that this is more complicated than it | |||
|
129 | needs to be. | |||
|
130 | ||||
|
131 | One of the main issues is how stdout/stderr will be handled. Our idea is to | |||
|
132 | replace sys.stdout/sys.stderr by custom classes that will immediately write | |||
|
133 | data to the main thread's output queue when user code writes to these streams | |||
|
134 | (by doing print). Once on the main thread's output queue, the networking thread | |||
|
135 | will make the data available to the GUI application over the network. | |||
|
136 | ||||
|
137 | One unavoidable limitation in this design is that if user code does a print and | |||
|
138 | then enters non-GIL-releasing extension code, the networking thread will go | |||
|
139 | silent until the GIL is again released. During this time, the networking thread | |||
|
140 | will not be able to process the GUI application's requests of the engine. Thus, | |||
|
141 | the values of stdout/stderr will be unavailable during this time. This goes | |||
|
142 | beyond stdout/stderr, however. Anytime the main thread is holding the GIL, the | |||
|
143 | networking thread will go silent and be unable to handle requests. | |||
|
144 | ||||
|
145 | Refactoring of IPython.core | |||
|
146 | =========================== | |||
|
147 | ||||
|
148 | We need to go through IPython.core and describe what specifically needs to be | |||
|
149 | done. |
General Comments 0
You need to be logged in to leave comments.
Login now