11"""For running Python code that could interrupt itself at any time in order to,
22for example, ask for a read on stdin, or a write on stdout
33
4- The CodeRunner spawns a greenlet to run code in, and that code can suspend its
5- own execution to ask the main greenlet to refresh the display or get
4+ The CodeRunner spawns a thread to run code in, and that code can block
5+ on a queue to ask the main (UI) thread to refresh the display or get
66information.
7-
8- Greenlets are basically threads that can explicitly switch control to each
9- other. You can replace the word "greenlet" with "thread" in these docs if that
10- makes more sense to you.
117"""
128
139import code
14- import greenlet
10+ from six .moves import queue
11+ import threading
1512import logging
1613import signal
1714
2118
2219
2320class SigintHappened :
24- """If this class is returned, a SIGINT happened while the main greenlet """
21+ """If this class is returned, a SIGINT happened while the main thread """
2522
2623
2724class SystemExitFromCodeRunner (SystemExit ):
2825 """If this class is returned, a SystemExit happened while in the code
29- greenlet """
26+ thread """
3027
3128
3229class RequestFromCodeRunner :
@@ -61,7 +58,8 @@ class CodeRunner:
6158
6259 Running code requests a refresh by calling
6360 request_from_main_context(force_refresh=True), which
64- suspends execution of the code and switches back to the main greenlet
61+ suspends execution of the code by blocking on a queue
62+ that the main thread was blocked on.
6563
6664 After load_code() is called with the source code to be run,
6765 the run_code() method should be called to start running the code.
@@ -77,10 +75,10 @@ class CodeRunner:
7775 has been gathered, run_code() should be called again, passing in any
7876 requested user input. This continues until run_code returns Done.
7977
80- The code greenlet is responsible for telling the main greenlet
78+ The code thread is responsible for telling the main thread
8179 what it wants returned in the next run_code call - CodeRunner
8280 just passes whatever is passed in to run_code(for_code) to the
83- code greenlet
81+ code thread.
8482 """
8583
8684 def __init__ (self , interp = None , request_refresh = lambda : None ):
@@ -93,64 +91,68 @@ def __init__(self, interp=None, request_refresh=lambda: None):
9391 """
9492 self .interp = interp or code .InteractiveInterpreter ()
9593 self .source = None
96- self .main_context = greenlet .getcurrent ()
97- self .code_context = None
94+ self .code_thread = None
95+ self .requests_from_code_thread = queue .Queue (maxsize = 0 )
96+ self .responses_for_code_thread = queue .Queue ()
9897 self .request_refresh = request_refresh
9998 # waiting for response from main thread
10099 self .code_is_waiting = False
101100 # sigint happened while in main thread
102- self .sigint_happened_in_main_context = False
101+ self .sigint_happened_in_main_context = False # TODO rename context to thread
103102 self .orig_sigint_handler = None
104103
105104 @property
106105 def running (self ):
107- """Returns greenlet if code has been loaded greenlet has been
108- started"""
109- return self .source and self .code_context
106+ """Returns the running thread if code has been loaded and started."""
107+ return self .source and self .code_thread
110108
111109 def load_code (self , source ):
112110 """Prep code to be run"""
113111 assert self .source is None , (
114112 "you shouldn't load code when some is " "already running"
115113 )
116114 self .source = source
117- self .code_context = None
115+ self .code_thread = None
118116
119117 def _unload_code (self ):
120118 """Called when done running code"""
121119 self .source = None
122- self .code_context = None
120+ self .code_thread = None
123121 self .code_is_waiting = False
124122
125123 def run_code (self , for_code = None ):
126124 """Returns Truthy values if code finishes, False otherwise
127125
128- if for_code is provided, send that value to the code greenlet
126+ if for_code is provided, send that value to the code thread
129127 if source code is complete, returns "done"
130128 if source code is incomplete, returns "unfinished"
131129 """
132- if self .code_context is None :
130+ if self .code_thread is None :
133131 assert self .source is not None
134- self .code_context = greenlet .greenlet (self ._blocking_run_code )
132+ self .code_thread = threading .Thread (
133+ target = self ._blocking_run_code ,
134+ name = 'codethread' )
135+ self .code_thread .daemon = True
135136 if is_main_thread ():
136137 self .orig_sigint_handler = signal .getsignal (signal .SIGINT )
137138 signal .signal (signal .SIGINT , self .sigint_handler )
138- request = self .code_context . switch ()
139+ self .code_thread . start ()
139140 else :
140141 assert self .code_is_waiting
141142 self .code_is_waiting = False
142143 if is_main_thread ():
143144 signal .signal (signal .SIGINT , self .sigint_handler )
144145 if self .sigint_happened_in_main_context :
145146 self .sigint_happened_in_main_context = False
146- request = self .code_context . switch (SigintHappened )
147+ self .responses_for_code_thread . put (SigintHappened )
147148 else :
148- request = self .code_context . switch (for_code )
149+ self .responses_for_code_thread . put (for_code )
149150
151+ request = self .requests_from_code_thread .get ()
150152 logger .debug ("request received from code was %r" , request )
151153 if not isinstance (request , RequestFromCodeRunner ):
152154 raise ValueError (
153- "Not a valid value from code greenlet : %r" % request
155+ "Not a valid value from code thread : %r" % request
154156 )
155157 if isinstance (request , (Wait , Refresh )):
156158 self .code_is_waiting = True
@@ -170,7 +172,7 @@ def run_code(self, for_code=None):
170172 def sigint_handler (self , * args ):
171173 """SIGINT handler to use while code is running or request being
172174 fulfilled"""
173- if greenlet . getcurrent () is self .code_context :
175+ if threading . current_thread () is self .code_thread :
174176 logger .debug ("sigint while running user code!" )
175177 raise KeyboardInterrupt ()
176178 else :
@@ -184,18 +186,23 @@ def _blocking_run_code(self):
184186 try :
185187 unfinished = self .interp .runsource (self .source )
186188 except SystemExit as e :
187- return SystemExitRequest (* e .args )
188- return Unfinished () if unfinished else Done ()
189+ self .requests_from_code_thread .push (SystemExitRequest (* e .args ))
190+ return
191+ self .requests_from_code_thread .put (Unfinished ()
192+ if unfinished
193+ else Done ())
189194
190195 def request_from_main_context (self , force_refresh = False ):
191196 """Return the argument passed in to .run_code(for_code)
192197
193198 Nothing means calls to run_code must be... ???
194199 """
195200 if force_refresh :
196- value = self .main_context .switch (Refresh ())
201+ self .requests_from_code_thread .put (Refresh ())
202+ value = self .responses_for_code_thread .get ()
197203 else :
198- value = self .main_context .switch (Wait ())
204+ self .requests_from_code_thread .put (Wait ())
205+ value = self .responses_for_code_thread .get ()
199206 if value is SigintHappened :
200207 raise KeyboardInterrupt ()
201208 return value
0 commit comments