def remove_stale_owners(self): """Remove any lock owners that have gone stale.""" with self.lock: content = self._read() if 'owners' not in content: return False fresh_owners = [] for owner in content['owners']: if 'client_name' in owner: result = self.p4.run('clients', '-e', owner['client_name']) LOG.debug3('remove_stale_owners(): clients matching %s: %s', owner['client_name'], result) if len(result) > 0: fresh_owners.append(owner) else: pid = owner.get('process_id', 'unknown') LOG.warning('stale p4key-lock %s for %s removed', pid, self._repo_name) else: # client-less owner, cannot safely remove this entry fresh_owners.append(owner) if len(fresh_owners) < len(content['owners']): if len(fresh_owners) == 0: P4Key.delete(self.p4, self.owners_key) else: content['owners'] = fresh_owners self._write(content) return True return False
def _delete_p4key(p4, name): """Attempt to delete p4key. Report and continue on error.""" try: P4Key.delete(p4, name) except P4.P4Exception as e: if str(e).find("No such p4key") < 0: print('ERROR: Failed to delete p4key {name}: {e}'.format(name=name, e=str(e)))
def _delete_old_init_p4keys(p4, server_id): """Remove the old host-specific initialization p4keys, if any.""" names = [] names.append("git-fusion-{}-init-started".format(server_id)) names.append("git-fusion-{}-init-complete".format(server_id)) with p4.at_exception_level(p4.RAISE_NONE): for name in names: P4Key.delete(p4, name)
def _init_if_needed(p4, started_p4key, complete_p4key, func, nop_msg): """Call func to do initialization if keys are not set. Check that the completed p4key is non-zero, as that indicates that initialization has already been done. If neither the started nor complete key are set, set the started key, call the function, and finally set the complete key. If only the started key is set, there may be an initialization attempt in progress. Give it a chance to complete before assuming it has failed and restarting the initialization. Arguments: p4 -- P4 API object started_p4key -- name of init started p4key complete_p4key -- name of init completed p4key func -- initialization function to be called, takes a P4 argument. Must be idempotent since it is possible initialization may be performed more than once. nop_msg -- message to report if no initialization is needed Return True if initialization performed, False it not needed. """ if P4Key.is_set(p4, complete_p4key): # Short-circuit when there is nothing to be done. Verbosity.report(Verbosity.INFO, nop_msg) return False check_times = 5 while True: # If initialization has not been started, start it now. # Check before set to avoid a needless database write, # especially since this is run on every pull and push. if not P4Key.is_set(p4, started_p4key): if P4Key.acquire(p4, started_p4key): func(p4) # Set a p4key so we will not repeat initialization later. P4Key.acquire(p4, complete_p4key) return True # If initialization has been completed, we're done. if P4Key.is_set(p4, complete_p4key): Verbosity.report(Verbosity.INFO, nop_msg) return False # Another process started initialization. It may still be working on # it or it may have died. Wait a bit to give it a chance to complete. time.sleep(1) check_times -= 1 if not check_times: # Other process failed to finish perhaps. # Steal the "lock" and do the init ourselves. P4Key.delete(p4, started_p4key) elif check_times < 0: raise RuntimeError(_('error: unable to aquire lock for initialization'))
def do_release(self): """Release a held lock.""" label = "releasing p4key-lock for {}".format(self._repo_name) wait_reporter = p4gf_log.LongWaitReporter(label, LOG) while True: with self.lock: content = self._read() if self.ignore_pending_acquire or 'acquire_pending' not in content: content = self._remove_self(content) if content is None: P4Key.delete(self.p4, self.owners_key) else: self._write(content) if self._transfer_complete_cb: self._transfer_complete_cb() if self._post_lock_release_cb: self._post_lock_release_cb() td = time.time() - self._acquire_time LOG.debug("p4key-lock released: %s after %s ms", self._repo_name, td) return wait_reporter.been_waiting() time.sleep(_RETRY_PERIOD)
def main(): """Parse the command-line arguments and report on locks.""" # pylint: disable=too-many-statements desc = _("Report the currently held locks in Git Fusion.") parser = p4gf_util.create_arg_parser(desc=desc) parser.add_argument('--test', action='store_true', help=_('invoke test mode, acquire locks and report')) parser.add_argument( '--test2', action='store_true', help=_( 'invoke test mode, acquire locks and report, set dead processes.')) args = parser.parse_args() p4gf_util.has_server_id_or_exit() server_id = p4gf_util.get_server_id() p4 = p4gf_create_p4.create_p4_temp_client() if not p4: sys.exit(1) print("Connecting to P4PORT={} as P4USER={}".format(p4.port, p4.user)) if args.test or args.test2: repo_name = "p4gf_test_status_repo" status_key_name = p4gf_p4key.calc_repo_status_p4key_name( repo_name, None) p4gf_p4key.set(p4, status_key_name, 'Push 1 completed successfully') pushid_key_name = p4gf_p4key.calc_repo_push_id_p4key_name(repo_name) p4gf_p4key.set(p4, pushid_key_name, '1') # create a process and kill it and set its dead pid as a RepoLock owner below. if args.test: # A test with nothing stale with ExitStack() as stack: stack.enter_context(p4gf_lock.ReviewsLock(p4)) stack.enter_context(p4gf_lock.RepoLock(p4, repo_name)) stack.enter_context(p4gf_git_repo_lock.read_lock(repo_name)) stack.enter_context( p4gf_git_repo_lock.write_lock(repo_name, upgrade=True)) print_lock_status(p4, server_id) else: # if args.test2 # Now a test with some DEAD processes and a stale view Lock dead_process = subprocess.Popen(['echo', 'x'], stdout=subprocess.DEVNULL) dead_process.kill() while dead_process.returncode is None: dead_process.communicate() lock2 = None with ExitStack() as stack: stack.enter_context(p4gf_lock.ReviewsLock(p4)) # first lock owner lock1 = p4gf_lock.RepoLock(p4, repo_name) # second lock owner with same group_id and a dead pid lock2 = p4gf_lock.RepoLock(p4, repo_name, group_id=lock1.group_id) lock2.process_id = dead_process.pid # acquire the first RepoLock stack.enter_context(lock1) # Use low level method to add this DEAD pid to the group's lock owners lock2.do_acquire() stack.enter_context(p4gf_git_repo_lock.read_lock(repo_name)) stack.enter_context( p4gf_git_repo_lock.write_lock(repo_name, upgrade=True)) print("Test 1:") print_lock_status(p4, server_id) p4gf_p4key.set(p4, pushid_key_name, '2') p4gf_p4key.set(p4, status_key_name, 'Push 2 failed: some error') # Finally lets set the P4GF_P4KEY_LOCK_VIEW - the least likley to be stale p4gf_p4key.set(p4, lock2.lock_key, '1') print("Test 2:") print_lock_status(p4, server_id) # Cant exit the ExistStack unless we clean this p4gf_p4key.delete(p4, lock2.lock_key) # Clean up this lock so the test may be run again p4gf_p4key.delete(p4, lock2.owners_key) # remove test keys p4gf_p4key.delete(p4, status_key_name) p4gf_p4key.delete(p4, pushid_key_name) else: print_lock_status(p4, server_id)
def do_release(self): """First delete the owner key, then release the lock.""" P4Key.delete(self.p4, p4gf_const.P4GF_REVIEWS_COMMON_LOCK_OWNER) super(ReviewsLock, self).do_release()
def do_release(self): """Release a held lock.""" P4Key.delete(self.p4, self.lock_key)
def unset_server_id_p4key(server_id): """Delete the server-id p4key.""" if server_id: P4Key.delete(p4, p4gf_const.P4GF_P4KEY_SERVER_ID + server_id)
def delete_all(args, p4, metrics): """Remove all Git Fusion clients, as well as the object cache. Keyword arguments: args -- parsed command line arguments p4 -- Git user's Perforce client """ # pylint:disable=too-many-branches p4.user = p4gf_const.P4GF_USER group_list = [p4gf_const.P4GF_GROUP_PULL, p4gf_const.P4GF_GROUP_PUSH] print(_('Connected to {P4PORT}').format(P4PORT=p4.port)) print_verbose(args, _('Scanning for Git Fusion clients...')) client_name = p4gf_util.get_object_client_name() locks = _lock_all_repos(p4) if args.delete: was_prevented = _prevent_access(p4) else: was_prevented = None delete_clients(args, p4, metrics) # Retrieve the names of the initialization/upgrade "lock" p4keys. p4keys = [ p4gf_const.P4GF_P4KEY_ALL_PENDING_MB, p4gf_const.P4GF_P4KEY_ALL_REMAINING_MB ] # Key patterns NOT published in p4gf_const because they have trailing * # wildcards and it's not worth cluttering p4gf_const for this one use. p4key_patterns = [ 'git-fusion-init-started*', 'git-fusion-init-complete*', 'git-fusion-upgrade-started*', 'git-fusion-upgrade-complete*', 'git-fusion-index-*' ] for p4key_pattern in p4key_patterns: d = P4Key.get_all(p4, p4key_pattern) p4keys.extend(sorted(d.keys())) localroot = get_p4gf_localroot(p4) if not args.delete: if localroot: if args.no_obliterate: print(NTR('p4 sync -f #none')) else: print(NTR('p4 client -f -d {}').format(client_name)) print(NTR('rm -rf {}').format(localroot)) if not args.no_obliterate: print( NTR('p4 obliterate -hay //{}/objects/...').format( p4gf_const.P4GF_DEPOT)) for p4key in p4keys: print(NTR('p4 key -d {}').format(p4key)) for group in group_list: print(NTR('p4 group -a -d {}').format(group)) else: if localroot: if not args.no_obliterate: # Need this in order to use --gc later on # client should not exist; this is likely a NOOP p4gf_util.p4_client_df(p4, client_name) metrics.clients += 1 print_verbose( args, _("Deleting client '{client_name}'s workspace...").format( client_name=client_name)) _remove_local_root(localroot) _delete_cache(args, p4, metrics) print_verbose(args, _('Removing initialization p4keys...')) for p4key in p4keys: delete_p4key(p4, p4key, metrics) for group in group_list: delete_group(args, p4, group, metrics) _release_locks(locks) if was_prevented is not None: if was_prevented != '0': P4Key.set(p4, p4gf_const.P4GF_P4KEY_PREVENT_NEW_SESSIONS, was_prevented) else: P4Key.delete(p4, p4gf_const.P4GF_P4KEY_PREVENT_NEW_SESSIONS)