lib/roda/plugins/per_thread_caching.rb



# frozen-string-literal: true

#
class Roda
  module RodaPlugins
    # The per_thread_caching plugin changes the default cache
    # from being a shared thread safe cache to a separate cache per
    # thread.  This means getting or setting values no longer
    # needs a mutex on non-MRI ruby implementations, which may be
    # faster when using a thread pool.  However, since the caches
    # are no longer shared, this will take up more memory.
    #
    # Note that it does not make sense to use this plugin on MRI,
    # since the default cache on MRI doesn't use a mutex as it
    # is already thread safe due to the GVL.
    #
    # Using this plugin changes the matcher regexp cache to use
    # per-thread caches, and changes the default for future
    # thread-safe caches to use per-thread caches.
    #
    # If you want the render plugin's template cache to use
    # per-thread caches, you should load this plugin before the
    # render plugin.
    module PerThreadCaching
      def self.configure(app)
        app::RodaRequest.match_pattern_cache = app.thread_safe_cache
      end

      class Cache
        # Mutex used to ensure multiple per-thread caches
        # don't use the same key
        MUTEX = ::Mutex.new

        n = 0
        # Auto incrementing number proc used to make sure
        # multiple thread-thread caches don't use the same key.
        N = lambda{MUTEX.synchronize{n += 1}}

        # Store unique symbol used to look up in the per
        # thread caches.
        def initialize
          @o = :"roda_per_thread_cache_#{N.call}"
        end

        # Return the current thread's cached value.
        def [](key)
          _hash[key]
        end

        # Set the current thread's cached value.
        def []=(key, value)
          _hash[key] = value
        end

        private

        # The current thread's cache.
        def _hash
          ::Thread.current[@o] ||= {}
        end
      end

      module ClassMethods
        # Use the per-thread cache instead of the default cache.
        def thread_safe_cache
          Cache.new
        end
      end
    end

    register_plugin(:per_thread_caching, PerThreadCaching)
  end
end