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
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
def __init__ (self , interp = None , request_refresh = lambda : None ):
89
87
"""
@@ -95,60 +93,63 @@ def __init__(self, interp=None, request_refresh=lambda: None):
95
93
"""
96
94
self .interp = interp or code .InteractiveInterpreter ()
97
95
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 ()
100
99
self .request_refresh = request_refresh
101
100
# waiting for response from main thread
102
101
self .code_is_waiting = False
103
102
# 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
105
104
self .orig_sigint_handler = None
106
105
107
106
@property
108
107
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
112
110
113
111
def load_code (self , source ):
114
112
"""Prep code to be run"""
115
113
assert self .source is None , "you shouldn't load code when some is " \
116
114
"already running"
117
115
self .source = source
118
- self .code_context = None
116
+ self .code_thread = None
119
117
120
118
def _unload_code (self ):
121
119
"""Called when done running code"""
122
120
self .source = None
123
- self .code_context = None
121
+ self .code_thread = None
124
122
self .code_is_waiting = False
125
123
126
124
def run_code (self , for_code = None ):
127
125
"""Returns Truthy values if code finishes, False otherwise
128
126
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
130
128
if source code is complete, returns "done"
131
129
if source code is incomplete, returns "unfinished"
132
130
"""
133
- if self .code_context is None :
131
+ if self .code_thread is None :
134
132
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
136
136
self .orig_sigint_handler = signal .getsignal (signal .SIGINT )
137
137
signal .signal (signal .SIGINT , self .sigint_handler )
138
- request = self .code_context . switch ()
138
+ self .code_thread . start ()
139
139
else :
140
140
assert self .code_is_waiting
141
141
self .code_is_waiting = False
142
142
signal .signal (signal .SIGINT , self .sigint_handler )
143
143
if self .sigint_happened_in_main_context :
144
144
self .sigint_happened_in_main_context = False
145
- request = self .code_context . switch (SigintHappened )
145
+ self .responses_for_code_thread . put (SigintHappened )
146
146
else :
147
- request = self .code_context . switch (for_code )
147
+ self .responses_for_code_thread . put (for_code )
148
148
149
+ request = self .requests_from_code_thread .get ()
149
150
logger .debug ('request received from code was %r' , request )
150
151
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" %
152
153
request )
153
154
if isinstance (request , (Wait , Refresh )):
154
155
self .code_is_waiting = True
@@ -167,7 +168,7 @@ def run_code(self, for_code=None):
167
168
def sigint_handler (self , * args ):
168
169
"""SIGINT handler to use while code is running or request being
169
170
fulfilled"""
170
- if greenlet . getcurrent () is self .code_context :
171
+ if threading . current_thread () is self .code_thread :
171
172
logger .debug ('sigint while running user code!' )
172
173
raise KeyboardInterrupt ()
173
174
else :
@@ -179,18 +180,23 @@ def _blocking_run_code(self):
179
180
try :
180
181
unfinished = self .interp .runsource (self .source )
181
182
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 ())
184
188
185
189
def request_from_main_context (self , force_refresh = False ):
186
190
"""Return the argument passed in to .run_code(for_code)
187
191
188
192
Nothing means calls to run_code must be... ???
189
193
"""
190
194
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 ()
192
197
else :
193
- value = self .main_context .switch (Wait ())
198
+ self .requests_from_code_thread .put (Wait ())
199
+ value = self .responses_for_code_thread .get ()
194
200
if value is SigintHappened :
195
201
raise KeyboardInterrupt ()
196
202
return value
0 commit comments