94 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			94 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| module Gitlab
 | |
|   # Reference Counter
 | |
|   #
 | |
|   # A reference counter is used as a mechanism to identify when
 | |
|   # a repository is being accessed by a writable operation.
 | |
|   #
 | |
|   # Maintenance operations would use this as a clue to when it should
 | |
|   # execute significant changes in order to avoid disrupting running traffic
 | |
|   class ReferenceCounter
 | |
|     REFERENCE_EXPIRE_TIME = 600
 | |
| 
 | |
|     attr_reader :gl_repository, :key
 | |
| 
 | |
|     # Reference Counter instance
 | |
|     #
 | |
|     # @example
 | |
|     #   Gitlab::ReferenceCounter.new('project-1')
 | |
|     #
 | |
|     # @see Gitlab::GlRepository::RepoType.identifier_for_repositorable
 | |
|     # @param [String] gl_repository repository identifier
 | |
|     def initialize(gl_repository)
 | |
|       @gl_repository = gl_repository
 | |
|       @key = "git-receive-pack-reference-counter:#{gl_repository}"
 | |
|     end
 | |
| 
 | |
|     # Return the actual counter value
 | |
|     #
 | |
|     # @return [Integer] value
 | |
|     def value
 | |
|       Gitlab::Redis::SharedState.with do |redis|
 | |
|         (redis.get(key) || 0).to_i
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Increase the counter
 | |
|     #
 | |
|     # @return [Boolean] whether operation was a success
 | |
|     def increase
 | |
|       redis_cmd do |redis|
 | |
|         redis.incr(key)
 | |
|         redis.expire(key, REFERENCE_EXPIRE_TIME)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Decrease the counter
 | |
|     #
 | |
|     # @return [Boolean] whether operation was a success
 | |
|     def decrease
 | |
|       redis_cmd do |redis|
 | |
|         current_value = redis.decr(key)
 | |
|         if current_value < 0
 | |
|           Gitlab::AppLogger.warn("Reference counter for #{gl_repository} decreased" \
 | |
|             " when its value was less than 1. Resetting the counter.")
 | |
|           redis.del(key)
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Reset the reference 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
 | |
| 
 | |
|     # When the reference counter would expire
 | |
|     #
 | |
|     # @api private Used internally by SRE and debugging purpose
 | |
|     # @return [Integer] Number in seconds until expiration or false if never
 | |
|     def expires_in
 | |
|       Gitlab::Redis::SharedState.with do |redis|
 | |
|         redis.ttl(key)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     def redis_cmd
 | |
|       Gitlab::Redis::SharedState.with { |redis| yield(redis) }
 | |
| 
 | |
|       true
 | |
|     rescue StandardError => e
 | |
|       Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")
 | |
| 
 | |
|       false
 | |
|     end
 | |
|   end
 | |
| end
 |