Skip to content

Commit 2e54cbb

Browse files
threads -> greenlets
also fixed ctrl-c during raw_input bug --HG-- branch : scroll-frontend
1 parent 1934a83 commit 2e54cbb

File tree

4 files changed

+48
-61
lines changed

4 files changed

+48
-61
lines changed

bpython/curtsiesfrontend/coderunner.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import code
2-
import Queue
32
import signal
43
import sys
5-
import threading
4+
import greenlet
65
import logging
76

87
class SigintHappened(object):
@@ -16,54 +15,51 @@ class CodeRunner(object):
1615
def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
1716
self.interp = interp or code.InteractiveInterpreter()
1817
self.source = None
19-
self.code_thread = None
20-
self.requests_from_code_thread = Queue.Queue(maxsize=1)
21-
self.responses_for_code_thread = Queue.Queue(maxsize=1)
18+
self.main_greenlet = greenlet.getcurrent()
19+
self.code_greenlet = None
2220
self.stuff_a_refresh_request = stuff_a_refresh_request
2321
self.code_is_waiting = False
2422
self.sigint_happened = False
2523
self.orig_sigint_handler = None
2624

2725
@property
2826
def running(self):
29-
return self.source and self.code_thread
27+
return self.source and self.code_greenlet
3028

3129
def load_code(self, source):
3230
"""Prep code to be run"""
3331
self.source = source
34-
self.code_thread = None
32+
self.code_greenlet = None
3533

3634
def _unload_code(self):
3735
"""Called when done running code"""
3836
self.source = None
39-
self.code_thread = None
37+
self.code_greenlet = None
4038
self.code_is_waiting = False
4139

4240
def run_code(self, for_code=None):
4341
"""Returns Truthy values if code finishes, False otherwise
4442
45-
if for_code is provided, send that value to the code thread
43+
if for_code is provided, send that value to the code greenlet
4644
if source code is complete, returns "done"
4745
if source code is incomplete, returns "unfinished"
4846
"""
49-
if self.code_thread is None:
47+
if self.code_greenlet is None:
5048
assert self.source is not None
51-
self.code_thread = threading.Thread(target=self._blocking_run_code, name='codethread')
52-
self.code_thread.daemon = True
49+
self.code_greenlet = greenlet.greenlet(self._blocking_run_code)
5350
self.orig_sigint_handler = signal.getsignal(signal.SIGINT)
5451
signal.signal(signal.SIGINT, self.sigint_handler)
55-
self.code_thread.start()
52+
request = self.code_greenlet.switch()
5653
else:
5754
assert self.code_is_waiting
5855
self.code_is_waiting = False
5956
signal.signal(signal.SIGINT, self.sigint_handler)
6057
if self.sigint_happened:
6158
self.sigint_happened = False
62-
self.responses_for_code_thread.put(SigintHappened)
59+
request = self.code_greenlet.switch(SigintHappened)
6360
else:
64-
self.responses_for_code_thread.put(for_code)
61+
request = self.code_greenlet.switch(for_code)
6562

66-
request = self.requests_from_code_thread.get()
6763
if request in ['wait', 'refresh']:
6864
self.code_is_waiting = True
6965
if request == 'refresh':
@@ -78,10 +74,10 @@ def run_code(self, for_code=None):
7874
self._unload_code()
7975
raise SystemExitFromCodeThread()
8076
else:
81-
raise ValueError("Not a valid request_from_code_thread value: %r" % request)
77+
raise ValueError("Not a valid value from code greenlet: %r" % request)
8278

8379
def sigint_handler(self, *args):
84-
if threading.current_thread() is self.code_thread:
80+
if greenlet.getcurrent() is self.code_greenlet:
8581
logging.debug('sigint while running user code!')
8682
raise KeyboardInterrupt()
8783
else:
@@ -92,25 +88,22 @@ def _blocking_run_code(self):
9288
try:
9389
unfinished = self.interp.runsource(self.source)
9490
except SystemExit:
95-
self.requests_from_code_thread.put('SystemExit')
96-
return
97-
self.requests_from_code_thread.put('unfinished' if unfinished else 'done')
91+
return 'SystemExit'
92+
return 'unfinished' if unfinished else 'done'
9893

9994
def wait_and_get_value(self):
10095
"""Return the argument passed in to .run_code(for_code)
10196
10297
Nothing means calls to run_code must be...
10398
"""
104-
self.requests_from_code_thread.put('wait')
105-
value = self.responses_for_code_thread.get()
99+
value = self.main_greenlet.switch('wait')
106100
if value is SigintHappened:
107101
raise KeyboardInterrupt()
108102
return value
109103

110104
def refresh_and_get_value(self):
111105
"""Returns the argument passed in to .run_code(for_code) """
112-
self.requests_from_code_thread.put('refresh')
113-
value = self.responses_for_code_thread.get()
106+
value = self.main_greenlet.switch('refresh')
114107
if value is SigintHappened:
115108
raise KeyboardInterrupt()
116109
return value

bpython/curtsiesfrontend/interaction.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Queue
1+
import greenlet
22
import time
33

44
from bpython.repl import Interaction as BpythonInteraction
@@ -28,8 +28,8 @@ def __init__(self, initial_message='', permanent_text=""):
2828
self.message_start_time = time.time()
2929
self.message_time = 3
3030
self.permanent_text = permanent_text
31-
self.response_queue = Queue.Queue(maxsize=1)
32-
self.request_or_notify_queue = Queue.Queue()
31+
self.main_greenlet = greenlet.getcurrent()
32+
self.request_greenlet = None
3333

3434
@property
3535
def has_focus(self):
@@ -53,16 +53,17 @@ def process_event(self, e):
5353
elif e == "":
5454
raise SystemExit()
5555
elif self.in_prompt and e in ("\n", "\r"):
56-
self.response_queue.put(self._current_line)
56+
line = self._current_line
5757
self.escape()
58+
self.main_greenlet_switch(line)
5859
elif self.in_confirm:
5960
if e in ('y', 'Y'):
60-
self.response_queue.put(True)
61+
self.request_greenlet.switch(True)
6162
else:
62-
self.response_queue.put(False)
63+
self.request_greenlet.switch(False)
6364
self.escape()
6465
elif e in ['\x1b', '\t', '\x1b\t', '\x1b\x1b']:
65-
self.response_queue.put(False)
66+
self.request_greenlet.switch(False)
6667
self.escape()
6768
else: # add normal character
6869
self._current_line = (self._current_line[:self.cursor_offset_in_line] +
@@ -72,7 +73,6 @@ def process_event(self, e):
7273

7374
def escape(self):
7475
"""unfocus from statusbar, clear prompt state, wait for notify call"""
75-
self.wait_for_request_or_notify()
7676
self.in_prompt = False
7777
self.in_confirm = False
7878
self.prompt = ''
@@ -89,30 +89,23 @@ def current_line(self):
8989
return self._message
9090
return self.permanent_text
9191

92-
def wait_for_request_or_notify(self):
93-
try:
94-
r = self.request_or_notify_queue.get(True, 1)
95-
except Queue.Empty:
96-
raise Exception('Main thread blocked because task thread not calling back')
97-
return r
98-
99-
# interaction interface - should be called from other threads
92+
# interaction interface - should be called from other greenlets
10093
def notify(self, msg, n=3):
94+
self.request_greenlet = greenlet.getcurrent()
10195
self.message_time = n
10296
self.message(msg)
103-
self.request_or_notify_queue.put(msg)
104-
# below Really ought to be called from threads other than the mainloop because they block
97+
self.main_greenlet.switch(msg)
98+
99+
# below Really ought to be called from greenlets other than main because they block
105100
def confirm(self, q):
106101
"""Expected to return True or False, given question prompt q"""
102+
self.request_greenlet = greenlet.getcurrent()
107103
self.prompt = q
108104
self.in_confirm = True
109-
self.request_or_notify_queue.put(q)
110-
return self.response_queue.get()
105+
return self.main_greenlet.switch(q)
111106
def file_prompt(self, s):
112107
"""Expected to return a file name, given """
108+
self.request_greenlet = greenlet.getcurrent()
113109
self.prompt = s.replace('Esc', 'Tab')
114110
self.in_prompt = True
115-
self.request_or_notify_queue.put(s)
116-
r = self.response_queue.get()
117-
return r
118-
111+
return self.main_greenlet.switch(s)

bpython/curtsiesfrontend/repl.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import code
66
import threading
7+
import greenlet
78
import subprocess
89
import tempfile
910

@@ -60,6 +61,9 @@ def process_event(self, e):
6061
#TODO EOF on ctrl-d
6162
elif isinstance(e, events.SigIntEvent):
6263
self.coderunner.sigint_happened = True
64+
self.has_focus = False
65+
self.current_line = ''
66+
self.cursor_offset_in_line = 0
6367
self.repl.run_code_and_maybe_finish()
6468
else: # add normal character
6569
logging.debug('adding normal char %r to current line', e)
@@ -203,7 +207,8 @@ def start_background_tasks(self):
203207
t.start()
204208

205209
def importcompletion_thread(self):
206-
"""quick tasks we want to do bits of during downtime"""
210+
"""task that should run on startup in the background"""
211+
#TODO use locks or something to avoid error on import completion right at startup
207212
while importcompletion.find_coroutine(): # returns None when fully initialized
208213
pass
209214

@@ -292,18 +297,12 @@ def process_event(self, e):
292297
self.undo()
293298
self.update_completion()
294299
elif e in key_dispatch[self.config.save_key]: # ctrl-s for save
295-
t = threading.Thread(target=self.write2file)
296-
t.daemon = True
297-
logging.debug('starting write2file thread')
298-
t.start()
299-
self.interact.wait_for_request_or_notify()
300+
g = greenlet.greenlet(self.write2file)
301+
g.switch()
300302
# F8 for pastebin
301303
elif e in key_dispatch[self.config.pastebin_key]:
302-
t = threading.Thread(target=self.pastebin)
303-
t.daemon = True
304-
logging.debug('starting pastebin thread')
305-
t.start()
306-
self.interact.wait_for_request_or_notify()
304+
g = greenlet.greenlet(self.pastebin)
305+
g.switch()
307306
elif e in key_dispatch[self.config.external_editor_key]:
308307
self.send_to_external_editor()
309308
#TODO add PAD keys hack as in bpython.cli
@@ -482,6 +481,8 @@ def run_code_and_maybe_finish(self, for_code=None):
482481
if err:
483482
indent = 0
484483

484+
#TODO This should be printed ABOVE the error that just happened instead
485+
# or maybe just thrown away and not shown
485486
if self.current_stdouterr_line:
486487
self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width))
487488
self.current_stdouterr_line = ''

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def initialize_options(self):
153153
'pygments'
154154
],
155155
extras_require = {
156-
'curtsies': ['curtsies>=0.0.24'],
156+
'curtsies': ['curtsies>=0.0.24', 'greenlet'],
157157
'urwid' : ['urwid']
158158
},
159159
tests_require = ['mock'],

0 commit comments

Comments
 (0)