33"""For running Python code that could interrupt itself at any time in order to,
44for example, ask for a read on stdin, or a write on stdout
55
6- The CodeRunner spawns a greenlet to run code in, and that code can suspend its
7- own execution to ask the main greenlet to refresh the display or get
6+ The CodeRunner spawns a thread to run code in, and that code can block
7+ on a queue to ask the main (UI) thread to refresh the display or get
88information.
9-
10- Greenlets are basically threads that can explicitly switch control to each
11- other. You can replace the word "greenlet" with "thread" in these docs if that
12- makes more sense to you.
139"""
1410
1511import code
1612import signal
17- import greenlet
13+ from six .moves import queue
14+ import threading
1815import logging
1916
2017from bpython ._py3compat import py3
2421
2522
2623class SigintHappened (object ):
27- """If this class is returned, a SIGINT happened while the main greenlet """
24+ """If this class is returned, a SIGINT happened while the main thread """
2825
2926
3027class SystemExitFromCodeRunner (SystemExit ):
3128 """If this class is returned, a SystemExit happened while in the code
32- greenlet """
29+ thread """
3330
3431
3532class RequestFromCodeRunner (object ):
@@ -64,7 +61,8 @@ class CodeRunner(object):
6461
6562 Running code requests a refresh by calling
6663 request_from_main_context(force_refresh=True), which
67- suspends execution of the code and switches back to the main greenlet
64+ suspends execution of the code by blocking on a queue
65+ that the main thread was blocked on.
6866
6967 After load_code() is called with the source code to be run,
7068 the run_code() method should be called to start running the code.
@@ -80,10 +78,10 @@ class CodeRunner(object):
8078 has been gathered, run_code() should be called again, passing in any
8179 requested user input. This continues until run_code returns Done.
8280
83- The code greenlet is responsible for telling the main greenlet
81+ The code thread is responsible for telling the main thread
8482 what it wants returned in the next run_code call - CodeRunner
8583 just passes whatever is passed in to run_code(for_code) to the
86- code greenlet
84+ code thread.
8785 """
8886 def __init__ (self , interp = None , request_refresh = lambda : None ):
8987 """
@@ -95,60 +93,63 @@ def __init__(self, interp=None, request_refresh=lambda: None):
9593 """
9694 self .interp = interp or code .InteractiveInterpreter ()
9795 self .source = None
98- self .main_context = greenlet .getcurrent ()
99- self .code_context = None
96+ self .code_thread = None
97+ self .requests_from_code_thread = queue .Queue (maxsize = 0 )
98+ self .responses_for_code_thread = queue .Queue ()
10099 self .request_refresh = request_refresh
101100 # waiting for response from main thread
102101 self .code_is_waiting = False
103102 # sigint happened while in main thread
104- self .sigint_happened_in_main_context = False
103+ self .sigint_happened_in_main_context = False # TODO rename context to thread
105104 self .orig_sigint_handler = None
106105
107106 @property
108107 def running (self ):
109- """Returns greenlet if code has been loaded greenlet has been
110- started"""
111- return self .source and self .code_context
108+ """Returns the running thread if code has been loaded and started."""
109+ return self .source and self .code_thread
112110
113111 def load_code (self , source ):
114112 """Prep code to be run"""
115113 assert self .source is None , "you shouldn't load code when some is " \
116114 "already running"
117115 self .source = source
118- self .code_context = None
116+ self .code_thread = None
119117
120118 def _unload_code (self ):
121119 """Called when done running code"""
122120 self .source = None
123- self .code_context = None
121+ self .code_thread = None
124122 self .code_is_waiting = False
125123
126124 def run_code (self , for_code = None ):
127125 """Returns Truthy values if code finishes, False otherwise
128126
129- if for_code is provided, send that value to the code greenlet
127+ if for_code is provided, send that value to the code thread
130128 if source code is complete, returns "done"
131129 if source code is incomplete, returns "unfinished"
132130 """
133- if self .code_context is None :
131+ if self .code_thread is None :
134132 assert self .source is not None
135- self .code_context = greenlet .greenlet (self ._blocking_run_code )
133+ self .code_thread = threading .Thread (target = self ._blocking_run_code ,
134+ name = 'codethread' )
135+ self .code_thread .daemon = True
136136 self .orig_sigint_handler = signal .getsignal (signal .SIGINT )
137137 signal .signal (signal .SIGINT , self .sigint_handler )
138- request = self .code_context . switch ()
138+ self .code_thread . start ()
139139 else :
140140 assert self .code_is_waiting
141141 self .code_is_waiting = False
142142 signal .signal (signal .SIGINT , self .sigint_handler )
143143 if self .sigint_happened_in_main_context :
144144 self .sigint_happened_in_main_context = False
145- request = self .code_context . switch (SigintHappened )
145+ self .responses_for_code_thread . put (SigintHappened )
146146 else :
147- request = self .code_context . switch (for_code )
147+ self .responses_for_code_thread . put (for_code )
148148
149+ request = self .requests_from_code_thread .get ()
149150 logger .debug ('request received from code was %r' , request )
150151 if not isinstance (request , RequestFromCodeRunner ):
151- raise ValueError ("Not a valid value from code greenlet : %r" %
152+ raise ValueError ("Not a valid value from code thread : %r" %
152153 request )
153154 if isinstance (request , (Wait , Refresh )):
154155 self .code_is_waiting = True
@@ -167,7 +168,7 @@ def run_code(self, for_code=None):
167168 def sigint_handler (self , * args ):
168169 """SIGINT handler to use while code is running or request being
169170 fulfilled"""
170- if greenlet . getcurrent () is self .code_context :
171+ if threading . current_thread () is self .code_thread :
171172 logger .debug ('sigint while running user code!' )
172173 raise KeyboardInterrupt ()
173174 else :
@@ -179,18 +180,23 @@ def _blocking_run_code(self):
179180 try :
180181 unfinished = self .interp .runsource (self .source )
181182 except SystemExit as e :
182- return SystemExitRequest (e .args )
183- return Unfinished () if unfinished else Done ()
183+ self .requests_from_code_thread .push (SystemExitRequest (e .args ))
184+ return
185+ self .requests_from_code_thread .put (Unfinished ()
186+ if unfinished
187+ else Done ())
184188
185189 def request_from_main_context (self , force_refresh = False ):
186190 """Return the argument passed in to .run_code(for_code)
187191
188192 Nothing means calls to run_code must be... ???
189193 """
190194 if force_refresh :
191- value = self .main_context .switch (Refresh ())
195+ self .requests_from_code_thread .put (Refresh ())
196+ value = self .responses_for_code_thread .get ()
192197 else :
193- value = self .main_context .switch (Wait ())
198+ self .requests_from_code_thread .put (Wait ())
199+ value = self .responses_for_code_thread .get ()
194200 if value is SigintHappened :
195201 raise KeyboardInterrupt ()
196202 return value
0 commit comments