95 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			95 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| module Gitlab
 | |
|   module RepositoryCacheAdapter
 | |
|     extend ActiveSupport::Concern
 | |
| 
 | |
|     class_methods do
 | |
|       # Wraps around the given method and caches its output in Redis and an instance
 | |
|       # variable.
 | |
|       #
 | |
|       # This only works for methods that do not take any arguments.
 | |
|       def cache_method(name, fallback: nil, memoize_only: false)
 | |
|         original = :"_uncached_#{name}"
 | |
| 
 | |
|         alias_method(original, name)
 | |
| 
 | |
|         define_method(name) do
 | |
|           cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
 | |
|             __send__(original) # rubocop:disable GitlabSecurity/PublicSend
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # RepositoryCache to be used. Should be overridden by the including class
 | |
|     def cache
 | |
|       raise NotImplementedError
 | |
|     end
 | |
| 
 | |
|     # List of cached methods. Should be overridden by the including class
 | |
|     def cached_methods
 | |
|       raise NotImplementedError
 | |
|     end
 | |
| 
 | |
|     # Caches the supplied block both in a cache and in an instance variable.
 | |
|     #
 | |
|     # The cache key and instance variable are named the same way as the value of
 | |
|     # the `key` argument.
 | |
|     #
 | |
|     # This method will return `nil` if the corresponding instance variable is also
 | |
|     # set to `nil`. This ensures we don't keep yielding the block when it returns
 | |
|     # `nil`.
 | |
|     #
 | |
|     # key - The name of the key to cache the data in.
 | |
|     # fallback - A value to fall back to in the event of a Git error.
 | |
|     def cache_method_output(key, fallback: nil, memoize_only: false, &block)
 | |
|       ivar = cache_instance_variable_name(key)
 | |
| 
 | |
|       if instance_variable_defined?(ivar)
 | |
|         instance_variable_get(ivar)
 | |
|       else
 | |
|         # If the repository doesn't exist and a fallback was specified we return
 | |
|         # that value inmediately. This saves us Rugged/gRPC invocations.
 | |
|         return fallback unless fallback.nil? || cache.repository.exists?
 | |
| 
 | |
|         begin
 | |
|           value =
 | |
|             if memoize_only
 | |
|               yield
 | |
|             else
 | |
|               cache.fetch(key, &block)
 | |
|             end
 | |
| 
 | |
|           instance_variable_set(ivar, value)
 | |
|         rescue Gitlab::Git::Repository::NoRepository
 | |
|           # Even if the above `#exists?` check passes these errors might still
 | |
|           # occur (for example because of a non-existing HEAD). We want to
 | |
|           # gracefully handle this and not cache anything
 | |
|           fallback
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Expires the caches of a specific set of methods
 | |
|     def expire_method_caches(methods)
 | |
|       methods.each do |key|
 | |
|         unless cached_methods.include?(key.to_sym)
 | |
|           Rails.logger.error "Requested to expire non-existent method '#{key}' for Repository"
 | |
|           next
 | |
|         end
 | |
| 
 | |
|         cache.expire(key)
 | |
| 
 | |
|         ivar = cache_instance_variable_name(key)
 | |
| 
 | |
|         remove_instance_variable(ivar) if instance_variable_defined?(ivar)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     def cache_instance_variable_name(key)
 | |
|       :"@#{key.to_s.tr('?!', '')}"
 | |
|     end
 | |
|   end
 | |
| end
 |