Check for existing SSH ControlMaster
Be more thorough about checking for an existing ssh master by
running a test command first, and only opening up a new master
if the test fails to connect.
Change-Id: I56fe8e7b4dbc123675b7f259e81d359ed0cd55cf
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/git_config.py b/git_config.py
index 1382d5d..138470c 100644
--- a/git_config.py
+++ b/git_config.py
@@ -356,18 +356,21 @@
return s
-_ssh_cache = {}
+_master_processes = []
+_master_keys = set()
_ssh_master = True
def _open_ssh(host, port=None):
global _ssh_master
+ # Check to see whether we already think that the master is running; if we
+ # think it's already running, return right away.
if port is not None:
key = '%s:%s' % (host, port)
else:
key = host
- if key in _ssh_cache:
+ if key in _master_keys:
return True
if not _ssh_master \
@@ -377,15 +380,39 @@
#
return False
- command = ['ssh',
- '-o','ControlPath %s' % ssh_sock(),
- '-M',
- '-N',
- host]
-
+ # We will make two calls to ssh; this is the common part of both calls.
+ command_base = ['ssh',
+ '-o','ControlPath %s' % ssh_sock(),
+ host]
if port is not None:
- command[3:3] = ['-p',str(port)]
+ command_base[1:1] = ['-p',str(port)]
+
+ # Since the key wasn't in _master_keys, we think that master isn't running.
+ # ...but before actually starting a master, we'll double-check. This can
+ # be important because we can't tell that that 'git@myhost.com' is the same
+ # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
+ check_command = command_base + ['-O','check']
+ try:
+ Trace(': %s', ' '.join(check_command))
+ check_process = subprocess.Popen(check_command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ check_process.communicate() # read output, but ignore it...
+ isnt_running = check_process.wait()
+
+ if not isnt_running:
+ # Our double-check found that the master _was_ infact running. Add to
+ # the list of keys.
+ _master_keys.add(key)
+ return True
+ except Exception:
+ # Ignore excpetions. We we will fall back to the normal command and print
+ # to the log there.
+ pass
+ command = command_base[:1] + \
+ ['-M', '-N'] + \
+ command_base[1:]
try:
Trace(': %s', ' '.join(command))
p = subprocess.Popen(command)
@@ -396,20 +423,22 @@
% (host,port, str(e))
return False
- _ssh_cache[key] = p
+ _master_processes.append(p)
+ _master_keys.add(key)
time.sleep(1)
return True
def close_ssh():
terminate_ssh_clients()
- for key,p in _ssh_cache.iteritems():
+ for p in _master_processes:
try:
os.kill(p.pid, SIGTERM)
p.wait()
except OSError:
pass
- _ssh_cache.clear()
+ del _master_processes[:]
+ _master_keys.clear()
d = ssh_sock(create=False)
if d: