Commit b0ee2260 authored by Michael Kozono's avatar Michael Kozono

Reduce risk of deadlocks

We’ve seen a deadlock in CI here https://gitlab.com/mkozono/gitlab-ce/builds/15644492#down-build-trace. This commit should not fix that particular failure, but perhaps it will avoid others.

* Don’t call delete_conflicting_redirects after update if the path wasn’t changed
* Rename descendants without using recursion again, so we can run delete_conflicting_redirects exactly once.
parent e1c245af
...@@ -8,19 +8,19 @@ class Route < ActiveRecord::Base ...@@ -8,19 +8,19 @@ class Route < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
after_save :delete_conflicting_redirects after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed?
after_update :create_redirect_for_old_path after_update :create_redirect_for_old_path
after_update :rename_direct_descendant_routes after_update :rename_descendants
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") } scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
scope :direct_descendant_routes, -> (path) { where('routes.path LIKE ? AND routes.path NOT LIKE ?', "#{sanitize_sql_like(path)}/%", "#{sanitize_sql_like(path)}/%/%") }
def rename_direct_descendant_routes def rename_descendants
return unless path_changed? || name_changed? return unless path_changed? || name_changed?
direct_descendant_routes = self.class.direct_descendant_routes(path_was) descendant_routes = self.class.inside_path(path_was)
direct_descendant_routes.each do |route| descendant_routes.each do |route|
attributes = {} attributes = {}
if path_changed? && route.path.present? if path_changed? && route.path.present?
...@@ -31,7 +31,17 @@ class Route < ActiveRecord::Base ...@@ -31,7 +31,17 @@ class Route < ActiveRecord::Base
attributes[:name] = route.name.sub(name_was, name) attributes[:name] = route.name.sub(name_was, name)
end end
route.update(attributes) unless attributes.empty? if attributes.present?
old_path = route.path
# Callbacks must be run manually
route.update_columns(attributes)
# We are not calling route.delete_conflicting_redirects here, in hopes
# of avoiding deadlocks. The parent (self, in this method) already
# called it, which deletes conflicts for all descendants.
route.create_redirect(old_path) if attributes[:path]
end
end end
end end
......
...@@ -50,20 +50,7 @@ describe Route, models: true do ...@@ -50,20 +50,7 @@ describe Route, models: true do
end end
end end
describe '.direct_descendant_routes' do describe '#rename_descendants' do
let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
let!(:another_group) { create(:group, path: 'other') }
let!(:similar_group) { create(:group, path: 'gitllab') }
let!(:another_group_nested) { create(:group, path: 'another', name: 'another', parent: similar_group) }
it 'returns correct routes' do
expect(Route.direct_descendant_routes('git_lab')).to match_array([nested_group.route])
expect(Route.direct_descendant_routes('git_lab/test')).to match_array([deep_nested_group.route])
end
end
describe '#rename_direct_descendant_routes' do
let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) } let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) } let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
let!(:similar_group) { create(:group, path: 'gitlab-org', name: 'gitlab-org') } let!(:similar_group) { create(:group, path: 'gitlab-org', name: 'gitlab-org') }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment