1require 'active_support/cache/dalli_store'
 
2
 
3module ActiveSupport
 
4  module Cache
 
5    class AtomicDalliStore < DalliStore
 
6      def fetch(name, options = nil)
 
7        if block_given?
 
8          result = read(name, options)
 
 9
 
10          if result.nil?
 
11            result = instrument(:generate, name, options) do |payload|
 
12              yield
 
13            end
 
 
15            write(name, result, options)
 
16          else
 
17            instrument(:fetch_hit, name, options) { |payload| }
 
18          end
 
 
20          result
 
21        else
 
22          read(name, options)
 
23        end
 
24      end
 
 
26      def read(name, options = nil)
 
27        super.tap do |result|
 
28          if result.present?
 
29            return nil if lock!(name, options)
 
30          end
 
31        end
 
32      end
 
 
34      def write(name, value, options = nil)
 
35        expiry = (options && options[:expires_in]) || 0
 
36        options[:expires_in] = expiry + 20 unless expiry.zero?
 
37        ttl_set(ttl_key(name, options), expiry) && super
 
38      end
 
 
40      def delete(name, options = nil)
 
41        ttl_delete(ttl_key(name, options)) && super
 
42      end
 
 
44      private
 
 
46      def lock!(name, options)
 
47        key = ttl_key(name, options)
 
48        ttl_get(key) ? false : ttl_add(key)
 
49      end
 
 
51      def ttl_key(name, options)
 
52        "#{namespaced_key(name, options)}.ttl"
 
53      end
 
 
55      def ttl_get(key)
 
56        with { |c| c.get(key, raw: true) }
 
57      rescue Dalli::DalliError => e
 
58        logger.error("DalliError: #{e.message}") if logger
 
59        raise if raise_errors?
 
60        nil
 
61      end
 
 
63      def ttl_add(key)
 
64        with { |c| c.add(key, "", 10, raw: true) }
 
65      rescue Dalli::DalliError => e
 
66        logger.error("DalliError: #{e.message}") if logger
 
67        raise if raise_errors?
 
68        false
 
69      end
 
 
71      def ttl_set(key, expiry)
 
72        with { |c| c.set(key, "", expiry, raw: true) }
 
73      rescue Dalli::DalliError => e
 
74        logger.error("DalliError: #{e.message}") if logger
 
75        raise if raise_errors?
 
76        false
 
77      end
 
 
79      def ttl_delete(key)
 
80        with { |c| c.delete(key) }
 
81      rescue Dalli::DalliError => e
 
82        logger.error("DalliError: #{e.message}") if logger
 
83        raise if raise_errors?
 
84        false
 
85      end
 
86    end
 
87  end
 
88end