1
1
"""For running Python code that could interrupt itself at any time in order to,
2
2
for example, ask for a read on stdin, or a write on stdout
3
3
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
6
6
information.
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.
11
7
"""
12
8
13
9
import code
14
- import greenlet
10
+ from six .moves import queue
11
+ import threading
15
12
import logging
16
13
import signal
17
14
21
18
22
19
23
20
class 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 """
25
22
26
23
27
24
class SystemExitFromCodeRunner (SystemExit ):
28
25
"""If this class is returned, a SystemExit happened while in the code
29
- greenlet """
26
+ thread """
30
27
31
28
32
29
class RequestFromCodeRunner :
@@ -61,7 +58,8 @@ class CodeRunner:
61
58
62
59
Running code requests a refresh by calling
63
60
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.
65
63
66
64
After load_code() is called with the source code to be run,
67
65
the run_code() method should be called to start running the code.
@@ -77,10 +75,10 @@ class CodeRunner:
77
75
has been gathered, run_code() should be called again, passing in any
78
76
requested user input. This continues until run_code returns Done.
79
77
80
- The code greenlet is responsible for telling the main greenlet
78
+ The code thread is responsible for telling the main thread
81
79
what it wants returned in the next run_code call - CodeRunner
82
80
just passes whatever is passed in to run_code(for_code) to the
83
- code greenlet
81
+ code thread.
84
82
"""
85
83
86
84
def __init__ (self , interp = None , request_refresh = lambda : None ):
@@ -93,64 +91,68 @@ def __init__(self, interp=None, request_refresh=lambda: None):
93
91
"""
94
92
self .interp = interp or code .InteractiveInterpreter ()
95
93
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 ()
98
97
self .request_refresh = request_refresh
99
98
# waiting for response from main thread
100
99
self .code_is_waiting = False
101
100
# 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
103
102
self .orig_sigint_handler = None
104
103
105
104
@property
106
105
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
110
108
111
109
def load_code (self , source ):
112
110
"""Prep code to be run"""
113
111
assert self .source is None , (
114
112
"you shouldn't load code when some is " "already running"
115
113
)
116
114
self .source = source
117
- self .code_context = None
115
+ self .code_thread = None
118
116
119
117
def _unload_code (self ):
120
118
"""Called when done running code"""
121
119
self .source = None
122
- self .code_context = None
120
+ self .code_thread = None
123
121
self .code_is_waiting = False
124
122
125
123
def run_code (self , for_code = None ):
126
124
"""Returns Truthy values if code finishes, False otherwise
127
125
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
129
127
if source code is complete, returns "done"
130
128
if source code is incomplete, returns "unfinished"
131
129
"""
132
- if self .code_context is None :
130
+ if self .code_thread is None :
133
131
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
135
136
if is_main_thread ():
136
137
self .orig_sigint_handler = signal .getsignal (signal .SIGINT )
137
138
signal .signal (signal .SIGINT , self .sigint_handler )
138
- request = self .code_context . switch ()
139
+ self .code_thread . start ()
139
140
else :
140
141
assert self .code_is_waiting
141
142
self .code_is_waiting = False
142
143
if is_main_thread ():
143
144
signal .signal (signal .SIGINT , self .sigint_handler )
144
145
if self .sigint_happened_in_main_context :
145
146
self .sigint_happened_in_main_context = False
146
- request = self .code_context . switch (SigintHappened )
147
+ self .responses_for_code_thread . put (SigintHappened )
147
148
else :
148
- request = self .code_context . switch (for_code )
149
+ self .responses_for_code_thread . put (for_code )
149
150
151
+ request = self .requests_from_code_thread .get ()
150
152
logger .debug ("request received from code was %r" , request )
151
153
if not isinstance (request , RequestFromCodeRunner ):
152
154
raise ValueError (
153
- "Not a valid value from code greenlet : %r" % request
155
+ "Not a valid value from code thread : %r" % request
154
156
)
155
157
if isinstance (request , (Wait , Refresh )):
156
158
self .code_is_waiting = True
@@ -170,7 +172,7 @@ def run_code(self, for_code=None):
170
172
def sigint_handler (self , * args ):
171
173
"""SIGINT handler to use while code is running or request being
172
174
fulfilled"""
173
- if greenlet . getcurrent () is self .code_context :
175
+ if threading . current_thread () is self .code_thread :
174
176
logger .debug ("sigint while running user code!" )
175
177
raise KeyboardInterrupt ()
176
178
else :
@@ -184,18 +186,23 @@ def _blocking_run_code(self):
184
186
try :
185
187
unfinished = self .interp .runsource (self .source )
186
188
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 ())
189
194
190
195
def request_from_main_context (self , force_refresh = False ):
191
196
"""Return the argument passed in to .run_code(for_code)
192
197
193
198
Nothing means calls to run_code must be... ???
194
199
"""
195
200
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 ()
197
203
else :
198
- value = self .main_context .switch (Wait ())
204
+ self .requests_from_code_thread .put (Wait ())
205
+ value = self .responses_for_code_thread .get ()
199
206
if value is SigintHappened :
200
207
raise KeyboardInterrupt ()
201
208
return value
0 commit comments