Module: Interlock
- Defined in:
- lib/interlock/config.rb,
lib/interlock.rb,
lib/interlock/lock.rb,
lib/interlock/finders.rb,
lib/interlock/interlock.rb,
lib/interlock/pass_through_store.rb
Overview
require ‘ehcache’
Defined Under Namespace
Modules: Config, Finders, Lock Classes: ConfigurationError, DependencyError, FragmentConsistencyError, InterlockError, LockAcquisitionError, PassThroughStore, UsageError
Constant Summary collapse
- DEFAULTS =
{ :ttl => 1.day, :namespace => 'app', :servers => ['127.0.0.1:11211'], :client => 'memcache-client', :with_finders => false }
- CLIENT_KEYS =
[ :prefix_key, :distribution, :verify_key, :tcp_nodelay, :hash, :hash_with_prefix_key, :show_backtraces, :default_ttl, :ketama_weighted, :retry_timeout, :default_weight, :buffer_requests, :timeout, :sort_hosts, :cache_lookups, :connect_timeout, :no_block, :failover, :support_cas, :namespace ]
- SCOPE_KEYS =
[:controller, :action, :id]
- KEY_LENGTH_LIMIT =
Buried value extracted from memcache.rb in the memcache-client gem. If one tries to request a key that is too long, the client throws an error. In practice, it seems better to simply avoid ever setting such long keys, so we use this value to achieve such for keys generated by Interlock.
250- ILLEGAL_KEY_CHARACTERS_PATTERN =
Similarly buried and useful: no whitespace allowed in keys.
/\s/- @@config =
DEFAULTS- @@local_cache =
Install the pass-through store. This is used in the console and test environment. In a server environment, the controller callbacks install the memory store before each request.
Interlock::PassThroughStore.new
Class Method Summary collapse
-
.caching_key(controller, action, id, tag) ⇒ Object
Build a fragment key for an explicitly passed context.
-
.dependency_key(klass, scope, key) ⇒ Object
Get the Memcached key for a class’s dependency list.
-
.extract_options_and_dependencies(dependencies, default = nil) ⇒ Object
Extract the dependencies from the rest of the arguments and registers them with the appropriate models.
-
.invalidate(key) ⇒ Object
Invalidate a particular key.
- .log(msg) ⇒ Object
-
.register_dependencies(dependencies, key) ⇒ Object
Add each key with scope to the appropriate dependencies array.
-
.say(key, msg, type = "fragment") ⇒ Object
:nodoc:.
Class Method Details
.caching_key(controller, action, id, tag) ⇒ Object
Build a fragment key for an explicitly passed context. Shouldn’t be called unless you need to write your own fine-grained invalidation rules. Make sure the default ones are really unacceptable before you go to the trouble of rolling your own.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/interlock/interlock.rb', line 128 def caching_key(controller, action, id, tag) raise ArgumentError, 'Both controller and action must be specified' unless controller and action id = (id or 'all').to_interlock_tag tag = tag.to_interlock_tag key = "interlock:#{ENV['RAILS_ASSET_ID']}:#{controller}:#{action}:#{id}:#{tag}" if key.length > KEY_LENGTH_LIMIT old_key = key key = key[0..KEY_LENGTH_LIMIT-1] say old_key, "truncated to #{key}" end key.gsub ILLEGAL_KEY_CHARACTERS_PATTERN, '' end |
.dependency_key(klass, scope, key) ⇒ Object
Get the Memcached key for a class’s dependency list. We store per-class to reduce lock contention.
117 118 119 120 |
# File 'lib/interlock/interlock.rb', line 117 def dependency_key(klass, scope, key) id = (scope == :id ? ":#{key.field(4)}" : nil) "interlock:#{ENV['RAILS_ASSET_ID']}:dependency:#{klass.name}#{id}" end |
.extract_options_and_dependencies(dependencies, default = nil) ⇒ Object
Extract the dependencies from the rest of the arguments and registers them with the appropriate models.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/interlock/interlock.rb', line 40 def (dependencies, default = nil) = dependencies. # Hook up the dependencies nested array. dependencies.map! { |klass| [klass, :all] } .each do |klass, scope| if klass.is_a? Class # # Beware! Scoping to :id means that a request's params[:id] must equal # klass#id or the rule will not trigger. This is because params[:id] is the # only record-specific scope include in the key. # # If you want fancier invalidation, think hard about whether it really # matters. Over-invalidation is rarely a problem, but under-invalidation # frequently is. # # "But I need it!" you say. All right, then start using key tags. # raise Interlock::DependencyError, "#{scope.inspect} is not a valid scope" unless [:all, :id].include?(scope) dependencies << [klass, scope.to_sym] end end unless dependencies.any? # Use the conventional controller/model association if none are provided # Can be skipped by calling caching(nil) dependencies = [[default, :all]] end # Remove nils dependencies.reject! {|klass, scope| klass.nil? } [.indifferentiate, dependencies] end |
.invalidate(key) ⇒ Object
Invalidate a particular key.
148 149 150 151 152 |
# File 'lib/interlock/interlock.rb', line 148 def invalidate(key) # Console and tests do not install the local cache Interlock.local_cache.delete(key) if Interlock.local_cache ActionController::Base.cache_store.delete key end |
.log(msg) ⇒ Object
102 103 104 105 106 107 108 109 110 111 |
# File 'lib/interlock/interlock.rb', line 102 def log(msg) case Interlock.config[:log_level] when 'debug', 'info', 'warn', 'error' log_method = Interlock.config[:log_level] else log_method = :debug end RAILS_DEFAULT_LOGGER.send( log_method, msg ) end |
.register_dependencies(dependencies, key) ⇒ Object
Add each key with scope to the appropriate dependencies array.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/interlock/interlock.rb', line 78 def register_dependencies(dependencies, key) return if Interlock.config[:disabled] Array(dependencies).each do |klass, scope| dep_key = dependency_key(klass, scope, key) # Get the value for this class/key out of the global store. this = (CACHE.get(dep_key) || {})[key] # Make sure to not overwrite broader scopes. unless this == :all or this == scope # We need to write, so acquire the lock. CACHE.lock(dep_key) do |hash| Interlock.say key, "registered a dependency on #{klass} -> #{scope.inspect}." (hash || {}).merge({key => scope}) end end end end |
.say(key, msg, type = "fragment") ⇒ Object
:nodoc:
98 99 100 |
# File 'lib/interlock/interlock.rb', line 98 def say(key, msg, type = "fragment") #:nodoc: log "** #{type} #{key.inspect[1..-2]} #{msg}" end |