1""" idlelib.run 2 3Simplified, pyshell.ModifiedInterpreter spawns a subprocess with 4f'''{sys.executable} -c "__import__('idlelib.run').run.main()"''' 5'.run' is needed because __import__ returns idlelib, not idlelib.run. 6""" 7import functools 8import io 9import linecache 10import queue 11import sys 12import textwrap 13import time 14import traceback 15import _thread as thread 16import threading 17import warnings 18 19from idlelib import autocomplete # AutoComplete, fetch_encodings 20from idlelib import calltip # Calltip 21from idlelib import debugger_r # start_debugger 22from idlelib import debugobj_r # remote_object_tree_item 23from idlelib import iomenu # encoding 24from idlelib import rpc # multiple objects 25from idlelib import stackviewer # StackTreeItem 26import __main__ 27 28import tkinter # Use tcl and, if startup fails, messagebox. 29if not hasattr(sys.modules['idlelib.run'], 'firstrun'): 30 # Undo modifications of tkinter by idlelib imports; see bpo-25507. 31 for mod in ('simpledialog', 'messagebox', 'font', 32 'dialog', 'filedialog', 'commondialog', 33 'ttk'): 34 delattr(tkinter, mod) 35 del sys.modules['tkinter.' + mod] 36 # Avoid AttributeError if run again; see bpo-37038. 37 sys.modules['idlelib.run'].firstrun = False 38 39LOCALHOST = '127.0.0.1' 40 41 42def idle_formatwarning(message, category, filename, lineno, line=None): 43 """Format warnings the IDLE way.""" 44 45 s = "\nWarning (from warnings module):\n" 46 s += ' File \"%s\", line %s\n' % (filename, lineno) 47 if line is None: 48 line = linecache.getline(filename, lineno) 49 line = line.strip() 50 if line: 51 s += " %s\n" % line 52 s += "%s: %s\n" % (category.__name__, message) 53 return s 54 55def idle_showwarning_subproc( 56 message, category, filename, lineno, file=None, line=None): 57 """Show Idle-format warning after replacing warnings.showwarning. 58 59 The only difference is the formatter called. 60 """ 61 if file is None: 62 file = sys.stderr 63 try: 64 file.write(idle_formatwarning( 65 message, category, filename, lineno, line)) 66 except OSError: 67 pass # the file (probably stderr) is invalid - this warning gets lost. 68 69_warnings_showwarning = None 70 71def capture_warnings(capture): 72 "Replace warning.showwarning with idle_showwarning_subproc, or reverse." 73 74 global _warnings_showwarning 75 if capture: 76 if _warnings_showwarning is None: 77 _warnings_showwarning = warnings.showwarning 78 warnings.showwarning = idle_showwarning_subproc 79 else: 80 if _warnings_showwarning is not None: 81 warnings.showwarning = _warnings_showwarning 82 _warnings_showwarning = None 83 84capture_warnings(True) 85tcl = tkinter.Tcl() 86 87def handle_tk_events(tcl=tcl): 88 """Process any tk events that are ready to be dispatched if tkinter 89 has been imported, a tcl interpreter has been created and tk has been 90 loaded.""" 91 tcl.eval("update") 92 93# Thread shared globals: Establish a queue between a subthread (which handles 94# the socket) and the main thread (which runs user code), plus global 95# completion, exit and interruptable (the main thread) flags: 96 97exit_now = False 98quitting = False 99interruptable = False 100 101def main(del_exitfunc=False): 102 """Start the Python execution server in a subprocess 103 104 In the Python subprocess, RPCServer is instantiated with handlerclass 105 MyHandler, which inherits register/unregister methods from RPCHandler via 106 the mix-in class SocketIO. 107 108 When the RPCServer 'server' is instantiated, the TCPServer initialization 109 creates an instance of run.MyHandler and calls its handle() method. 110 handle() instantiates a run.Executive object, passing it a reference to the 111 MyHandler object. That reference is saved as attribute rpchandler of the 112 Executive instance. The Executive methods have access to the reference and 113 can pass it on to entities that they command 114 (e.g. debugger_r.Debugger.start_debugger()). The latter, in turn, can 115 call MyHandler(SocketIO) register/unregister methods via the reference to 116 register and unregister themselves. 117 118 """ 119 global exit_now 120 global quitting 121 global no_exitfunc 122 no_exitfunc = del_exitfunc 123 #time.sleep(15) # test subprocess not responding 124 try: 125 assert(len(sys.argv) > 1) 126 port = int(sys.argv[-1]) 127 except: 128 print("IDLE Subprocess: no IP port passed in sys.argv.", 129 file=sys.__stderr__) 130 return 131 132 capture_warnings(True) 133 sys.argv[:] = [""] 134 sockthread = threading.Thread(target=manage_socket, 135 name='SockThread', 136 args=((LOCALHOST, port),)) 137 sockthread.daemon = True 138 sockthread.start() 139 while 1: 140 try: 141 if exit_now: 142 try: 143 exit() 144 except KeyboardInterrupt: 145 # exiting but got an extra KBI? Try again! 146 continue 147 try: 148 request = rpc.request_queue.get(block=True, timeout=0.05) 149 except queue.Empty: 150 request = None 151 # Issue 32207: calling handle_tk_events here adds spurious 152 # queue.Empty traceback to event handling exceptions. 153 if request: 154 seq, (method, args, kwargs) = request 155 ret = method(*args, **kwargs) 156 rpc.response_queue.put((seq, ret)) 157 else: 158 handle_tk_events() 159 except KeyboardInterrupt: 160 if quitting: 161 exit_now = True 162 continue 163 except SystemExit: 164 capture_warnings(False) 165 raise 166 except: 167 type, value, tb = sys.exc_info() 168 try: 169 print_exception() 170 rpc.response_queue.put((seq, None)) 171 except: 172 # Link didn't work, print same exception to __stderr__ 173 traceback.print_exception(type, value, tb, file=sys.__stderr__) 174 exit() 175 else: 176 continue 177 178def manage_socket(address): 179 for i in range(3): 180 time.sleep(i) 181 try: 182 server = MyRPCServer(address, MyHandler) 183 break 184 except OSError as err: 185 print("IDLE Subprocess: OSError: " + err.args[1] + 186 ", retrying....", file=sys.__stderr__) 187 socket_error = err 188 else: 189 print("IDLE Subprocess: Connection to " 190 "IDLE GUI failed, exiting.", file=sys.__stderr__) 191 show_socket_error(socket_error, address) 192 global exit_now 193 exit_now = True 194 return 195 server.handle_request() # A single request only 196 197def show_socket_error(err, address): 198 "Display socket error from manage_socket." 199 import tkinter 200 from tkinter.messagebox import showerror 201 root = tkinter.Tk() 202 fix_scaling(root) 203 root.withdraw() 204 showerror( 205 "Subprocess Connection Error", 206 f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n" 207 f"Fatal OSError #{err.errno}: {err.strerror}.\n" 208 "See the 'Startup failure' section of the IDLE doc, online at\n" 209 "https://docs.python.org/3/library/idle.html#startup-failure", 210 parent=root) 211 root.destroy() 212 213def print_exception(): 214 import linecache 215 linecache.checkcache() 216 flush_stdout() 217 efile = sys.stderr 218 typ, val, tb = excinfo = sys.exc_info() 219 sys.last_type, sys.last_value, sys.last_traceback = excinfo 220 seen = set() 221 222 def print_exc(typ, exc, tb): 223 seen.add(id(exc)) 224 context = exc.__context__ 225 cause = exc.__cause__ 226 if cause is not None and id(cause) not in seen: 227 print_exc(type(cause), cause, cause.__traceback__) 228 print("\nThe above exception was the direct cause " 229 "of the following exception:\n", file=efile) 230 elif (context is not None and 231 not exc.__suppress_context__ and 232 id(context) not in seen): 233 print_exc(type(context), context, context.__traceback__) 234 print("\nDuring handling of the above exception, " 235 "another exception occurred:\n", file=efile) 236 if tb: 237 tbe = traceback.extract_tb(tb) 238 print('Traceback (most recent call last):', file=efile) 239 exclude = ("run.py", "rpc.py", "threading.py", "queue.py", 240 "debugger_r.py", "bdb.py") 241 cleanup_traceback(tbe, exclude) 242 traceback.print_list(tbe, file=efile) 243 lines = traceback.format_exception_only(typ, exc) 244 for line in lines: 245 print(line, end='', file=efile) 246 247 print_exc(typ, val, tb) 248 249def cleanup_traceback(tb, exclude): 250 "Remove excluded traces from beginning/end of tb; get cached lines" 251 orig_tb = tb[:] 252 while tb: 253 for rpcfile in exclude: 254 if tb[0][0].count(rpcfile): 255 break # found an exclude, break for: and delete tb[0] 256 else: 257 break # no excludes, have left RPC code, break while: 258 del tb[0] 259 while tb: 260 for rpcfile in exclude: 261 if tb[-1][0].count(rpcfile): 262 break 263 else: 264 break 265 del tb[-1] 266 if len(tb) == 0: 267 # exception was in IDLE internals, don't prune! 268 tb[:] = orig_tb[:] 269 print("** IDLE Internal Exception: ", file=sys.stderr) 270 rpchandler = rpc.objecttable['exec'].rpchandler 271 for i in range(len(tb)): 272 fn, ln, nm, line = tb[i] 273 if nm == '?': 274 nm = "-toplevel-" 275 if not line and fn.startswith("<pyshell#"): 276 line = rpchandler.remotecall('linecache', 'getline', 277 (fn, ln), {}) 278 tb[i] = fn, ln, nm, line 279 280def flush_stdout(): 281 """XXX How to do this now?""" 282 283def exit(): 284 """Exit subprocess, possibly after first clearing exit functions. 285 286 If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any 287 functions registered with atexit will be removed before exiting. 288 (VPython support) 289 290 """ 291 if no_exitfunc: 292 import atexit 293 atexit._clear() 294 capture_warnings(False) 295 sys.exit(0) 296 297 298def fix_scaling(root): 299 """Scale fonts on HiDPI displays.""" 300 import tkinter.font 301 scaling = float(root.tk.call('tk', 'scaling')) 302 if scaling > 1.4: 303 for name in tkinter.font.names(root): 304 font = tkinter.font.Font(root=root, name=name, exists=True) 305 size = int(font['size']) 306 if size < 0: 307 font['size'] = round(-0.75*size) 308 309 310def fixdoc(fun, text): 311 tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else '' 312 fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text)) 313 314RECURSIONLIMIT_DELTA = 30 315 316def install_recursionlimit_wrappers(): 317 """Install wrappers to always add 30 to the recursion limit.""" 318 # see: bpo-26806 319 320 @functools.wraps(sys.setrecursionlimit) 321 def setrecursionlimit(*args, **kwargs): 322 # mimic the original sys.setrecursionlimit()'s input handling 323 if kwargs: 324 raise TypeError( 325 "setrecursionlimit() takes no keyword arguments") 326 try: 327 limit, = args 328 except ValueError: 329 raise TypeError(f"setrecursionlimit() takes exactly one " 330 f"argument ({len(args)} given)") 331 if not limit > 0: 332 raise ValueError( 333 "recursion limit must be greater or equal than 1") 334 335 return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA) 336 337 fixdoc(setrecursionlimit, f"""\ 338 This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible 339 uninterruptible loops.""") 340 341 @functools.wraps(sys.getrecursionlimit) 342 def getrecursionlimit(): 343 return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA 344 345 fixdoc(getrecursionlimit, f"""\ 346 This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate 347 for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""") 348 349 # add the delta to the default recursion limit, to compensate 350 sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA) 351 352 sys.setrecursionlimit = setrecursionlimit 353 sys.getrecursionlimit = getrecursionlimit 354 355 356def uninstall_recursionlimit_wrappers(): 357 """Uninstall the recursion limit wrappers from the sys module. 358 359 IDLE only uses this for tests. Users can import run and call 360 this to remove the wrapping. 361 """ 362 if ( 363 getattr(sys.setrecursionlimit, '__wrapped__', None) and 364 getattr(sys.getrecursionlimit, '__wrapped__', None) 365 ): 366 sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__ 367 sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__ 368 sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA) 369 370 371class MyRPCServer(rpc.RPCServer): 372 373 def handle_error(self, request, client_address): 374 """Override RPCServer method for IDLE 375 376 Interrupt the MainThread and exit server if link is dropped. 377 378 """ 379 global quitting 380 try: 381 raise 382 except SystemExit: 383 raise 384 except EOFError: 385 global exit_now 386 exit_now = True 387 thread.interrupt_main() 388 except: 389 erf = sys.__stderr__ 390 print(textwrap.dedent(f""" 391 {'-'*40} 392 Unhandled exception in user code execution server!' 393 Thread: {threading.current_thread().name} 394 IDLE Client Address: {client_address} 395 Request: {request!r} 396 """), file=erf) 397 traceback.print_exc(limit=-20, file=erf) 398 print(textwrap.dedent(f""" 399 *** Unrecoverable, server exiting! 400 401 Users should never see this message; it is likely transient. 402 If this recurs, report this with a copy of the message 403 and an explanation of how to make it repeat. 404 {'-'*40}"""), file=erf) 405 quitting = True 406 thread.interrupt_main() 407 408 409# Pseudofiles for shell-remote communication (also used in pyshell) 410 411class StdioFile(io.TextIOBase): 412 413 def __init__(self, shell, tags, encoding='utf-8', errors='strict'): 414 self.shell = shell 415 self.tags = tags 416 self._encoding = encoding 417 self._errors = errors 418 419 @property 420 def encoding(self): 421 return self._encoding 422 423 @property 424 def errors(self): 425 return self._errors 426 427 @property 428 def name(self): 429 return '<%s>' % self.tags 430 431 def isatty(self): 432 return True 433 434 435class StdOutputFile(StdioFile): 436 437 def writable(self): 438 return True 439 440 def write(self, s): 441 if self.closed: 442 raise ValueError("write to closed file") 443 s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors) 444 return self.shell.write(s, self.tags) 445 446 447class StdInputFile(StdioFile): 448 _line_buffer = '' 449 450 def readable(self): 451 return True 452 453 def read(self, size=-1): 454 if self.closed: 455 raise ValueError("read from closed file") 456 if size is None: 457 size = -1 458 elif not isinstance(size, int): 459 raise TypeError('must be int, not ' + type(size).__name__) 460 result = self._line_buffer 461 self._line_buffer = '' 462 if size < 0: 463 while True: 464 line = self.shell.readline() 465 if not line: break 466 result += line 467 else: 468 while len(result) < size: 469 line = self.shell.readline() 470 if not line: break 471 result += line 472 self._line_buffer = result[size:] 473 result = result[:size] 474 return result 475 476 def readline(self, size=-1): 477 if self.closed: 478 raise ValueError("read from closed file") 479 if size is None: 480 size = -1 481 elif not isinstance(size, int): 482 raise TypeError('must be int, not ' + type(size).__name__) 483 line = self._line_buffer or self.shell.readline() 484 if size < 0: 485 size = len(line) 486 eol = line.find('\n', 0, size) 487 if eol >= 0: 488 size = eol + 1 489 self._line_buffer = line[size:] 490 return line[:size] 491 492 def close(self): 493 self.shell.close() 494 495 496class MyHandler(rpc.RPCHandler): 497 498 def handle(self): 499 """Override base method""" 500 executive = Executive(self) 501 self.register("exec", executive) 502 self.console = self.get_remote_proxy("console") 503 sys.stdin = StdInputFile(self.console, "stdin", 504 iomenu.encoding, iomenu.errors) 505 sys.stdout = StdOutputFile(self.console, "stdout", 506 iomenu.encoding, iomenu.errors) 507 sys.stderr = StdOutputFile(self.console, "stderr", 508 iomenu.encoding, "backslashreplace") 509 510 sys.displayhook = rpc.displayhook 511 # page help() text to shell. 512 import pydoc # import must be done here to capture i/o binding 513 pydoc.pager = pydoc.plainpager 514 515 # Keep a reference to stdin so that it won't try to exit IDLE if 516 # sys.stdin gets changed from within IDLE's shell. See issue17838. 517 self._keep_stdin = sys.stdin 518 519 install_recursionlimit_wrappers() 520 521 self.interp = self.get_remote_proxy("interp") 522 rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) 523 524 def exithook(self): 525 "override SocketIO method - wait for MainThread to shut us down" 526 time.sleep(10) 527 528 def EOFhook(self): 529 "Override SocketIO method - terminate wait on callback and exit thread" 530 global quitting 531 quitting = True 532 thread.interrupt_main() 533 534 def decode_interrupthook(self): 535 "interrupt awakened thread" 536 global quitting 537 quitting = True 538 thread.interrupt_main() 539 540 541class Executive(object): 542 543 def __init__(self, rpchandler): 544 self.rpchandler = rpchandler 545 self.locals = __main__.__dict__ 546 self.calltip = calltip.Calltip() 547 self.autocomplete = autocomplete.AutoComplete() 548 549 def runcode(self, code): 550 global interruptable 551 try: 552 self.usr_exc_info = None 553 interruptable = True 554 try: 555 exec(code, self.locals) 556 finally: 557 interruptable = False 558 except SystemExit as e: 559 if e.args: # SystemExit called with an argument. 560 ob = e.args[0] 561 if not isinstance(ob, (type(None), int)): 562 print('SystemExit: ' + str(ob), file=sys.stderr) 563 # Return to the interactive prompt. 564 except: 565 self.usr_exc_info = sys.exc_info() 566 if quitting: 567 exit() 568 print_exception() 569 jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") 570 if jit: 571 self.rpchandler.interp.open_remote_stack_viewer() 572 else: 573 flush_stdout() 574 575 def interrupt_the_server(self): 576 if interruptable: 577 thread.interrupt_main() 578 579 def start_the_debugger(self, gui_adap_oid): 580 return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) 581 582 def stop_the_debugger(self, idb_adap_oid): 583 "Unregister the Idb Adapter. Link objects and Idb then subject to GC" 584 self.rpchandler.unregister(idb_adap_oid) 585 586 def get_the_calltip(self, name): 587 return self.calltip.fetch_tip(name) 588 589 def get_the_completion_list(self, what, mode): 590 return self.autocomplete.fetch_completions(what, mode) 591 592 def stackviewer(self, flist_oid=None): 593 if self.usr_exc_info: 594 typ, val, tb = self.usr_exc_info 595 else: 596 return None 597 flist = None 598 if flist_oid is not None: 599 flist = self.rpchandler.get_remote_proxy(flist_oid) 600 while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: 601 tb = tb.tb_next 602 sys.last_type = typ 603 sys.last_value = val 604 item = stackviewer.StackTreeItem(flist, tb) 605 return debugobj_r.remote_object_tree_item(item) 606 607 608if __name__ == '__main__': 609 from unittest import main 610 main('idlelib.idle_test.test_run', verbosity=2) 611 612capture_warnings(False) # Make sure turned off; see bpo-18081. 613