summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2017-01-16 16:34:13 -0500
committerYorick Peterse <yorickpeterse@gmail.com>2017-01-16 16:37:48 -0500
commit6f88984b0d935b5c39b063969c14204cf0a62362 (patch)
tree29d3ca6f3aeed57d366e473fee2f2b75ec30be7f
parent142be72a2aa6920fa60cc267737f2e702fdeae12 (diff)
downloadgitlab-ce-refresh-authorizations-tighter-lease.tar.gz
Synchronize all project authorization refreshingrefresh-authorizations-tighter-lease
Previously a lease would only be obtained to update data. This could lead to duplicate data being inserted, triggering a UNIQUE constraint error. To work around this we now acquire a lease before performing _any_ project authorization work, releasing it at the very end. Fixes #25987
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb41
-rw-r--r--changelogs/unreleased/refresh-authorizations-tighter-lease.yml4
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb40
3 files changed, 44 insertions, 41 deletions
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 21ec1bd9e65..2d211d5ebbe 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -26,8 +26,26 @@ module Users
user.reload
end
- # This method returns the updated User object.
def execute
+ lease_key = "refresh_authorized_projects:#{user.id}"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
+
+ until uuid = lease.try_obtain
+ # Keep trying until we obtain the lease. If we don't do so we may end up
+ # not updating the list of authorized projects properly. To prevent
+ # hammering Redis too much we'll wait for a bit between retries.
+ sleep(1)
+ end
+
+ begin
+ execute_without_lease
+ ensure
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ end
+ end
+
+ # This method returns the updated User object.
+ def execute_without_lease
current = current_authorizations_per_project
fresh = fresh_access_levels_per_project
@@ -47,26 +65,7 @@ module Users
end
end
- update_with_lease(remove, add)
- end
-
- # Updates the list of authorizations using an exclusive lease.
- def update_with_lease(remove = [], add = [])
- lease_key = "refresh_authorized_projects:#{user.id}"
- lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
-
- until uuid = lease.try_obtain
- # Keep trying until we obtain the lease. If we don't do so we may end up
- # not updating the list of authorized projects properly. To prevent
- # hammering Redis too much we'll wait for a bit between retries.
- sleep(1)
- end
-
- begin
- update_authorizations(remove, add)
- ensure
- Gitlab::ExclusiveLease.cancel(lease_key, uuid)
- end
+ update_authorizations(remove, add)
end
# Updates the list of authorizations for the current user.
diff --git a/changelogs/unreleased/refresh-authorizations-tighter-lease.yml b/changelogs/unreleased/refresh-authorizations-tighter-lease.yml
new file mode 100644
index 00000000000..ab42b2eb72d
--- /dev/null
+++ b/changelogs/unreleased/refresh-authorizations-tighter-lease.yml
@@ -0,0 +1,4 @@
+---
+title: Synchronize all project authorization refreshing work to prevent race conditions
+merge_request:
+author:
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 9fbb61565e3..690fe979492 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -10,7 +10,21 @@ describe Users::RefreshAuthorizedProjectsService do
create!(project: project, user: user, access_level: access_level)
end
- describe '#execute' do
+ describe '#execute', :redis do
+ it 'refreshes the authorizations using a lease' do
+ expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
+ and_return('foo')
+
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).
+ with(an_instance_of(String), 'foo')
+
+ expect(service).to receive(:execute_without_lease)
+
+ service.execute
+ end
+ end
+
+ describe '#execute_without_lease' do
before do
user.project_authorizations.delete_all
end
@@ -19,37 +33,23 @@ describe Users::RefreshAuthorizedProjectsService do
project2 = create(:empty_project)
to_remove = create_authorization(project2, user)
- expect(service).to receive(:update_with_lease).
+ expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
- service.execute
+ service.execute_without_lease
end
it 'sets the access level of a project to the highest available level' do
to_remove = create_authorization(project, user, Gitlab::Access::DEVELOPER)
- expect(service).to receive(:update_with_lease).
+ expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
- service.execute
+ service.execute_without_lease
end
it 'returns a User' do
- expect(service.execute).to be_an_instance_of(User)
- end
- end
-
- describe '#update_with_lease', :redis do
- it 'refreshes the authorizations using a lease' do
- expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
- and_return('foo')
-
- expect(Gitlab::ExclusiveLease).to receive(:cancel).
- with(an_instance_of(String), 'foo')
-
- expect(service).to receive(:update_authorizations).with([1], [])
-
- service.update_with_lease([1])
+ expect(service.execute_without_lease).to be_an_instance_of(User)
end
end