diff options
Diffstat (limited to 'app/experiments/strategy/round_robin.rb')
-rw-r--r-- | app/experiments/strategy/round_robin.rb | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/app/experiments/strategy/round_robin.rb b/app/experiments/strategy/round_robin.rb new file mode 100644 index 00000000000..7b80c0e984d --- /dev/null +++ b/app/experiments/strategy/round_robin.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Strategy + class RoundRobin + CacheError = Class.new(StandardError) + + COUNTER_EXPIRE_TIME = 86400 # one day + + def initialize(key, variants) + @key = key + @variants = variants + end + + def execute + increment_counter + resolve_variant_name + end + + # When the counter would expire + # + # @api private Used internally by SRE and debugging purpose + # @return [Integer] Number in seconds until expiration or false if never + def counter_expires_in + Gitlab::Redis::SharedState.with do |redis| + redis.ttl(key) + end + end + + # Return the actual counter value + # + # @return [Integer] value + def counter_value + Gitlab::Redis::SharedState.with do |redis| + (redis.get(key) || 0).to_i + end + end + + # Reset the counter + # + # @private Used internally by SRE and debugging purpose + # @return [Boolean] whether reset was a success + def reset! + redis_cmd do |redis| + redis.del(key) + end + end + + private + + attr_reader :key, :variants + + # Increase the counter + # + # @return [Boolean] whether operation was a success + def increment_counter + redis_cmd do |redis| + redis.incr(key) + redis.expire(key, COUNTER_EXPIRE_TIME) + end + end + + def resolve_variant_name + remainder = counter_value % variants.size + + variants[remainder] + end + + def redis_cmd + Gitlab::Redis::SharedState.with { |redis| yield(redis) } + + true + rescue CacheError => e + Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}") + + false + end + end +end |