Commit fc292ca0 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'declarative-policy-optimisations' into 'master'

Speed up DeclarativePolicy::Runner#steps_by_score

See merge request gitlab-org/gitlab-ce!14679
parents de01353b e9476eb9
---
title: Speed up permission checks
merge_request:
author:
type: other
...@@ -206,11 +206,13 @@ module DeclarativePolicy ...@@ -206,11 +206,13 @@ module DeclarativePolicy
end end
def cached_pass?(context) def cached_pass?(context)
passes = @rules.map { |r| r.cached_pass?(context) } @rules.each do |rule|
return false if passes.any? { |p| p == false } pass = rule.cached_pass?(context)
return true if passes.all? { |p| p == true }
nil return pass if pass.nil? || pass == false
end
true
end end
def repr def repr
...@@ -245,11 +247,13 @@ module DeclarativePolicy ...@@ -245,11 +247,13 @@ module DeclarativePolicy
end end
def cached_pass?(context) def cached_pass?(context)
passes = @rules.map { |r| r.cached_pass?(context) } @rules.each do |rule|
return true if passes.any? { |p| p == true } pass = rule.cached_pass?(context)
return false if passes.all? { |p| p == false }
nil return pass if pass.nil? || pass == true
end
false
end end
def score(context) def score(context)
......
...@@ -107,7 +107,7 @@ module DeclarativePolicy ...@@ -107,7 +107,7 @@ module DeclarativePolicy
end end
# This is the core spot where all those `#score` methods matter. # This is the core spot where all those `#score` methods matter.
# It is critcal for performance to run steps in the correct order, # It is critical for performance to run steps in the correct order,
# so that we don't compute expensive conditions (potentially n times # so that we don't compute expensive conditions (potentially n times
# if we're called on, say, a large list of users). # if we're called on, say, a large list of users).
# #
...@@ -139,30 +139,39 @@ module DeclarativePolicy ...@@ -139,30 +139,39 @@ module DeclarativePolicy
return return
end end
steps = Set.new(@steps) remaining_steps = Set.new(@steps)
remaining_enablers = steps.count { |s| s.enable? } remaining_enablers, remaining_preventers = remaining_steps.partition(&:enable?).map { |s| Set.new(s) }
loop do loop do
return if steps.empty? if @state.enabled?
# Once we set this, we never need to unset it, because a single
# prevent will stop this from being enabled
remaining_steps = remaining_preventers
else
# if the permission hasn't yet been enabled and we only have # if the permission hasn't yet been enabled and we only have
# prevent steps left, we short-circuit the state here # prevent steps left, we short-circuit the state here
@state.prevent! if !@state.enabled? && remaining_enablers == 0 @state.prevent! if remaining_enablers.empty?
end
return if remaining_steps.empty?
lowest_score = Float::INFINITY lowest_score = Float::INFINITY
next_step = nil next_step = nil
steps.each do |step| remaining_steps.each do |step|
score = step.score score = step.score
if score < lowest_score if score < lowest_score
next_step = step next_step = step
lowest_score = score lowest_score = score
end end
end
steps.delete(next_step) break if lowest_score.zero?
end
remaining_enablers -= 1 if next_step.enable? [remaining_steps, remaining_enablers, remaining_preventers].each do |set|
set.delete(next_step)
end
yield next_step, lowest_score yield next_step, lowest_score
end end
......
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