summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/cacher.rb
blob: 3a75dc88d67716e9cfc4fd74605bc49f5449a8f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
module Puppet::Util::Cacher
    module Expirer
        attr_reader :timestamp

        # Cause all cached values to be considered expired.
        def expire
            @timestamp = Time.now
        end

        # Is the provided timestamp earlier than our expiration timestamp?
        # If it is, then the associated value is expired.
        def dependent_data_expired?(ts)
            return false unless timestamp

            return timestamp > ts
        end
    end

    extend Expirer

    # Our module has been extended in a class; we can only add the Instance methods,
    # which become *class* methods in the class.
    def self.extended(other)
        class << other
            extend ClassMethods
            include InstanceMethods
        end
    end

    # Our module has been included in a class, which means the class gets the class methods
    # and all of its instances get the instance methods.
    def self.included(other)
        other.extend(ClassMethods)
        other.send(:include, InstanceMethods)
    end

    # Methods that can get added to a class.
    module ClassMethods
        # Provide a means of defining an attribute whose value will be cached.
        # Must provide a block capable of defining the value if it's flushed..
        def cached_attr(name, options = {}, &block)
            init_method = "init_#{name}"
            define_method(init_method, &block)

            define_method(name) do
                cached_value(name)
            end

            define_method(name.to_s + "=") do |value|
                # Make sure the cache timestamp is set
                cache_timestamp
                value_cache[name] = value
            end

            if ttl = options[:ttl]
                set_attr_ttl(name, ttl)
            end
        end

        def attr_ttl(name)
            return nil unless defined?(@attr_ttls) and @attr_ttls
            @attr_ttls[name]
        end

        def set_attr_ttl(name, value)
            @attr_ttls ||= {}
            @attr_ttls[name] = Integer(value)
        end
    end

    # Methods that get added to instances.
    module InstanceMethods
        def expire
            # Only expire if we have an expirer.  This is
            # mostly so that we can comfortably handle cases
            # like Puppet::Type instances, which use their
            # catalog as their expirer, and they often don't
            # have a catalog.
            if e = expirer
                e.expire
            end
        end

        def expirer
            Puppet::Util::Cacher
        end

        private

        def cache_timestamp
            unless defined?(@cache_timestamp)
                @cache_timestamp = Time.now
            end
            @cache_timestamp
        end

        def cached_value(name)
            # Allow a nil expirer, in which case we regenerate the value every time.
            if expired_by_expirer?(name)
                value_cache.clear
                @cache_timestamp = Time.now
            elsif expired_by_ttl?(name)
                value_cache.delete(name)
            end
            unless value_cache.include?(name)
                value_cache[name] = send("init_#{name}")
            end
            value_cache[name]
        end

        def expired_by_expirer?(name)
            if expirer.nil?
                return true unless self.class.attr_ttl(name)
            end
            return expirer.dependent_data_expired?(cache_timestamp)
        end

        def expired_by_ttl?(name)
            return false unless self.class.respond_to?(:attr_ttl)
            return false unless ttl = self.class.attr_ttl(name)

            @ttl_timestamps ||= {}
            @ttl_timestamps[name] ||= Time.now

            return (Time.now - @ttl_timestamps[name]) > ttl
        end

        def value_cache
            unless defined?(@value_cache) and @value_cache
                @value_cache = {}
            end
            @value_cache
        end
    end
end