diff --git a/lib/searchkick.rb b/lib/searchkick.rb index e96f9c2..76cc381 100644 --- a/lib/searchkick.rb +++ b/lib/searchkick.rb @@ -12,6 +12,7 @@ require "forwardable" # modules require "searchkick/controller_runtime" require "searchkick/index" +require "searchkick/index_cache" require "searchkick/index_options" require "searchkick/indexer" require "searchkick/hash_wrapper" diff --git a/lib/searchkick/index_cache.rb b/lib/searchkick/index_cache.rb new file mode 100644 index 0000000..cd6ac53 --- /dev/null +++ b/lib/searchkick/index_cache.rb @@ -0,0 +1,30 @@ +module Searchkick + class IndexCache + def initialize(max_size: 20) + @data = {} + @mutex = Mutex.new + @max_size = max_size + end + + # probably a better pattern for this + # but keep it simple + def fetch(name) + # thread-safe in MRI without mutex + # due to how context switching works + @mutex.synchronize do + if @data.key?(name) + @data[name] + else + @data.clear if @data.size >= @max_size + @data[name] = yield + end + end + end + + def clear + @mutex.synchronize do + @data.clear + end + end + end +end diff --git a/lib/searchkick/model.rb b/lib/searchkick/model.rb index 4776fd7..cecdf56 100644 --- a/lib/searchkick/model.rb +++ b/lib/searchkick/model.rb @@ -55,7 +55,7 @@ module Searchkick class_variable_set :@@searchkick_options, options.dup class_variable_set :@@searchkick_klass, self - class_variable_set :@@searchkick_index_cache, {} + class_variable_set :@@searchkick_index_cache, Searchkick::IndexCache.new class << self def searchkick_search(term = "*", **options, &block) @@ -71,7 +71,7 @@ module Searchkick index = name || searchkick_index_name index = index.call if index.respond_to?(:call) index_cache = class_variable_get(:@@searchkick_index_cache) - index_cache[index] ||= Searchkick::Index.new(index, searchkick_options) + index_cache.fetch(index) { Searchkick::Index.new(index, searchkick_options) } end alias_method :search_index, :searchkick_index unless method_defined?(:search_index) diff --git a/test/index_cache_test.rb b/test/index_cache_test.rb new file mode 100644 index 0000000..42aed2e --- /dev/null +++ b/test/index_cache_test.rb @@ -0,0 +1,49 @@ +require_relative "test_helper" + +class IndexCacheTest < Minitest::Test + def setup + Product.class_variable_get(:@@searchkick_index_cache).clear + end + + def test_default + object_id = Product.search_index.object_id + 3.times do + assert_equal object_id, Product.search_index.object_id + end + end + + def test_max_size + starting_ids = object_ids(20) + assert_equal starting_ids, object_ids(20) + Product.search_index(name: "other") + refute_equal starting_ids, object_ids(20) + end + + def test_thread_safe + object_ids = with_threads { object_ids(20) } + assert_equal object_ids[0], object_ids[1] + assert_equal object_ids[0], object_ids[2] + end + + # object ids can differ since threads progress at different speeds + # test to make sure doesn't crash + def test_thread_safe_max_size + with_threads { object_ids(1000) } + end + + private + + def object_ids(count) + count.times.map { |i| Product.search_index(name: "index#{i}").object_id } + end + + def with_threads + previous = Thread.report_on_exception + begin + Thread.report_on_exception = true + 3.times.map { Thread.new { yield } }.map(&:join).map(&:value) + ensure + Thread.report_on_exception = previous + end + end +end -- libgit2 0.21.0