diff options
| author | Shawn O. Pearce <sop@google.com> | 2010-12-22 14:46:15 -0800 |
|---|---|---|
| committer | Shawn O. Pearce <sop@google.com> | 2010-12-22 14:46:15 -0800 |
| commit | 9275fd43294e760e47736857113f32a2f189c6c6 (patch) | |
| tree | 6e4f48d0a3691896f5bdff655077e4db268a32bf | |
| parent | 13f3da50d40b89ee5b05f5f3de9542c20edac6d1 (diff) | |
| parent | 0048b69c038306fe74408a63cdd0773b0d86a8fe (diff) | |
| download | git-repo-9275fd43294e760e47736857113f32a2f189c6c6.tar.gz | |
Merge branch 'stable'
* stable:
Fixed race condition in 'repo sync -jN' that would open multiple masters.
| -rw-r--r-- | git_config.py | 148 | ||||
| -rwxr-xr-x | main.py | 3 |
2 files changed, 90 insertions, 61 deletions
diff --git a/git_config.py b/git_config.py index 286e89ca..ff815e35 100644 --- a/git_config.py +++ b/git_config.py | |||
| @@ -18,6 +18,10 @@ import os | |||
| 18 | import re | 18 | import re |
| 19 | import subprocess | 19 | import subprocess |
| 20 | import sys | 20 | import sys |
| 21 | try: | ||
| 22 | import threading as _threading | ||
| 23 | except ImportError: | ||
| 24 | import dummy_threading as _threading | ||
| 21 | import time | 25 | import time |
| 22 | import urllib2 | 26 | import urllib2 |
| 23 | 27 | ||
| @@ -371,76 +375,97 @@ class RefSpec(object): | |||
| 371 | _master_processes = [] | 375 | _master_processes = [] |
| 372 | _master_keys = set() | 376 | _master_keys = set() |
| 373 | _ssh_master = True | 377 | _ssh_master = True |
| 378 | _master_keys_lock = None | ||
| 379 | |||
| 380 | def init_ssh(): | ||
| 381 | """Should be called once at the start of repo to init ssh master handling. | ||
| 382 | |||
| 383 | At the moment, all we do is to create our lock. | ||
| 384 | """ | ||
| 385 | global _master_keys_lock | ||
| 386 | assert _master_keys_lock is None, "Should only call init_ssh once" | ||
| 387 | _master_keys_lock = _threading.Lock() | ||
| 374 | 388 | ||
| 375 | def _open_ssh(host, port=None): | 389 | def _open_ssh(host, port=None): |
| 376 | global _ssh_master | 390 | global _ssh_master |
| 377 | 391 | ||
| 378 | # Check to see whether we already think that the master is running; if we | 392 | # Acquire the lock. This is needed to prevent opening multiple masters for |
| 379 | # think it's already running, return right away. | 393 | # the same host when we're running "repo sync -jN" (for N > 1) _and_ the |
| 380 | if port is not None: | 394 | # manifest <remote fetch="ssh://xyz"> specifies a different host from the |
| 381 | key = '%s:%s' % (host, port) | 395 | # one that was passed to repo init. |
| 382 | else: | 396 | _master_keys_lock.acquire() |
| 383 | key = host | 397 | try: |
| 384 | |||
| 385 | if key in _master_keys: | ||
| 386 | return True | ||
| 387 | 398 | ||
| 388 | if not _ssh_master \ | 399 | # Check to see whether we already think that the master is running; if we |
| 389 | or 'GIT_SSH' in os.environ \ | 400 | # think it's already running, return right away. |
| 390 | or sys.platform in ('win32', 'cygwin'): | 401 | if port is not None: |
| 391 | # failed earlier, or cygwin ssh can't do this | 402 | key = '%s:%s' % (host, port) |
| 392 | # | 403 | else: |
| 393 | return False | 404 | key = host |
| 394 | 405 | ||
| 395 | # We will make two calls to ssh; this is the common part of both calls. | 406 | if key in _master_keys: |
| 396 | command_base = ['ssh', | ||
| 397 | '-o','ControlPath %s' % ssh_sock(), | ||
| 398 | host] | ||
| 399 | if port is not None: | ||
| 400 | command_base[1:1] = ['-p',str(port)] | ||
| 401 | |||
| 402 | # Since the key wasn't in _master_keys, we think that master isn't running. | ||
| 403 | # ...but before actually starting a master, we'll double-check. This can | ||
| 404 | # be important because we can't tell that that 'git@myhost.com' is the same | ||
| 405 | # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file. | ||
| 406 | check_command = command_base + ['-O','check'] | ||
| 407 | try: | ||
| 408 | Trace(': %s', ' '.join(check_command)) | ||
| 409 | check_process = subprocess.Popen(check_command, | ||
| 410 | stdout=subprocess.PIPE, | ||
| 411 | stderr=subprocess.PIPE) | ||
| 412 | check_process.communicate() # read output, but ignore it... | ||
| 413 | isnt_running = check_process.wait() | ||
| 414 | |||
| 415 | if not isnt_running: | ||
| 416 | # Our double-check found that the master _was_ infact running. Add to | ||
| 417 | # the list of keys. | ||
| 418 | _master_keys.add(key) | ||
| 419 | return True | 407 | return True |
| 420 | except Exception: | ||
| 421 | # Ignore excpetions. We we will fall back to the normal command and print | ||
| 422 | # to the log there. | ||
| 423 | pass | ||
| 424 | |||
| 425 | command = command_base[:1] + \ | ||
| 426 | ['-M', '-N'] + \ | ||
| 427 | command_base[1:] | ||
| 428 | try: | ||
| 429 | Trace(': %s', ' '.join(command)) | ||
| 430 | p = subprocess.Popen(command) | ||
| 431 | except Exception, e: | ||
| 432 | _ssh_master = False | ||
| 433 | print >>sys.stderr, \ | ||
| 434 | '\nwarn: cannot enable ssh control master for %s:%s\n%s' \ | ||
| 435 | % (host,port, str(e)) | ||
| 436 | return False | ||
| 437 | 408 | ||
| 438 | _master_processes.append(p) | 409 | if not _ssh_master \ |
| 439 | _master_keys.add(key) | 410 | or 'GIT_SSH' in os.environ \ |
| 440 | time.sleep(1) | 411 | or sys.platform in ('win32', 'cygwin'): |
| 441 | return True | 412 | # failed earlier, or cygwin ssh can't do this |
| 413 | # | ||
| 414 | return False | ||
| 415 | |||
| 416 | # We will make two calls to ssh; this is the common part of both calls. | ||
| 417 | command_base = ['ssh', | ||
| 418 | '-o','ControlPath %s' % ssh_sock(), | ||
| 419 | host] | ||
| 420 | if port is not None: | ||
| 421 | command_base[1:1] = ['-p',str(port)] | ||
| 422 | |||
| 423 | # Since the key wasn't in _master_keys, we think that master isn't running. | ||
| 424 | # ...but before actually starting a master, we'll double-check. This can | ||
| 425 | # be important because we can't tell that that 'git@myhost.com' is the same | ||
| 426 | # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file. | ||
| 427 | check_command = command_base + ['-O','check'] | ||
| 428 | try: | ||
| 429 | Trace(': %s', ' '.join(check_command)) | ||
| 430 | check_process = subprocess.Popen(check_command, | ||
| 431 | stdout=subprocess.PIPE, | ||
| 432 | stderr=subprocess.PIPE) | ||
| 433 | check_process.communicate() # read output, but ignore it... | ||
| 434 | isnt_running = check_process.wait() | ||
| 435 | |||
| 436 | if not isnt_running: | ||
| 437 | # Our double-check found that the master _was_ infact running. Add to | ||
| 438 | # the list of keys. | ||
| 439 | _master_keys.add(key) | ||
| 440 | return True | ||
| 441 | except Exception: | ||
| 442 | # Ignore excpetions. We we will fall back to the normal command and print | ||
| 443 | # to the log there. | ||
| 444 | pass | ||
| 445 | |||
| 446 | command = command_base[:1] + \ | ||
| 447 | ['-M', '-N'] + \ | ||
| 448 | command_base[1:] | ||
| 449 | try: | ||
| 450 | Trace(': %s', ' '.join(command)) | ||
| 451 | p = subprocess.Popen(command) | ||
| 452 | except Exception, e: | ||
| 453 | _ssh_master = False | ||
| 454 | print >>sys.stderr, \ | ||
| 455 | '\nwarn: cannot enable ssh control master for %s:%s\n%s' \ | ||
| 456 | % (host,port, str(e)) | ||
| 457 | return False | ||
| 458 | |||
| 459 | _master_processes.append(p) | ||
| 460 | _master_keys.add(key) | ||
| 461 | time.sleep(1) | ||
| 462 | return True | ||
| 463 | finally: | ||
| 464 | _master_keys_lock.release() | ||
| 442 | 465 | ||
| 443 | def close_ssh(): | 466 | def close_ssh(): |
| 467 | global _master_keys_lock | ||
| 468 | |||
| 444 | terminate_ssh_clients() | 469 | terminate_ssh_clients() |
| 445 | 470 | ||
| 446 | for p in _master_processes: | 471 | for p in _master_processes: |
| @@ -459,6 +484,9 @@ def close_ssh(): | |||
| 459 | except OSError: | 484 | except OSError: |
| 460 | pass | 485 | pass |
| 461 | 486 | ||
| 487 | # We're done with the lock, so we can delete it. | ||
| 488 | _master_keys_lock = None | ||
| 489 | |||
| 462 | URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') | 490 | URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') |
| 463 | URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/') | 491 | URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/') |
| 464 | 492 | ||
| @@ -28,7 +28,7 @@ import re | |||
| 28 | import sys | 28 | import sys |
| 29 | 29 | ||
| 30 | from trace import SetTrace | 30 | from trace import SetTrace |
| 31 | from git_config import close_ssh | 31 | from git_config import init_ssh, close_ssh |
| 32 | from command import InteractiveCommand | 32 | from command import InteractiveCommand |
| 33 | from command import MirrorSafeCommand | 33 | from command import MirrorSafeCommand |
| 34 | from command import PagedCommand | 34 | from command import PagedCommand |
| @@ -212,6 +212,7 @@ def _Main(argv): | |||
| 212 | repo = _Repo(opt.repodir) | 212 | repo = _Repo(opt.repodir) |
| 213 | try: | 213 | try: |
| 214 | try: | 214 | try: |
| 215 | init_ssh() | ||
| 215 | repo._Run(argv) | 216 | repo._Run(argv) |
| 216 | finally: | 217 | finally: |
| 217 | close_ssh() | 218 | close_ssh() |
