Merge branch 'stable'

* stable:
  Fixed race condition in 'repo sync -jN' that would open multiple masters.
This commit is contained in:
Shawn O. Pearce 2010-12-22 14:46:15 -08:00
commit 9275fd4329
2 changed files with 89 additions and 60 deletions

View File

@ -18,6 +18,10 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
import time import time
import urllib2 import urllib2
@ -371,76 +375,97 @@ class RefSpec(object):
_master_processes = [] _master_processes = []
_master_keys = set() _master_keys = set()
_ssh_master = True _ssh_master = True
_master_keys_lock = None
def init_ssh():
"""Should be called once at the start of repo to init ssh master handling.
At the moment, all we do is to create our lock.
"""
global _master_keys_lock
assert _master_keys_lock is None, "Should only call init_ssh once"
_master_keys_lock = _threading.Lock()
def _open_ssh(host, port=None): def _open_ssh(host, port=None):
global _ssh_master global _ssh_master
# Check to see whether we already think that the master is running; if we # Acquire the lock. This is needed to prevent opening multiple masters for
# think it's already running, return right away. # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
if port is not None: # manifest <remote fetch="ssh://xyz"> specifies a different host from the
key = '%s:%s' % (host, port) # one that was passed to repo init.
else: _master_keys_lock.acquire()
key = host
if key in _master_keys:
return True
if not _ssh_master \
or 'GIT_SSH' in os.environ \
or sys.platform in ('win32', 'cygwin'):
# failed earlier, or cygwin ssh can't do this
#
return False
# 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_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: 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: # Check to see whether we already think that the master is running; if we
# Our double-check found that the master _was_ infact running. Add to # think it's already running, return right away.
# the list of keys. if port is not None:
_master_keys.add(key) key = '%s:%s' % (host, port)
else:
key = host
if key in _master_keys:
return True 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] + \ if not _ssh_master \
['-M', '-N'] + \ or 'GIT_SSH' in os.environ \
command_base[1:] or sys.platform in ('win32', 'cygwin'):
try: # failed earlier, or cygwin ssh can't do this
Trace(': %s', ' '.join(command)) #
p = subprocess.Popen(command) return False
except Exception, e:
_ssh_master = False
print >>sys.stderr, \
'\nwarn: cannot enable ssh control master for %s:%s\n%s' \
% (host,port, str(e))
return False
_master_processes.append(p) # We will make two calls to ssh; this is the common part of both calls.
_master_keys.add(key) command_base = ['ssh',
time.sleep(1) '-o','ControlPath %s' % ssh_sock(),
return True host]
if port is not None:
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)
except Exception, e:
_ssh_master = False
print >>sys.stderr, \
'\nwarn: cannot enable ssh control master for %s:%s\n%s' \
% (host,port, str(e))
return False
_master_processes.append(p)
_master_keys.add(key)
time.sleep(1)
return True
finally:
_master_keys_lock.release()
def close_ssh(): def close_ssh():
global _master_keys_lock
terminate_ssh_clients() terminate_ssh_clients()
for p in _master_processes: for p in _master_processes:
@ -459,6 +484,9 @@ def close_ssh():
except OSError: except OSError:
pass pass
# We're done with the lock, so we can delete it.
_master_keys_lock = None
URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/') URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')

View File

@ -28,7 +28,7 @@ import re
import sys import sys
from trace import SetTrace from trace import SetTrace
from git_config import close_ssh from git_config import init_ssh, close_ssh
from command import InteractiveCommand from command import InteractiveCommand
from command import MirrorSafeCommand from command import MirrorSafeCommand
from command import PagedCommand from command import PagedCommand
@ -212,6 +212,7 @@ def _Main(argv):
repo = _Repo(opt.repodir) repo = _Repo(opt.repodir)
try: try:
try: try:
init_ssh()
repo._Run(argv) repo._Run(argv)
finally: finally:
close_ssh() close_ssh()