identity_cache 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +5 -4
  3. data/.github/workflows/cla.yml +22 -0
  4. data/.rubocop.yml +7 -3
  5. data/.spin/bootstrap +7 -0
  6. data/.spin/svc.yml +2 -0
  7. data/CHANGELOG.md +21 -0
  8. data/Gemfile +15 -5
  9. data/LICENSE +1 -1
  10. data/README.md +5 -6
  11. data/Rakefile +13 -12
  12. data/dev.yml +2 -4
  13. data/gemfiles/Gemfile.latest-release +12 -7
  14. data/gemfiles/Gemfile.min-supported +11 -6
  15. data/gemfiles/Gemfile.rails-edge +8 -5
  16. data/identity_cache.gemspec +15 -27
  17. data/{railgun.yml → isogun.yml} +0 -5
  18. data/lib/identity_cache/belongs_to_caching.rb +1 -0
  19. data/lib/identity_cache/cache_fetcher.rb +241 -16
  20. data/lib/identity_cache/cache_hash.rb +7 -6
  21. data/lib/identity_cache/cache_invalidation.rb +1 -0
  22. data/lib/identity_cache/cache_key_generation.rb +22 -19
  23. data/lib/identity_cache/cache_key_loader.rb +3 -3
  24. data/lib/identity_cache/cached/association.rb +2 -4
  25. data/lib/identity_cache/cached/attribute.rb +9 -5
  26. data/lib/identity_cache/cached/attribute_by_multi.rb +1 -1
  27. data/lib/identity_cache/cached/belongs_to.rb +3 -0
  28. data/lib/identity_cache/cached/embedded_fetching.rb +2 -0
  29. data/lib/identity_cache/cached/primary_index.rb +3 -2
  30. data/lib/identity_cache/cached/recursive/association.rb +2 -0
  31. data/lib/identity_cache/cached/recursive/has_many.rb +1 -0
  32. data/lib/identity_cache/cached/recursive/has_one.rb +1 -0
  33. data/lib/identity_cache/cached/reference/association.rb +1 -0
  34. data/lib/identity_cache/cached/reference/has_many.rb +1 -0
  35. data/lib/identity_cache/cached/reference/has_one.rb +1 -0
  36. data/lib/identity_cache/cached.rb +1 -0
  37. data/lib/identity_cache/configuration_dsl.rb +1 -0
  38. data/lib/identity_cache/encoder.rb +2 -1
  39. data/lib/identity_cache/expiry_hook.rb +1 -0
  40. data/lib/identity_cache/fallback_fetcher.rb +6 -1
  41. data/lib/identity_cache/mem_cache_store_cas.rb +15 -5
  42. data/lib/identity_cache/memoized_cache_proxy.rb +15 -15
  43. data/lib/identity_cache/parent_model_expiration.rb +6 -3
  44. data/lib/identity_cache/query_api.rb +13 -6
  45. data/lib/identity_cache/railtie.rb +1 -0
  46. data/lib/identity_cache/should_use_cache.rb +1 -0
  47. data/lib/identity_cache/version.rb +2 -1
  48. data/lib/identity_cache/with_primary_index.rb +49 -12
  49. data/lib/identity_cache/without_primary_index.rb +7 -3
  50. data/lib/identity_cache.rb +38 -24
  51. data/performance/cache_runner.rb +12 -9
  52. data/performance/cpu.rb +6 -5
  53. data/performance/externals.rb +6 -5
  54. data/performance/profile.rb +7 -6
  55. metadata +27 -123
  56. data/.github/probots.yml +0 -2
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'dalli/cas/client'
2
+
3
+ require "dalli/cas/client" unless Dalli::VERSION > "3"
3
4
 
4
5
  module IdentityCache
5
6
  module MemCacheStoreCAS
@@ -11,7 +12,7 @@ module IdentityCache
11
12
  instrument(:cas, key, options) do
12
13
  @data.with do |connection|
13
14
  connection.cas(key, options[:expires_in].to_i, options) do |raw_value|
14
- entry = deserialize_entry(raw_value)
15
+ entry = deserialize_entry(raw_value, raw: options[:raw])
15
16
  value = yield entry.value
16
17
  entry = ActiveSupport::Cache::Entry.new(value, **options)
17
18
  options[:raw] ? entry.value.to_s : entry
@@ -29,11 +30,11 @@ module IdentityCache
29
30
  keys = keys_to_names.keys
30
31
  rescue_error_with(false) do
31
32
  instrument(:cas_multi, keys, options) do
32
- raw_values = @data.get_multi_cas(keys)
33
+ raw_values = @data.with { |c| c.get_multi_cas(keys) }
33
34
 
34
35
  values = {}
35
36
  raw_values.each do |key, raw_value|
36
- entry = deserialize_entry(raw_value.first)
37
+ entry = deserialize_entry(raw_value.first, raw: options[:raw])
37
38
  values[keys_to_names[key]] = entry.value unless entry.expired?
38
39
  end
39
40
 
@@ -44,10 +45,19 @@ module IdentityCache
44
45
  cas_id = raw_values[key].last
45
46
  entry = ActiveSupport::Cache::Entry.new(value, **options)
46
47
  payload = options[:raw] ? entry.value.to_s : entry
47
- @data.replace_cas(key, payload, cas_id, options[:expires_in].to_i, options)
48
+ @data.with { |c| c.replace_cas(key, payload, cas_id, options[:expires_in].to_i, options) }
48
49
  end
49
50
  end
50
51
  end
51
52
  end
53
+
54
+ if ActiveSupport::Cache::MemCacheStore.instance_method(:deserialize_entry).arity == 1
55
+
56
+ private
57
+
58
+ def deserialize_entry(payload, raw: nil)
59
+ super(payload)
60
+ end
61
+ end
52
62
  end
53
63
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require 'monitor'
3
- require 'benchmark'
2
+
3
+ require "monitor"
4
+ require "benchmark"
4
5
 
5
6
  module IdentityCache
6
7
  class MemoizedCacheProxy
@@ -12,7 +13,7 @@ module IdentityCache
12
13
  end
13
14
 
14
15
  def cache_backend=(cache_adaptor)
15
- if cache_adaptor.class.name == 'ActiveSupport::Cache::MemCacheStore'
16
+ if cache_adaptor.class.name == "ActiveSupport::Cache::MemCacheStore"
16
17
  if cache_adaptor.respond_to?(:cas) || cache_adaptor.respond_to?(:cas_multi)
17
18
  unless cache_adaptor.is_a?(MemCacheStoreCAS)
18
19
  raise "#{cache_adaptor} respond to :cas or :cas_multi, that's unexpected"
@@ -30,7 +31,7 @@ module IdentityCache
30
31
  # no need for CAS support
31
32
  else
32
33
  warn("[IdentityCache] Missing CAS support in cache backend #{cache_adaptor.class} "\
33
- "which is needed for cache consistency")
34
+ "which is needed for cache consistency")
34
35
  end
35
36
  @cache_fetcher = FallbackFetcher.new(cache_adaptor)
36
37
  end
@@ -50,7 +51,7 @@ module IdentityCache
50
51
 
51
52
  def write(key, value)
52
53
  memoizing = memoizing?
53
- ActiveSupport::Notifications.instrument('cache_write.identity_cache', memoizing: memoizing) do
54
+ ActiveSupport::Notifications.instrument("cache_write.identity_cache", memoizing: memoizing) do
54
55
  memoized_key_values[key] = value if memoizing
55
56
  @cache_fetcher.write(key, value)
56
57
  end
@@ -58,7 +59,7 @@ module IdentityCache
58
59
 
59
60
  def delete(key)
60
61
  memoizing = memoizing?
61
- ActiveSupport::Notifications.instrument('cache_delete.identity_cache', memoizing: memoizing) do
62
+ ActiveSupport::Notifications.instrument("cache_delete.identity_cache", memoizing: memoizing) do
62
63
  memoized_key_values.delete(key) if memoizing
63
64
  if (result = @cache_fetcher.delete(key))
64
65
  IdentityCache.logger.debug { "[IdentityCache] delete recorded for #{key}" }
@@ -69,20 +70,18 @@ module IdentityCache
69
70
  end
70
71
  end
71
72
 
72
- def fetch(key)
73
+ def fetch(key, cache_fetcher_options = {}, &block)
73
74
  memo_misses = 0
74
75
  cache_misses = 0
75
76
 
76
- value = ActiveSupport::Notifications.instrument('cache_fetch.identity_cache') do |payload|
77
+ value = ActiveSupport::Notifications.instrument("cache_fetch.identity_cache") do |payload|
77
78
  payload[:resolve_miss_time] = 0.0
78
79
 
79
80
  value = fetch_memoized(key) do
80
81
  memo_misses = 1
81
- @cache_fetcher.fetch(key) do
82
+ @cache_fetcher.fetch(key, **cache_fetcher_options) do
82
83
  cache_misses = 1
83
- instrument_duration(payload, :resolve_miss_time) do
84
- yield
85
- end
84
+ instrument_duration(payload, :resolve_miss_time, &block)
86
85
  end
87
86
  end
88
87
  set_instrumentation_payload(payload, num_keys: 1, memo_misses: memo_misses, cache_misses: cache_misses)
@@ -93,7 +92,7 @@ module IdentityCache
93
92
  IdentityCache.logger.debug { "[IdentityCache] cache miss for #{key}" }
94
93
  else
95
94
  IdentityCache.logger.debug do
96
- "[IdentityCache] #{memo_misses > 0 ? '(cache_backend)' : '(memoized)'} cache hit for #{key}"
95
+ "[IdentityCache] #{memo_misses > 0 ? "(cache_backend)" : "(memoized)"} cache hit for #{key}"
97
96
  end
98
97
  end
99
98
 
@@ -104,7 +103,7 @@ module IdentityCache
104
103
  memo_miss_keys = EMPTY_ARRAY
105
104
  cache_miss_keys = EMPTY_ARRAY
106
105
 
107
- result = ActiveSupport::Notifications.instrument('cache_fetch_multi.identity_cache') do |payload|
106
+ result = ActiveSupport::Notifications.instrument("cache_fetch_multi.identity_cache") do |payload|
108
107
  payload[:resolve_miss_time] = 0.0
109
108
 
110
109
  result = fetch_multi_memoized(keys) do |non_memoized_keys|
@@ -132,7 +131,7 @@ module IdentityCache
132
131
  end
133
132
 
134
133
  def clear
135
- ActiveSupport::Notifications.instrument('cache_clear.identity_cache') do
134
+ ActiveSupport::Notifications.instrument("cache_clear.identity_cache") do
136
135
  clear_memoization
137
136
  @cache_fetcher.clear
138
137
  end
@@ -155,6 +154,7 @@ module IdentityCache
155
154
  if memoized_key_values.key?(key)
156
155
  return memoized_key_values[key]
157
156
  end
157
+
158
158
  memoized_key_values[key] = yield
159
159
  end
160
160
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  module ParentModelExpiration # :nodoc:
4
5
  extend ActiveSupport::Concern
@@ -22,6 +23,7 @@ module IdentityCache
22
23
 
23
24
  def install_pending_parent_expiry_hooks(model)
24
25
  return if lazy_hooks.empty?
26
+
25
27
  name = model.name.demodulize
26
28
  if (hooks = lazy_hooks.delete(name))
27
29
  hooks.each(&:install)
@@ -43,8 +45,9 @@ module IdentityCache
43
45
  def expire_parent_caches
44
46
  parents_to_expire = Set.new
45
47
  add_parents_to_cache_expiry_set(parents_to_expire)
46
- parents_to_expire.each do |parent|
47
- parent.expire_primary_index if parent.class.primary_cache_index_enabled
48
+ parents_to_expire.select! { |parent| parent.class.primary_cache_index_enabled }
49
+ parents_to_expire.reduce(true) do |all_expired, parent|
50
+ parent.expire_primary_index && all_expired
48
51
  end
49
52
  end
50
53
 
@@ -85,7 +88,7 @@ module IdentityCache
85
88
 
86
89
  cached_associations.each do |parent_class, only_on_foreign_key_change|
87
90
  if new_parent&.is_a?(parent_class) &&
88
- should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
91
+ should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
89
92
  add_record_to_cache_expiry_set(parents_to_expire, new_parent)
90
93
  end
91
94
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  module QueryAPI
4
5
  extend ActiveSupport::Concern
@@ -68,6 +69,7 @@ module IdentityCache
68
69
  def setup_embedded_associations_on_miss(records,
69
70
  readonly: IdentityCache.fetch_read_only_records && should_use_cache?)
70
71
  return if records.empty?
72
+
71
73
  records.each(&:readonly!) if readonly
72
74
  each_id_embedded_association do |cached_association|
73
75
  preload_id_embedded_association(records, cached_association)
@@ -86,6 +88,7 @@ module IdentityCache
86
88
  association.reset
87
89
  # reset inverse associations
88
90
  next unless target && association_reflection.has_inverse?
91
+
89
92
  inverse_name = association_reflection.inverse_of.name
90
93
  if target.is_a?(Array)
91
94
  target.each { |child_record| child_record.association(inverse_name).reset }
@@ -163,15 +166,17 @@ module IdentityCache
163
166
  def _run_commit_callbacks
164
167
  if destroyed? || transaction_changed_attributes.present?
165
168
  expire_cache
166
- expire_parent_caches
167
169
  end
168
170
  super
169
171
  end
170
172
 
171
- # Invalidate the cache data associated with the record.
173
+ # Invalidate the cache data associated with the record. Returns `true` on success,
174
+ # `false` otherwise.
172
175
  def expire_cache
173
- expire_attribute_indexes
174
- true
176
+ expired_parent_caches = expire_parent_caches
177
+ expired_attribute_indexes = expire_attribute_indexes
178
+
179
+ expired_parent_caches && expired_attribute_indexes
175
180
  end
176
181
 
177
182
  # @api private
@@ -182,9 +187,11 @@ module IdentityCache
182
187
 
183
188
  private
184
189
 
190
+ # Even if we have problems with some attributes, carry over the results and expire
191
+ # all possible attributes without array allocation.
185
192
  def expire_attribute_indexes # :nodoc:
186
- cache_indexes.each do |cached_attribute|
187
- cached_attribute.expire(self)
193
+ cache_indexes.reduce(true) do |all_expired, cached_attribute|
194
+ cached_attribute.expire(self) && all_expired
188
195
  end
189
196
  end
190
197
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  class Railtie < Rails::Railtie
4
5
  initializer "identity_cache.setup" do |app|
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  module ShouldUseCache
4
5
  extend ActiveSupport::Concern
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
- VERSION = "1.1.0"
4
+ VERSION = "1.3.0"
4
5
  CACHE_VERSION = 8
5
6
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  module WithPrimaryIndex
4
5
  extend ActiveSupport::Concern
@@ -6,8 +7,9 @@ module IdentityCache
6
7
  include WithoutPrimaryIndex
7
8
 
8
9
  def expire_cache
9
- expire_primary_index
10
- super
10
+ expired_primary_index = expire_primary_index
11
+
12
+ super && expired_primary_index
11
13
  end
12
14
 
13
15
  # @api private
@@ -57,7 +59,7 @@ module IdentityCache
57
59
  cache_attribute_by_alias(attribute_proc, alias_name: :id, by: fields, unique: unique)
58
60
 
59
61
  field_list = fields.join("_and_")
60
- arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
62
+ arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(",")
61
63
 
62
64
  if unique
63
65
  instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
@@ -97,21 +99,47 @@ module IdentityCache
97
99
  !!fetch_by_id(id)
98
100
  end
99
101
 
100
- # Default fetcher added to the model on inclusion, it behaves like
101
- # ActiveRecord::Base.where(id: id).first
102
- def fetch_by_id(id, includes: nil)
102
+ # Fetch the record by its primary key from the cache or read from
103
+ # the database and fill the cache on a cache miss. This behaves like
104
+ # `where(id: id).readonly.first` being called on the model.
105
+ #
106
+ # @param id Primary key value for the record to fetch.
107
+ # @param includes [Hash|Array|Symbol] Cached associations to prefetch from
108
+ # the cache on the returned record
109
+ # @param fill_lock_duration [Float] If provided, take a fill lock around cache fills
110
+ # and wait for this duration for cache to be filled when reading a lock provided
111
+ # by another client. Defaults to not setting the fill lock and trying to fill the
112
+ # cache from the database regardless of the presence of another client's fill lock.
113
+ # Set this to just above the typical amount of time it takes to do a cache fill.
114
+ # @param lock_wait_tries [Integer] Only applicable if fill_lock_duration is provided,
115
+ # in which case it specifies the number of times to do a lock wait. After the first
116
+ # lock wait it will try to take the lock, so will only do following lock waits due
117
+ # to another client taking the lock first. If another lock wait would be needed after
118
+ # reaching the limit, then a `LockWaitTimeout` exception is raised. Default is 2. Use
119
+ # this to control the maximum total lock wait duration
120
+ # (`lock_wait_tries * fill_lock_duration`).
121
+ # @raise [LockWaitTimeout] Timeout after waiting `lock_wait_tries * fill_lock_duration`
122
+ # seconds for `lock_wait_tries` other clients to fill the cache.
123
+ # @return [self|nil] An instance of this model for the record with the specified id or
124
+ # `nil` if no record with this `id` exists in the database
125
+ def fetch_by_id(id, includes: nil, **cache_fetcher_options)
103
126
  ensure_base_model
104
127
  raise_if_scoped
105
- record = cached_primary_index.fetch(id)
128
+ record = cached_primary_index.fetch(id, cache_fetcher_options)
106
129
  prefetch_associations(includes, [record]) if record && includes
107
130
  record
108
131
  end
109
132
 
110
- # Default fetcher added to the model on inclusion, it behaves like
111
- # ActiveRecord::Base.find, but will raise IdentityCache::RecordNotFound
112
- # if the id is not in the cache.
113
- def fetch(id, includes: nil)
114
- fetch_by_id(id, includes: includes) || raise(
133
+ # Fetch the record by its primary key from the cache or read from
134
+ # the database and fill the cache on a cache miss. This behaves like
135
+ # `readonly.find(id)` being called on the model.
136
+ #
137
+ # @param (see #fetch_by_id)
138
+ # @raise (see #fetch_by_id)
139
+ # @raise [ActiveRecord::RecordNotFound] if the record isn't found
140
+ # @return [self] An instance of this model for the record with the specified id
141
+ def fetch(id, **options)
142
+ fetch_by_id(id, **options) || raise(
115
143
  IdentityCache::RecordNotFound, "Couldn't find #{name} with ID=#{id}"
116
144
  )
117
145
  end
@@ -131,6 +159,15 @@ module IdentityCache
131
159
  def expire_primary_key_cache_index(id)
132
160
  cached_primary_index.expire(id)
133
161
  end
162
+
163
+ private
164
+
165
+ def inherited(subclass)
166
+ super
167
+ subclass.class_eval do
168
+ @cached_primary_index = nil
169
+ end
170
+ end
134
171
  end
135
172
  end
136
173
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  module WithoutPrimaryIndex
4
5
  extend ActiveSupport::Concern
@@ -12,9 +13,12 @@ module IdentityCache
12
13
  include IdentityCache::ShouldUseCache
13
14
  include ParentModelExpiration
14
15
 
15
- def self.append_features(base) #:nodoc:
16
- raise AlreadyIncludedError if base.include?(WithoutPrimaryIndex)
17
- super
16
+ class << self
17
+ def append_features(base) # :nodoc:
18
+ raise AlreadyIncludedError if base.include?(WithoutPrimaryIndex)
19
+
20
+ super
21
+ end
18
22
  end
19
23
 
20
24
  included do
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require 'active_record'
3
- require 'active_support/core_ext/module/attribute_accessors'
4
- require 'ar_transaction_changes'
2
+
3
+ require "active_record"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+ require "ar_transaction_changes"
5
6
 
6
7
  require "identity_cache/version"
7
8
  require "identity_cache/record_not_found"
@@ -27,24 +28,24 @@ require "identity_cache/cached/reference/association"
27
28
  require "identity_cache/cached/reference/has_one"
28
29
  require "identity_cache/cached/reference/has_many"
29
30
  require "identity_cache/expiry_hook"
30
- require 'identity_cache/memoized_cache_proxy'
31
- require 'identity_cache/belongs_to_caching'
32
- require 'identity_cache/cache_key_generation'
33
- require 'identity_cache/configuration_dsl'
34
- require 'identity_cache/should_use_cache'
35
- require 'identity_cache/parent_model_expiration'
36
- require 'identity_cache/query_api'
31
+ require "identity_cache/memoized_cache_proxy"
32
+ require "identity_cache/belongs_to_caching"
33
+ require "identity_cache/cache_key_generation"
34
+ require "identity_cache/configuration_dsl"
35
+ require "identity_cache/should_use_cache"
36
+ require "identity_cache/parent_model_expiration"
37
+ require "identity_cache/query_api"
37
38
  require "identity_cache/cache_hash"
38
39
  require "identity_cache/cache_invalidation"
39
40
  require "identity_cache/cache_fetcher"
40
41
  require "identity_cache/fallback_fetcher"
41
- require 'identity_cache/without_primary_index'
42
- require 'identity_cache/with_primary_index'
42
+ require "identity_cache/without_primary_index"
43
+ require "identity_cache/with_primary_index"
43
44
 
44
45
  module IdentityCache
45
46
  extend ActiveSupport::Concern
46
47
 
47
- autoload :MemCacheStoreCAS, 'identity_cache/mem_cache_store_cas'
48
+ autoload :MemCacheStoreCAS, "identity_cache/mem_cache_store_cas"
48
49
 
49
50
  include WithPrimaryIndex
50
51
 
@@ -65,23 +66,26 @@ module IdentityCache
65
66
 
66
67
  class DerivedModelError < StandardError; end
67
68
 
69
+ class LockWaitTimeout < StandardError; end
70
+
68
71
  mattr_accessor :cache_namespace
69
72
  self.cache_namespace = "IDC:#{CACHE_VERSION}:"
70
73
 
71
74
  # Fetched records are not read-only and this could sometimes prevent IDC from
72
75
  # reflecting what's truly in the database when fetch_read_only_records is false.
73
76
  # When set to true, it will only return read-only records when cache is used.
74
- mattr_accessor :fetch_read_only_records
75
- self.fetch_read_only_records = true
77
+ @fetch_read_only_records = true
76
78
 
77
79
  class << self
78
80
  include IdentityCache::CacheHash
79
81
 
82
+ attr_writer :fetch_read_only_records
80
83
  attr_accessor :readonly
81
84
  attr_writer :logger
82
85
 
83
- def append_features(base) #:nodoc:
86
+ def append_features(base) # :nodoc:
84
87
  raise AlreadyIncludedError if base.include?(IdentityCache)
88
+
85
89
  super
86
90
  end
87
91
 
@@ -141,10 +145,13 @@ module IdentityCache
141
145
  #
142
146
  # == Parameters
143
147
  # +key+ A cache key string
148
+ # +cache_fetcher_options+ A hash of options to pass to the cache backend
144
149
  #
145
- def fetch(key)
150
+ def fetch(key, cache_fetcher_options = {})
146
151
  if should_use_cache?
147
- unmap_cached_nil_for(cache.fetch(key) { map_cached_nil_for(yield) })
152
+ unmap_cached_nil_for(cache.fetch(key, cache_fetcher_options) do
153
+ map_cached_nil_for(yield)
154
+ end)
148
155
  else
149
156
  yield
150
157
  end
@@ -185,11 +192,18 @@ module IdentityCache
185
192
  end
186
193
 
187
194
  def with_fetch_read_only_records(value = true)
188
- old_value = fetch_read_only_records
189
- self.fetch_read_only_records = value
195
+ old_value = Thread.current[:identity_cache_fetch_read_only_records]
196
+ Thread.current[:identity_cache_fetch_read_only_records] = value
190
197
  yield
191
198
  ensure
192
- self.fetch_read_only_records = old_value
199
+ Thread.current[:identity_cache_fetch_read_only_records] = old_value
200
+ end
201
+
202
+ def fetch_read_only_records
203
+ v = Thread.current[:identity_cache_fetch_read_only_records]
204
+ return v unless v.nil?
205
+
206
+ @fetch_read_only_records
193
207
  end
194
208
 
195
209
  def eager_load!
@@ -198,12 +212,12 @@ module IdentityCache
198
212
 
199
213
  private
200
214
 
201
- def fetch_in_batches(keys)
215
+ def fetch_in_batches(keys, &block)
202
216
  keys.each_slice(BATCH_SIZE).each_with_object({}) do |slice, result|
203
- result.merge!(cache.fetch_multi(*slice) { |missed_keys| yield missed_keys })
217
+ result.merge!(cache.fetch_multi(*slice, &block))
204
218
  end
205
219
  end
206
220
  end
207
221
  end
208
222
 
209
- require 'identity_cache/railtie' if defined?(Rails)
223
+ require "identity_cache/railtie" if defined?(Rails)
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
3
- require 'active_record'
4
- require 'active_support/core_ext'
5
- require 'active_support/cache'
6
- require 'identity_cache'
7
- require 'memcached_store'
8
- require 'active_support/cache/memcached_store'
4
+ require "active_record"
5
+ require "active_support/core_ext"
6
+ require "active_support/cache"
7
+ require "identity_cache"
8
+ require "memcached_store"
9
+ require "active_support/cache/memcached_store"
9
10
 
10
- require File.dirname(__FILE__) + '/../test/helpers/active_record_objects'
11
- require File.dirname(__FILE__) + '/../test/helpers/database_connection'
12
- require File.dirname(__FILE__) + '/../test/helpers/cache_connection'
11
+ require File.dirname(__FILE__) + "/../test/helpers/active_record_objects"
12
+ require File.dirname(__FILE__) + "/../test/helpers/database_connection"
13
+ require File.dirname(__FILE__) + "/../test/helpers/cache_connection"
13
14
 
14
15
  IdentityCache.logger = Logger.new(nil)
15
16
  CacheConnection.setup
@@ -31,6 +32,7 @@ def create_database(count)
31
32
  helper.setup_models
32
33
 
33
34
  return if database_ready(count)
35
+
34
36
  puts "Database not ready for performance testing, generating records"
35
37
 
36
38
  DatabaseConnection.drop_tables
@@ -38,6 +40,7 @@ def create_database(count)
38
40
  existing = Item.all
39
41
  (1..count).to_a.each do |i|
40
42
  next if existing.any? { |e| e.id == i }
43
+
41
44
  a = Item.new
42
45
  a.id = i
43
46
  a.associated = AssociatedRecord.new(name: "Associated for #{i}")
data/performance/cpu.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'rubygems'
3
- require 'benchmark'
4
2
 
5
- require_relative 'cache_runner'
3
+ require "rubygems"
4
+ require "benchmark"
5
+
6
+ require_relative "cache_runner"
6
7
 
7
8
  RUNS = 400
8
9
 
@@ -32,9 +33,9 @@ def bmbm(runners)
32
33
  label_width = runners.map { |r| r.name.size }.max + 2
33
34
  width = label_width + Benchmark::CAPTION.size
34
35
 
35
- puts 'Rehearsal: '.ljust(width, '-')
36
+ puts "Rehearsal: ".ljust(width, "-")
36
37
  benchmark(runners, label_width)
37
- puts '-' * width
38
+ puts "-" * width
38
39
 
39
40
  benchmark(runners, label_width)
40
41
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
- require 'rubygems'
3
- require 'benchmark'
4
- require 'ruby-prof'
5
2
 
6
- require_relative 'cache_runner'
3
+ require "rubygems"
4
+ require "benchmark"
5
+ require "ruby-prof"
6
+
7
+ require_relative "cache_runner"
7
8
 
8
9
  RUNS = 1000
9
10
  RubyProf.measure_mode = RubyProf::CPU_TIME
10
11
 
11
12
  EXTERNALS = { "Memcache" => ["MemCache#set", "MemCache#get"],
12
- "Database" => ["Mysql2::Client#query"] }
13
+ "Database" => ["Mysql2::Client#query"], }
13
14
 
14
15
  def run(obj)
15
16
  obj.prepare
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
- require 'rubygems'
3
- require 'benchmark'
4
- require 'stackprof'
5
2
 
6
- require_relative 'cache_runner'
3
+ require "rubygems"
4
+ require "benchmark"
5
+ require "stackprof"
6
+
7
+ require_relative "cache_runner"
7
8
 
8
9
  RUNS = 1000
9
10
 
@@ -22,9 +23,9 @@ end
22
23
 
23
24
  create_database(RUNS)
24
25
 
25
- if (runner_name = ENV['RUNNER'])
26
+ if (runner_name = ENV["RUNNER"])
26
27
  if (runner = CACHE_RUNNERS.find { |r| r.name == runner_name })
27
- run(runner.new(RUNS), filename: ENV['FILENAME'])
28
+ run(runner.new(RUNS), filename: ENV["FILENAME"])
28
29
  else
29
30
  puts "Couldn't find cache runner #{runner_name.inspect}"
30
31
  exit(1)