55import threading
66import logging
77
8+ class SigintHappened (object ):
9+ pass
10+
811class CodeRunner (object ):
912 """Runs user code in an interpreter, taking care of stdout/in/err"""
1013 def __init__ (self , interp = None , stuff_a_refresh_request = lambda :None ):
@@ -15,6 +18,8 @@ def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
1518 self .responses_for_code_thread = Queue .Queue (maxsize = 1 )
1619 self .stuff_a_refresh_request = stuff_a_refresh_request
1720 self .code_is_waiting = False
21+ self .sigint_happened = False
22+ self .orig_sigint_handler = None
1823
1924 @property
2025 def running (self ):
@@ -42,24 +47,39 @@ def run_code(self, for_code=None):
4247 assert self .source is not None
4348 self .code_thread = threading .Thread (target = self ._blocking_run_code , name = 'codethread' )
4449 self .code_thread .daemon = True
50+ self .orig_sigint_handler = signal .getsignal (signal .SIGINT )
51+ signal .signal (signal .SIGINT , signal .default_int_handler )
4552 self .code_thread .start ()
4653 else :
4754 assert self .code_is_waiting
4855 self .code_is_waiting = False
49- self .responses_for_code_thread .put (for_code )
56+ signal .signal (signal .SIGINT , signal .default_int_handler )
57+ if self .sigint_happened :
58+ self .sigint_happened = False
59+ self .responses_for_code_thread .put (SigintHappened )
60+ else :
61+ self .responses_for_code_thread .put (for_code )
5062
5163 request = self .requests_from_code_thread .get ()
64+ signal .signal (signal .SIGINT , self .sigint_handler_while_fufilling_code_request )
5265 if request in ['wait' , 'refresh' ]:
5366 self .code_is_waiting = True
5467 if request == 'refresh' :
5568 self .stuff_a_refresh_request ()
5669 return False
5770 elif request in ['done' , 'unfinished' ]:
5871 self ._unload_code ()
72+ if self .orig_sigint_handler :
73+ signal .signal (signal .SIGINT , self .orig_sigint_handler )
74+ self .orig_sigint_handler = None
5975 return request
6076 else :
6177 raise ValueError ("Not a valid request_from_code_thread value: %r" % request )
6278
79+ def sigint_handler_while_fufilling_code_request (self , * args ):
80+ logging .debug ('while fufilling code request sigint handler running!' )
81+ self .sigint_happened = True
82+
6383 def _blocking_run_code (self ):
6484 unfinished = self .interp .runsource (self .source )
6585 self .requests_from_code_thread .put ('unfinished' if unfinished else 'done' )
@@ -70,12 +90,18 @@ def wait_and_get_value(self):
7090 Nothing means calls to run_code must be...
7191 """
7292 self .requests_from_code_thread .put ('wait' )
73- return self .responses_for_code_thread .get ()
93+ value = self .responses_for_code_thread .get ()
94+ if value is SigintHappened :
95+ raise KeyboardInterrupt ()
96+ return value
7497
7598 def refresh_and_get_value (self ):
7699 """Returns the argument passed in to .run_code(for_code) """
77100 self .requests_from_code_thread .put ('refresh' )
78- return self .responses_for_code_thread .get ()
101+ value = self .responses_for_code_thread .get ()
102+ if value is SigintHappened :
103+ raise KeyboardInterrupt ()
104+ return value
79105
80106class FakeOutput (object ):
81107 def __init__ (self , coderunner , please ):
@@ -85,7 +111,7 @@ def write(self, *args, **kwargs):
85111 self .please (* args , ** kwargs )
86112 return self .coderunner .refresh_and_get_value ()
87113
88- if __name__ == '__main__' :
114+ def test_simple () :
89115 orig_stdout = sys .stdout
90116 orig_stderr = sys .stderr
91117 c = CodeRunner (stuff_a_refresh_request = lambda : orig_stdout .flush () or orig_stderr .flush ())
@@ -95,3 +121,18 @@ def write(self, *args, **kwargs):
95121 c .run_code ()
96122 c .run_code ()
97123 c .run_code ()
124+
125+ def test_exception ():
126+ orig_stdout = sys .stdout
127+ orig_stderr = sys .stderr
128+ c = CodeRunner (stuff_a_refresh_request = lambda : orig_stdout .flush () or orig_stderr .flush ())
129+ def ctrlc ():
130+ raise KeyboardInterrupt ()
131+ stdout = FakeOutput (c , lambda x : ctrlc ())
132+ sys .stdout = stdout
133+ c .load_code ('1 + 1' )
134+ c .run_code ()
135+
136+ if __name__ == '__main__' :
137+ test_simple ()
138+
0 commit comments