From 1ceecc85f26d15a20bf68c872c62d109bc8dae90 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: May 16 2017 12:18:36 +0000 Subject: Add project-level locking mechanism Signed-off-by: Patrick Uiterwijk --- diff --git a/pagure/__init__.py b/pagure/__init__.py index 7f14cb9..86db99f 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -351,7 +351,8 @@ def is_repo_user(repo_obj): ) or (user in usergrps) -def get_authorized_project(session, project_name, user=None, namespace=None): +def get_authorized_project(session, project_name, user=None, namespace=None, + with_lock=False): ''' Retrieving the project with user permission constraint :arg session: The SQLAlchemy session to use @@ -367,7 +368,8 @@ def get_authorized_project(session, project_name, user=None, namespace=None): :rtype: Project ''' - repo = pagure.lib._get_project(session, project_name, user, namespace) + repo = pagure.lib._get_project(session, project_name, user, namespace, + with_lock) if repo and repo.private and not is_repo_admin(repo): return None @@ -387,6 +389,29 @@ def generate_user_key_files(): pagure.lib.git.generate_gitolite_acls() +def acquire_lock(function): + """ Flask decorator to indicate the repo needs to be locked. + + This function reretrieves the flask.g.repo object, but this time requests + that the repo object gets locked. + This lock is retrieved in a way that actively waits until the lock is + acquired. + """ + @wraps(function) + def decorated_function(*args, **kwargs): + set_variables(with_lock=True) + return function(*args, **kwargs) + return decorated_function + + +def ensure_lock(repo): + """ Function to make sure that `repo` was retrieved locked. """ + if not flask.g.repo_locked: + raise Exception('Repo was not locked') + if repo is not flask.g.repo: + raise Exception('Incorrect repo was locked') + + def login_required(function): """ Flask decorator to retrict access to logged in user. If the auth system is ``fas`` it will also require that the user sign @@ -447,7 +472,7 @@ def set_session(): @APP.before_request -def set_variables(): +def set_variables(with_lock=False): """ This method retrieves the repo and username set in the URLs and provides some of the variables that are most often used. """ @@ -468,7 +493,9 @@ def set_variables(): # endpoint called is part of the API, just don't do anything if repo: flask.g.repo = pagure.get_authorized_project( - SESSION, repo, user=username, namespace=namespace) + SESSION, repo, user=username, namespace=namespace, + with_lock=with_lock) + flask.g.repo_locked = with_lock if authenticated(): flask.g.repo_forked = pagure.get_authorized_project( SESSION, repo, user=flask.g.fas_user.username, diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 65fbd4d..353b296 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2073,7 +2073,7 @@ def search_projects( return query.all() -def _get_project(session, name, user=None, namespace=None): +def _get_project(session, name, user=None, namespace=None, with_lock=False): '''Get a project from the database ''' query = session.query( @@ -2089,6 +2089,10 @@ def _get_project(session, name, user=None, namespace=None): else: query = query.filter(model.Project.namespace == namespace) + if with_lock: + query = query.with_for_update(nowait=False, + read=False) + if user is not None: query = query.filter( model.User.user == user