Index: issue_nested_set.rb =================================================================== --- issue_nested_set.rb (revision 16557) +++ issue_nested_set.rb (working copy) @@ -54,11 +54,13 @@ self.root_id = parent.root_id self.lft = target_lft self.rgt = lft + 1 - self.class.where(:root_id => root_id).where("lft >= ? OR rgt >= ?", lft, lft).update_all([ + get_update { + self.class.reorder('lft desc').where(:root_id => root_id).where("lft >= ? OR rgt >= ?", lft, lft).update_all([ "lft = CASE WHEN lft >= :lft THEN lft + 2 ELSE lft END, " + "rgt = CASE WHEN rgt >= :lft THEN rgt + 2 ELSE rgt END", {:lft => lft} ]) + } end def add_as_root @@ -84,7 +86,7 @@ if parent previous_root_id = root_id self.root_id = parent.root_id - + lft_after_move = target_lft self.class.where(:root_id => parent.root_id).update_all([ "lft = CASE WHEN lft >= :lft THEN lft + :shift ELSE lft END, " + @@ -91,12 +93,12 @@ "rgt = CASE WHEN rgt >= :lft THEN rgt + :shift ELSE rgt END", {:lft => lft_after_move, :shift => (rgt - lft + 1)} ]) - + self.class.where(:root_id => previous_root_id).update_all([ "root_id = :root_id, lft = lft + :shift, rgt = rgt + :shift", {:root_id => parent.root_id, :shift => lft_after_move - lft} ]) - + self.lft, self.rgt = lft_after_move, (rgt - lft + lft_after_move) parent.send :reload_nested_set_values end @@ -105,7 +107,7 @@ def remove_from_nested_set self.class.where(:root_id => root_id).where("lft >= ? AND rgt <= ?", lft, rgt). update_all(["root_id = :id, lft = lft - :shift, rgt = rgt - :shift", {:id => id, :shift => lft - 1}]) - + self.class.where(:root_id => root_id).update_all([ "lft = CASE WHEN lft >= :lft THEN lft - :shift ELSE lft END, " + "rgt = CASE WHEN rgt >= :lft THEN rgt - :shift ELSE rgt END", @@ -123,11 +125,13 @@ children.each {|c| c.send :destroy_without_nested_set_update} reload unless @without_nested_set_update - self.class.where(:root_id => root_id).where("lft > ? OR rgt > ?", lft, lft).update_all([ + get_update { + self.class.reorder('lft asc').where(:root_id => root_id).where("lft > ? OR rgt > ?", lft, lft).update_all([ "lft = CASE WHEN lft > :lft THEN lft - :shift ELSE lft END, " + "rgt = CASE WHEN rgt > :lft THEN rgt - :shift ELSE rgt END", {:lft => lft, :shift => rgt - lft + 1} ]) + } end end @@ -148,7 +152,43 @@ new_record? || !is_or_is_ancestor_of?(issue) end - def lock_nested_set + def get_update(&block) + retry_count = 0 + begin + yield + rescue ActiveRecord::StatementInvalid => error + raise unless error.message =~ /[Dd]eadlock|Lock wait timeout exceeded|[Dd]uplicate/ + raise unless retry_count < 20 + retry_count += 1 + if error.message =~ /[Dd]uplicate/ + puts "Duplicate retry ##{retry_count} thread: #{Thread.current.object_id}" + else + puts "Deadlock detected on update, restarting transaction retry ##{retry_count} thread: #{Thread.current.object_id}" + end + sleep(rand(retry_count)*0.5) + retry + end + end + + def get_lock_with_retry(&block) + retry_count = 0 + begin + yield + rescue ActiveRecord::StatementInvalid => error + raise unless error.message =~ /[Dd]eadlock|Lock wait timeout exceeded/ + raise unless retry_count < 20 + retry_count += 1 + puts "Deadlock detected on getting lock, restarting transaction retry ##{retry_count} thread: #{Thread.current.object_id}" + sleep(rand(retry_count)*0.5) + retry + end + end + + def lock_nested_set + get_lock_with_retry { get_lock } + end + + def get_lock if self.class.connection.adapter_name =~ /sqlserver/i lock = "WITH (ROWLOCK HOLDLOCK UPDLOCK)" # Custom lock for SQLServer @@ -158,7 +198,8 @@ self.class.reorder(:id).where(:root_id => sets_to_lock).lock(lock).ids else sets_to_lock = [id, parent_id].compact - self.class.reorder(:id).where("root_id IN (SELECT root_id FROM #{self.class.table_name} WHERE id IN (?))", sets_to_lock).lock.ids + #self.class.reorder(:id).where("root_id IN (SELECT root_id FROM #{self.class.table_name} WHERE id IN (?))", sets_to_lock).lock.ids + self.class.connection.execute(self.class.reorder(:id).where(:root_id => self.class.where(:id => sets_to_lock).select(:root_id)).lock.to_sql) end end