Allow multiple IDLE GUI/subprocess pairs to exist

simultaneously. Thanks to David Scherer for suggesting
the use of an ephemeral port for the GUI.
Patch 1529142 Weeble.
This commit is contained in:
Kurt B. Kaiser 2009-04-04 07:03:48 +00:00
parent 195374e836
commit 013d6cc0df
3 changed files with 34 additions and 19 deletions

View file

@ -3,6 +3,10 @@ What's New in IDLE 2.7a0?
*Release date: XX-XXX-2009* *Release date: XX-XXX-2009*
- Allow multiple IDLE GUI/subprocess pairs to exist simultaneously. Thanks to
David Scherer for suggesting the use of an ephemeral port for the GUI.
Patch 1529142 Weeble.
- Remove port spec from run.py and fix bug where subprocess fails to - Remove port spec from run.py and fix bug where subprocess fails to
extract port from command line when warnings are present. extract port from command line when warnings are present.

View file

@ -37,7 +37,8 @@ import Debugger
import RemoteDebugger import RemoteDebugger
IDENTCHARS = string.ascii_letters + string.digits + "_" IDENTCHARS = string.ascii_letters + string.digits + "_"
LOCALHOST = '127.0.0.1' HOST = '127.0.0.1' # python execution server on localhost loopback
PORT = 0 # someday pass in host, port for remote debug capability
try: try:
from signal import SIGTERM from signal import SIGTERM
@ -342,17 +343,21 @@ class ModifiedInterpreter(InteractiveInterpreter):
InteractiveInterpreter.__init__(self, locals=locals) InteractiveInterpreter.__init__(self, locals=locals)
self.save_warnings_filters = None self.save_warnings_filters = None
self.restarting = False self.restarting = False
self.subprocess_arglist = self.build_subprocess_arglist() self.subprocess_arglist = None
self.port = PORT
port = 8833
rpcclt = None rpcclt = None
rpcpid = None rpcpid = None
def spawn_subprocess(self): def spawn_subprocess(self):
if self.subprocess_arglist == None:
self.subprocess_arglist = self.build_subprocess_arglist()
args = self.subprocess_arglist args = self.subprocess_arglist
self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args) self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
def build_subprocess_arglist(self): def build_subprocess_arglist(self):
assert (self.port!=0), (
"Socket should have been assigned a port number.")
w = ['-W' + s for s in sys.warnoptions] w = ['-W' + s for s in sys.warnoptions]
if 1/2 > 0: # account for new division if 1/2 > 0: # account for new division
w.append('-Qnew') w.append('-Qnew')
@ -373,11 +378,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
return [decorated_exec] + w + ["-c", command, str(self.port)] return [decorated_exec] + w + ["-c", command, str(self.port)]
def start_subprocess(self): def start_subprocess(self):
# spawning first avoids passing a listening socket to the subprocess addr = (HOST, self.port)
self.spawn_subprocess() # GUI makes several attempts to acquire socket, listens for connection
#time.sleep(20) # test to simulate GUI not accepting connection
addr = (LOCALHOST, self.port)
# Idle starts listening for connection on localhost
for i in range(3): for i in range(3):
time.sleep(i) time.sleep(i)
try: try:
@ -388,6 +390,18 @@ class ModifiedInterpreter(InteractiveInterpreter):
else: else:
self.display_port_binding_error() self.display_port_binding_error()
return None return None
# if PORT was 0, system will assign an 'ephemeral' port. Find it out:
self.port = self.rpcclt.listening_sock.getsockname()[1]
# if PORT was not 0, probably working with a remote execution server
if PORT != 0:
# To allow reconnection within the 2MSL wait (cf. Stevens TCP
# V1, 18.6), set SO_REUSEADDR. Note that this can be problematic
# on Windows since the implementation allows two active sockets on
# the same address!
self.rpcclt.listening_sock.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
self.spawn_subprocess()
#time.sleep(20) # test to simulate GUI not accepting connection
# Accept the connection from the Python execution server # Accept the connection from the Python execution server
self.rpcclt.listening_sock.settimeout(10) self.rpcclt.listening_sock.settimeout(10)
try: try:
@ -754,13 +768,12 @@ class ModifiedInterpreter(InteractiveInterpreter):
def display_port_binding_error(self): def display_port_binding_error(self):
tkMessageBox.showerror( tkMessageBox.showerror(
"Port Binding Error", "Port Binding Error",
"IDLE can't bind TCP/IP port 8833, which is necessary to " "IDLE can't bind to a TCP/IP port, which is necessary to "
"communicate with its Python execution server. Either " "communicate with its Python execution server. This might be "
"no networking is installed on this computer or another " "because no networking is installed on this computer. "
"process (another IDLE?) is using the port. Run IDLE with the -n " "Run IDLE with the -n command line switch to start without a "
"command line switch to start without a subprocess and refer to " "subprocess and refer to Help/IDLE Help 'Running without a "
"Help/IDLE Help 'Running without a subprocess' for further " "subprocess' for further details.",
"details.",
master=self.tkconsole.text) master=self.tkconsole.text)
def display_no_subprocess_error(self): def display_no_subprocess_error(self):
@ -1300,7 +1313,7 @@ def main():
global flist, root, use_subprocess global flist, root, use_subprocess
use_subprocess = True use_subprocess = True
enable_shell = False enable_shell = True
enable_edit = False enable_edit = False
debug = False debug = False
cmd = None cmd = None
@ -1321,6 +1334,7 @@ def main():
enable_shell = True enable_shell = True
if o == '-e': if o == '-e':
enable_edit = True enable_edit = True
enable_shell = False
if o == '-h': if o == '-h':
sys.stdout.write(usage_msg) sys.stdout.write(usage_msg)
sys.exit() sys.exit()
@ -1371,7 +1385,6 @@ def main():
edit_start = idleConf.GetOption('main', 'General', edit_start = idleConf.GetOption('main', 'General',
'editor-on-startup', type='bool') 'editor-on-startup', type='bool')
enable_edit = enable_edit or edit_start enable_edit = enable_edit or edit_start
enable_shell = enable_shell or not edit_start
# start editor and/or shell windows: # start editor and/or shell windows:
root = Tk(className="Idle") root = Tk(className="Idle")

View file

@ -518,8 +518,6 @@ class RPCClient(SocketIO):
def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM): def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
self.listening_sock = socket.socket(family, type) self.listening_sock = socket.socket(family, type)
self.listening_sock.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
self.listening_sock.bind(address) self.listening_sock.bind(address)
self.listening_sock.listen(1) self.listening_sock.listen(1)