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