activesupport 5.0.7.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1018 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +39 -0
- data/lib/active_support.rb +99 -0
- data/lib/active_support/all.rb +3 -0
- data/lib/active_support/array_inquirer.rb +44 -0
- data/lib/active_support/backtrace_cleaner.rb +103 -0
- data/lib/active_support/benchmarkable.rb +49 -0
- data/lib/active_support/builder.rb +6 -0
- data/lib/active_support/cache.rb +701 -0
- data/lib/active_support/cache/file_store.rb +204 -0
- data/lib/active_support/cache/mem_cache_store.rb +207 -0
- data/lib/active_support/cache/memory_store.rb +167 -0
- data/lib/active_support/cache/null_store.rb +41 -0
- data/lib/active_support/cache/strategy/local_cache.rb +172 -0
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
- data/lib/active_support/callbacks.rb +791 -0
- data/lib/active_support/concern.rb +142 -0
- data/lib/active_support/concurrency/latch.rb +26 -0
- data/lib/active_support/concurrency/share_lock.rb +226 -0
- data/lib/active_support/configurable.rb +148 -0
- data/lib/active_support/core_ext.rb +4 -0
- data/lib/active_support/core_ext/array.rb +7 -0
- data/lib/active_support/core_ext/array/access.rb +90 -0
- data/lib/active_support/core_ext/array/conversions.rb +211 -0
- data/lib/active_support/core_ext/array/extract_options.rb +29 -0
- data/lib/active_support/core_ext/array/grouping.rb +107 -0
- data/lib/active_support/core_ext/array/inquiry.rb +17 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
- data/lib/active_support/core_ext/array/wrap.rb +46 -0
- data/lib/active_support/core_ext/benchmark.rb +14 -0
- data/lib/active_support/core_ext/big_decimal.rb +1 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/active_support/core_ext/class.rb +2 -0
- data/lib/active_support/core_ext/class/attribute.rb +128 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/active_support/core_ext/class/subclasses.rb +41 -0
- data/lib/active_support/core_ext/date.rb +5 -0
- data/lib/active_support/core_ext/date/acts_like.rb +8 -0
- data/lib/active_support/core_ext/date/blank.rb +12 -0
- data/lib/active_support/core_ext/date/calculations.rb +143 -0
- data/lib/active_support/core_ext/date/conversions.rb +95 -0
- data/lib/active_support/core_ext/date/zones.rb +6 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +14 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
- data/lib/active_support/core_ext/date_time.rb +5 -0
- data/lib/active_support/core_ext/date_time/acts_like.rb +14 -0
- data/lib/active_support/core_ext/date_time/blank.rb +12 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +199 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +105 -0
- data/lib/active_support/core_ext/digest/uuid.rb +51 -0
- data/lib/active_support/core_ext/enumerable.rb +146 -0
- data/lib/active_support/core_ext/file.rb +1 -0
- data/lib/active_support/core_ext/file/atomic.rb +68 -0
- data/lib/active_support/core_ext/hash.rb +9 -0
- data/lib/active_support/core_ext/hash/compact.rb +24 -0
- data/lib/active_support/core_ext/hash/conversions.rb +262 -0
- data/lib/active_support/core_ext/hash/deep_merge.rb +38 -0
- data/lib/active_support/core_ext/hash/except.rb +22 -0
- data/lib/active_support/core_ext/hash/indifferent_access.rb +23 -0
- data/lib/active_support/core_ext/hash/keys.rb +170 -0
- data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -0
- data/lib/active_support/core_ext/hash/slice.rb +48 -0
- data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
- data/lib/active_support/core_ext/integer.rb +3 -0
- data/lib/active_support/core_ext/integer/inflections.rb +29 -0
- data/lib/active_support/core_ext/integer/multiple.rb +10 -0
- data/lib/active_support/core_ext/integer/time.rb +29 -0
- data/lib/active_support/core_ext/kernel.rb +4 -0
- data/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
- data/lib/active_support/core_ext/kernel/concern.rb +12 -0
- data/lib/active_support/core_ext/kernel/debugger.rb +3 -0
- data/lib/active_support/core_ext/kernel/reporting.rb +43 -0
- data/lib/active_support/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/active_support/core_ext/load_error.rb +31 -0
- data/lib/active_support/core_ext/marshal.rb +22 -0
- data/lib/active_support/core_ext/module.rb +12 -0
- data/lib/active_support/core_ext/module/aliasing.rb +74 -0
- data/lib/active_support/core_ext/module/anonymous.rb +28 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +36 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
- data/lib/active_support/core_ext/module/concerning.rb +135 -0
- data/lib/active_support/core_ext/module/delegation.rb +216 -0
- data/lib/active_support/core_ext/module/deprecation.rb +23 -0
- data/lib/active_support/core_ext/module/introspection.rb +68 -0
- data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
- data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
- data/lib/active_support/core_ext/module/reachable.rb +8 -0
- data/lib/active_support/core_ext/module/remove_method.rb +35 -0
- data/lib/active_support/core_ext/name_error.rb +31 -0
- data/lib/active_support/core_ext/numeric.rb +4 -0
- data/lib/active_support/core_ext/numeric/bytes.rb +64 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +144 -0
- data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
- data/lib/active_support/core_ext/numeric/time.rb +74 -0
- data/lib/active_support/core_ext/object.rb +14 -0
- data/lib/active_support/core_ext/object/acts_like.rb +10 -0
- data/lib/active_support/core_ext/object/blank.rb +143 -0
- data/lib/active_support/core_ext/object/conversions.rb +4 -0
- data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
- data/lib/active_support/core_ext/object/duplicable.rb +124 -0
- data/lib/active_support/core_ext/object/inclusion.rb +27 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +28 -0
- data/lib/active_support/core_ext/object/json.rb +205 -0
- data/lib/active_support/core_ext/object/to_param.rb +1 -0
- data/lib/active_support/core_ext/object/to_query.rb +84 -0
- data/lib/active_support/core_ext/object/try.rb +146 -0
- data/lib/active_support/core_ext/object/with_options.rb +69 -0
- data/lib/active_support/core_ext/range.rb +4 -0
- data/lib/active_support/core_ext/range/conversions.rb +31 -0
- data/lib/active_support/core_ext/range/each.rb +21 -0
- data/lib/active_support/core_ext/range/include_range.rb +23 -0
- data/lib/active_support/core_ext/range/overlaps.rb +8 -0
- data/lib/active_support/core_ext/regexp.rb +5 -0
- data/lib/active_support/core_ext/securerandom.rb +23 -0
- data/lib/active_support/core_ext/string.rb +13 -0
- data/lib/active_support/core_ext/string/access.rb +104 -0
- data/lib/active_support/core_ext/string/behavior.rb +6 -0
- data/lib/active_support/core_ext/string/conversions.rb +57 -0
- data/lib/active_support/core_ext/string/exclude.rb +11 -0
- data/lib/active_support/core_ext/string/filters.rb +102 -0
- data/lib/active_support/core_ext/string/indent.rb +43 -0
- data/lib/active_support/core_ext/string/inflections.rb +244 -0
- data/lib/active_support/core_ext/string/inquiry.rb +13 -0
- data/lib/active_support/core_ext/string/multibyte.rb +53 -0
- data/lib/active_support/core_ext/string/output_safety.rb +260 -0
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/active_support/core_ext/string/strip.rb +23 -0
- data/lib/active_support/core_ext/string/zones.rb +14 -0
- data/lib/active_support/core_ext/struct.rb +3 -0
- data/lib/active_support/core_ext/time.rb +5 -0
- data/lib/active_support/core_ext/time/acts_like.rb +8 -0
- data/lib/active_support/core_ext/time/calculations.rb +290 -0
- data/lib/active_support/core_ext/time/compatibility.rb +14 -0
- data/lib/active_support/core_ext/time/conversions.rb +67 -0
- data/lib/active_support/core_ext/time/marshal.rb +3 -0
- data/lib/active_support/core_ext/time/zones.rb +111 -0
- data/lib/active_support/core_ext/uri.rb +24 -0
- data/lib/active_support/dependencies.rb +755 -0
- data/lib/active_support/dependencies/autoload.rb +77 -0
- data/lib/active_support/dependencies/interlock.rb +55 -0
- data/lib/active_support/deprecation.rb +43 -0
- data/lib/active_support/deprecation/behaviors.rb +90 -0
- data/lib/active_support/deprecation/instance_delegator.rb +37 -0
- data/lib/active_support/deprecation/method_wrappers.rb +70 -0
- data/lib/active_support/deprecation/proxy_wrappers.rb +149 -0
- data/lib/active_support/deprecation/reporting.rb +112 -0
- data/lib/active_support/descendants_tracker.rb +60 -0
- data/lib/active_support/duration.rb +235 -0
- data/lib/active_support/duration/iso8601_parser.rb +122 -0
- data/lib/active_support/duration/iso8601_serializer.rb +51 -0
- data/lib/active_support/evented_file_update_checker.rb +199 -0
- data/lib/active_support/execution_wrapper.rb +126 -0
- data/lib/active_support/executor.rb +6 -0
- data/lib/active_support/file_update_checker.rb +157 -0
- data/lib/active_support/gem_version.rb +15 -0
- data/lib/active_support/gzip.rb +36 -0
- data/lib/active_support/hash_with_indifferent_access.rb +329 -0
- data/lib/active_support/i18n.rb +13 -0
- data/lib/active_support/i18n_railtie.rb +115 -0
- data/lib/active_support/inflections.rb +70 -0
- data/lib/active_support/inflector.rb +7 -0
- data/lib/active_support/inflector/inflections.rb +242 -0
- data/lib/active_support/inflector/methods.rb +390 -0
- data/lib/active_support/inflector/transliterate.rb +112 -0
- data/lib/active_support/json.rb +2 -0
- data/lib/active_support/json/decoding.rb +74 -0
- data/lib/active_support/json/encoding.rb +127 -0
- data/lib/active_support/key_generator.rb +71 -0
- data/lib/active_support/lazy_load_hooks.rb +76 -0
- data/lib/active_support/locale/en.yml +135 -0
- data/lib/active_support/log_subscriber.rb +109 -0
- data/lib/active_support/log_subscriber/test_helper.rb +104 -0
- data/lib/active_support/logger.rb +106 -0
- data/lib/active_support/logger_silence.rb +28 -0
- data/lib/active_support/logger_thread_safe_level.rb +31 -0
- data/lib/active_support/message_encryptor.rb +114 -0
- data/lib/active_support/message_verifier.rb +134 -0
- data/lib/active_support/multibyte.rb +21 -0
- data/lib/active_support/multibyte/chars.rb +231 -0
- data/lib/active_support/multibyte/unicode.rb +413 -0
- data/lib/active_support/notifications.rb +212 -0
- data/lib/active_support/notifications/fanout.rb +157 -0
- data/lib/active_support/notifications/instrumenter.rb +91 -0
- data/lib/active_support/number_helper.rb +368 -0
- data/lib/active_support/number_helper/number_converter.rb +182 -0
- data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
- data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
- data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
- data/lib/active_support/option_merger.rb +25 -0
- data/lib/active_support/ordered_hash.rb +48 -0
- data/lib/active_support/ordered_options.rb +81 -0
- data/lib/active_support/per_thread_registry.rb +58 -0
- data/lib/active_support/proxy_object.rb +13 -0
- data/lib/active_support/rails.rb +27 -0
- data/lib/active_support/railtie.rb +51 -0
- data/lib/active_support/reloader.rb +129 -0
- data/lib/active_support/rescuable.rb +173 -0
- data/lib/active_support/security_utils.rb +27 -0
- data/lib/active_support/string_inquirer.rb +26 -0
- data/lib/active_support/subscriber.rb +120 -0
- data/lib/active_support/tagged_logging.rb +77 -0
- data/lib/active_support/test_case.rb +88 -0
- data/lib/active_support/testing/assertions.rb +99 -0
- data/lib/active_support/testing/autorun.rb +5 -0
- data/lib/active_support/testing/constant_lookup.rb +50 -0
- data/lib/active_support/testing/declarative.rb +26 -0
- data/lib/active_support/testing/deprecation.rb +36 -0
- data/lib/active_support/testing/file_fixtures.rb +34 -0
- data/lib/active_support/testing/isolation.rb +115 -0
- data/lib/active_support/testing/method_call_assertions.rb +41 -0
- data/lib/active_support/testing/setup_and_teardown.rb +50 -0
- data/lib/active_support/testing/stream.rb +42 -0
- data/lib/active_support/testing/tagged_logging.rb +25 -0
- data/lib/active_support/testing/time_helpers.rb +136 -0
- data/lib/active_support/time.rb +18 -0
- data/lib/active_support/time_with_zone.rb +511 -0
- data/lib/active_support/values/time_zone.rb +484 -0
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +8 -0
- data/lib/active_support/xml_mini.rb +209 -0
- data/lib/active_support/xml_mini/jdom.rb +181 -0
- data/lib/active_support/xml_mini/libxml.rb +77 -0
- data/lib/active_support/xml_mini/libxmlsax.rb +82 -0
- data/lib/active_support/xml_mini/nokogiri.rb +81 -0
- data/lib/active_support/xml_mini/nokogirisax.rb +85 -0
- data/lib/active_support/xml_mini/rexml.rb +128 -0
- metadata +349 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'active_support/core_ext/marshal'
|
2
|
+
require 'active_support/core_ext/file/atomic'
|
3
|
+
require 'active_support/core_ext/string/conversions'
|
4
|
+
require 'uri/common'
|
5
|
+
|
6
|
+
module ActiveSupport
|
7
|
+
module Cache
|
8
|
+
# A cache store implementation which stores everything on the filesystem.
|
9
|
+
#
|
10
|
+
# FileStore implements the Strategy::LocalCache strategy which implements
|
11
|
+
# an in-memory cache inside of a block.
|
12
|
+
class FileStore < Store
|
13
|
+
prepend Strategy::LocalCache
|
14
|
+
attr_reader :cache_path
|
15
|
+
|
16
|
+
DIR_FORMATTER = "%03X"
|
17
|
+
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
|
18
|
+
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
|
19
|
+
EXCLUDED_DIRS = ['.', '..'].freeze
|
20
|
+
GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
|
21
|
+
|
22
|
+
def initialize(cache_path, options = nil)
|
23
|
+
super(options)
|
24
|
+
@cache_path = cache_path.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
# Deletes all items from the cache. In this case it deletes all the entries in the specified
|
28
|
+
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
|
29
|
+
# config file when using +FileStore+ because everything in that directory will be deleted.
|
30
|
+
def clear(options = nil)
|
31
|
+
root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
|
32
|
+
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
|
33
|
+
rescue Errno::ENOENT
|
34
|
+
end
|
35
|
+
|
36
|
+
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
37
|
+
def cleanup(options = nil)
|
38
|
+
options = merged_options(options)
|
39
|
+
search_dir(cache_path) do |fname|
|
40
|
+
key = file_path_key(fname)
|
41
|
+
entry = read_entry(key, options)
|
42
|
+
delete_entry(key, options) if entry && entry.expired?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Increments an already existing integer value that is stored in the cache.
|
47
|
+
# If the key is not found nothing is done.
|
48
|
+
def increment(name, amount = 1, options = nil)
|
49
|
+
modify_value(name, amount, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Decrements an already existing integer value that is stored in the cache.
|
53
|
+
# If the key is not found nothing is done.
|
54
|
+
def decrement(name, amount = 1, options = nil)
|
55
|
+
modify_value(name, -amount, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_matched(matcher, options = nil)
|
59
|
+
options = merged_options(options)
|
60
|
+
instrument(:delete_matched, matcher.inspect) do
|
61
|
+
matcher = key_matcher(matcher, options)
|
62
|
+
search_dir(cache_path) do |path|
|
63
|
+
key = file_path_key(path)
|
64
|
+
delete_entry(path, options) if key.match(matcher)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def read_entry(key, options)
|
72
|
+
if File.exist?(key)
|
73
|
+
File.open(key) { |f| Marshal.load(f) }
|
74
|
+
end
|
75
|
+
rescue => e
|
76
|
+
logger.error("FileStoreError (#{e}): #{e.message}") if logger
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_entry(key, entry, options)
|
81
|
+
return false if options[:unless_exist] && File.exist?(key)
|
82
|
+
ensure_cache_path(File.dirname(key))
|
83
|
+
File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)}
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_entry(key, options)
|
88
|
+
if File.exist?(key)
|
89
|
+
begin
|
90
|
+
File.delete(key)
|
91
|
+
delete_empty_directories(File.dirname(key))
|
92
|
+
true
|
93
|
+
rescue => e
|
94
|
+
# Just in case the error was caused by another process deleting the file first.
|
95
|
+
raise e if File.exist?(key)
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
# Lock a file for a block so only one process can modify it at a time.
|
103
|
+
def lock_file(file_name, &block) # :nodoc:
|
104
|
+
if File.exist?(file_name)
|
105
|
+
File.open(file_name, 'r+') do |f|
|
106
|
+
begin
|
107
|
+
f.flock File::LOCK_EX
|
108
|
+
yield
|
109
|
+
ensure
|
110
|
+
f.flock File::LOCK_UN
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
yield
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Translate a key into a file path.
|
119
|
+
def normalize_key(key, options)
|
120
|
+
key = super
|
121
|
+
fname = URI.encode_www_form_component(key)
|
122
|
+
|
123
|
+
if fname.size > FILEPATH_MAX_SIZE
|
124
|
+
fname = Digest::MD5.hexdigest(key)
|
125
|
+
end
|
126
|
+
|
127
|
+
hash = Zlib.adler32(fname)
|
128
|
+
hash, dir_1 = hash.divmod(0x1000)
|
129
|
+
dir_2 = hash.modulo(0x1000)
|
130
|
+
fname_paths = []
|
131
|
+
|
132
|
+
# Make sure file name doesn't exceed file system limits.
|
133
|
+
begin
|
134
|
+
fname_paths << fname[0, FILENAME_MAX_SIZE]
|
135
|
+
fname = fname[FILENAME_MAX_SIZE..-1]
|
136
|
+
end until fname.blank?
|
137
|
+
|
138
|
+
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
|
139
|
+
end
|
140
|
+
|
141
|
+
def key_file_path(key)
|
142
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
143
|
+
`key_file_path` is deprecated and will be removed from Rails 5.1.
|
144
|
+
Please use `normalize_key` which will return a fully resolved key or nothing.
|
145
|
+
MESSAGE
|
146
|
+
key
|
147
|
+
end
|
148
|
+
|
149
|
+
# Translate a file path into a key.
|
150
|
+
def file_path_key(path)
|
151
|
+
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
|
152
|
+
URI.decode_www_form_component(fname, Encoding::UTF_8)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Delete empty directories in the cache.
|
156
|
+
def delete_empty_directories(dir)
|
157
|
+
return if File.realpath(dir) == File.realpath(cache_path)
|
158
|
+
if exclude_from(dir, EXCLUDED_DIRS).empty?
|
159
|
+
Dir.delete(dir) rescue nil
|
160
|
+
delete_empty_directories(File.dirname(dir))
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Make sure a file path's directories exist.
|
165
|
+
def ensure_cache_path(path)
|
166
|
+
FileUtils.makedirs(path) unless File.exist?(path)
|
167
|
+
end
|
168
|
+
|
169
|
+
def search_dir(dir, &callback)
|
170
|
+
return if !File.exist?(dir)
|
171
|
+
Dir.foreach(dir) do |d|
|
172
|
+
next if EXCLUDED_DIRS.include?(d)
|
173
|
+
name = File.join(dir, d)
|
174
|
+
if File.directory?(name)
|
175
|
+
search_dir(name, &callback)
|
176
|
+
else
|
177
|
+
callback.call name
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Modifies the amount of an already existing integer value that is stored in the cache.
|
183
|
+
# If the key is not found nothing is done.
|
184
|
+
def modify_value(name, amount, options)
|
185
|
+
file_name = normalize_key(name, options)
|
186
|
+
|
187
|
+
lock_file(file_name) do
|
188
|
+
options = merged_options(options)
|
189
|
+
|
190
|
+
if num = read(name, options)
|
191
|
+
num = num.to_i + amount
|
192
|
+
write(name, num, options)
|
193
|
+
num
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Exclude entries from source directory
|
199
|
+
def exclude_from(source, excludes)
|
200
|
+
Dir.entries(source).reject { |f| excludes.include?(f) }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
begin
|
2
|
+
require 'dalli'
|
3
|
+
rescue LoadError => e
|
4
|
+
$stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
|
5
|
+
raise e
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'digest/md5'
|
9
|
+
require 'active_support/core_ext/marshal'
|
10
|
+
require 'active_support/core_ext/array/extract_options'
|
11
|
+
|
12
|
+
module ActiveSupport
|
13
|
+
module Cache
|
14
|
+
# A cache store implementation which stores data in Memcached:
|
15
|
+
# http://memcached.org/
|
16
|
+
#
|
17
|
+
# This is currently the most popular cache store for production websites.
|
18
|
+
#
|
19
|
+
# Special features:
|
20
|
+
# - Clustering and load balancing. One can specify multiple memcached servers,
|
21
|
+
# and MemCacheStore will load balance between all available servers. If a
|
22
|
+
# server goes down, then MemCacheStore will ignore it until it comes back up.
|
23
|
+
#
|
24
|
+
# MemCacheStore implements the Strategy::LocalCache strategy which implements
|
25
|
+
# an in-memory cache inside of a block.
|
26
|
+
class MemCacheStore < Store
|
27
|
+
# Provide support for raw values in the local cache strategy.
|
28
|
+
module LocalCacheWithRaw # :nodoc:
|
29
|
+
protected
|
30
|
+
def read_entry(key, options)
|
31
|
+
entry = super
|
32
|
+
if options[:raw] && local_cache && entry
|
33
|
+
entry = deserialize_entry(entry.value)
|
34
|
+
end
|
35
|
+
entry
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_entry(key, entry, options) # :nodoc:
|
39
|
+
if options[:raw] && local_cache
|
40
|
+
raw_entry = Entry.new(entry.value.to_s)
|
41
|
+
raw_entry.expires_at = entry.expires_at
|
42
|
+
super(key, raw_entry, options)
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
prepend Strategy::LocalCache
|
50
|
+
prepend LocalCacheWithRaw
|
51
|
+
|
52
|
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
53
|
+
|
54
|
+
# Creates a new Dalli::Client instance with specified addresses and options.
|
55
|
+
# By default address is equal localhost:11211.
|
56
|
+
#
|
57
|
+
# ActiveSupport::Cache::MemCacheStore.build_mem_cache
|
58
|
+
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
|
59
|
+
# ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
|
60
|
+
# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
|
61
|
+
def self.build_mem_cache(*addresses) # :nodoc:
|
62
|
+
addresses = addresses.flatten
|
63
|
+
options = addresses.extract_options!
|
64
|
+
addresses = ["localhost:11211"] if addresses.empty?
|
65
|
+
Dalli::Client.new(addresses, options)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates a new MemCacheStore object, with the given memcached server
|
69
|
+
# addresses. Each address is either a host name, or a host-with-port string
|
70
|
+
# in the form of "host_name:port". For example:
|
71
|
+
#
|
72
|
+
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
73
|
+
#
|
74
|
+
# If no addresses are specified, then MemCacheStore will connect to
|
75
|
+
# localhost port 11211 (the default memcached port).
|
76
|
+
def initialize(*addresses)
|
77
|
+
addresses = addresses.flatten
|
78
|
+
options = addresses.extract_options!
|
79
|
+
super(options)
|
80
|
+
|
81
|
+
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
82
|
+
raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance."
|
83
|
+
end
|
84
|
+
if addresses.first.is_a?(Dalli::Client)
|
85
|
+
@data = addresses.first
|
86
|
+
else
|
87
|
+
mem_cache_options = options.dup
|
88
|
+
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
|
89
|
+
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Reads multiple values from the cache using a single call to the
|
94
|
+
# servers for all keys. Options can be passed in the last argument.
|
95
|
+
def read_multi(*names)
|
96
|
+
options = names.extract_options!
|
97
|
+
options = merged_options(options)
|
98
|
+
|
99
|
+
keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}]
|
100
|
+
raw_values = @data.get_multi(keys_to_names.keys)
|
101
|
+
values = {}
|
102
|
+
raw_values.each do |key, value|
|
103
|
+
entry = deserialize_entry(value)
|
104
|
+
values[keys_to_names[key]] = entry.value unless entry.expired?
|
105
|
+
end
|
106
|
+
values
|
107
|
+
end
|
108
|
+
|
109
|
+
# Increment a cached value. This method uses the memcached incr atomic
|
110
|
+
# operator and can only be used on values written with the :raw option.
|
111
|
+
# Calling it on a value not stored with :raw will initialize that value
|
112
|
+
# to zero.
|
113
|
+
def increment(name, amount = 1, options = nil) # :nodoc:
|
114
|
+
options = merged_options(options)
|
115
|
+
instrument(:increment, name, :amount => amount) do
|
116
|
+
rescue_error_with nil do
|
117
|
+
@data.incr(normalize_key(name, options), amount)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Decrement a cached value. This method uses the memcached decr atomic
|
123
|
+
# operator and can only be used on values written with the :raw option.
|
124
|
+
# Calling it on a value not stored with :raw will initialize that value
|
125
|
+
# to zero.
|
126
|
+
def decrement(name, amount = 1, options = nil) # :nodoc:
|
127
|
+
options = merged_options(options)
|
128
|
+
instrument(:decrement, name, :amount => amount) do
|
129
|
+
rescue_error_with nil do
|
130
|
+
@data.decr(normalize_key(name, options), amount)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Clear the entire cache on all memcached servers. This method should
|
136
|
+
# be used with care when shared cache is being used.
|
137
|
+
def clear(options = nil)
|
138
|
+
rescue_error_with(nil) { @data.flush_all }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Get the statistics from the memcached servers.
|
142
|
+
def stats
|
143
|
+
@data.stats
|
144
|
+
end
|
145
|
+
|
146
|
+
protected
|
147
|
+
# Read an entry from the cache.
|
148
|
+
def read_entry(key, options) # :nodoc:
|
149
|
+
rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
|
150
|
+
end
|
151
|
+
|
152
|
+
# Write an entry to the cache.
|
153
|
+
def write_entry(key, entry, options) # :nodoc:
|
154
|
+
method = options && options[:unless_exist] ? :add : :set
|
155
|
+
value = options[:raw] ? entry.value.to_s : entry
|
156
|
+
expires_in = options[:expires_in].to_i
|
157
|
+
if expires_in > 0 && !options[:raw]
|
158
|
+
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
159
|
+
expires_in += 5.minutes
|
160
|
+
end
|
161
|
+
rescue_error_with false do
|
162
|
+
@data.send(method, key, value, expires_in, options)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Delete an entry from the cache.
|
167
|
+
def delete_entry(key, options) # :nodoc:
|
168
|
+
rescue_error_with(false) { @data.delete(key) }
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
# Memcache keys are binaries. So we need to force their encoding to binary
|
174
|
+
# before applying the regular expression to ensure we are escaping all
|
175
|
+
# characters properly.
|
176
|
+
def normalize_key(key, options)
|
177
|
+
key = super.dup
|
178
|
+
key = key.force_encoding(Encoding::ASCII_8BIT)
|
179
|
+
key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
180
|
+
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
|
181
|
+
key
|
182
|
+
end
|
183
|
+
|
184
|
+
def escape_key(key)
|
185
|
+
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
186
|
+
`escape_key` is deprecated and will be removed from Rails 5.1.
|
187
|
+
Please use `normalize_key` which will return a fully resolved key or nothing.
|
188
|
+
MESSAGE
|
189
|
+
key
|
190
|
+
end
|
191
|
+
|
192
|
+
def deserialize_entry(raw_value)
|
193
|
+
if raw_value
|
194
|
+
entry = Marshal.load(raw_value) rescue raw_value
|
195
|
+
entry.is_a?(Entry) ? entry : Entry.new(entry)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def rescue_error_with(fallback)
|
200
|
+
yield
|
201
|
+
rescue Dalli::DalliError => e
|
202
|
+
logger.error("DalliError (#{e}): #{e.message}") if logger
|
203
|
+
fallback
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module Cache
|
5
|
+
# A cache store implementation which stores everything into memory in the
|
6
|
+
# same process. If you're running multiple Ruby on Rails server processes
|
7
|
+
# (which is the case if you're using mongrel_cluster or Phusion Passenger),
|
8
|
+
# then this means that Rails server process instances won't be able
|
9
|
+
# to share cache data with each other and this may not be the most
|
10
|
+
# appropriate cache in that scenario.
|
11
|
+
#
|
12
|
+
# This cache has a bounded size specified by the :size options to the
|
13
|
+
# initializer (default is 32Mb). When the cache exceeds the allotted size,
|
14
|
+
# a cleanup will occur which tries to prune the cache down to three quarters
|
15
|
+
# of the maximum size by removing the least recently used entries.
|
16
|
+
#
|
17
|
+
# MemoryStore is thread-safe.
|
18
|
+
class MemoryStore < Store
|
19
|
+
def initialize(options = nil)
|
20
|
+
options ||= {}
|
21
|
+
super(options)
|
22
|
+
@data = {}
|
23
|
+
@key_access = {}
|
24
|
+
@max_size = options[:size] || 32.megabytes
|
25
|
+
@max_prune_time = options[:max_prune_time] || 2
|
26
|
+
@cache_size = 0
|
27
|
+
@monitor = Monitor.new
|
28
|
+
@pruning = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear(options = nil)
|
32
|
+
synchronize do
|
33
|
+
@data.clear
|
34
|
+
@key_access.clear
|
35
|
+
@cache_size = 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
40
|
+
def cleanup(options = nil)
|
41
|
+
options = merged_options(options)
|
42
|
+
instrument(:cleanup, :size => @data.size) do
|
43
|
+
keys = synchronize{ @data.keys }
|
44
|
+
keys.each do |key|
|
45
|
+
entry = @data[key]
|
46
|
+
delete_entry(key, options) if entry && entry.expired?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# To ensure entries fit within the specified memory prune the cache by removing the least
|
52
|
+
# recently accessed entries.
|
53
|
+
def prune(target_size, max_time = nil)
|
54
|
+
return if pruning?
|
55
|
+
@pruning = true
|
56
|
+
begin
|
57
|
+
start_time = Time.now
|
58
|
+
cleanup
|
59
|
+
instrument(:prune, target_size, :from => @cache_size) do
|
60
|
+
keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} }
|
61
|
+
keys.each do |key|
|
62
|
+
delete_entry(key, options)
|
63
|
+
return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
ensure
|
67
|
+
@pruning = false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns true if the cache is currently being pruned.
|
72
|
+
def pruning?
|
73
|
+
@pruning
|
74
|
+
end
|
75
|
+
|
76
|
+
# Increment an integer value in the cache.
|
77
|
+
def increment(name, amount = 1, options = nil)
|
78
|
+
modify_value(name, amount, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Decrement an integer value in the cache.
|
82
|
+
def decrement(name, amount = 1, options = nil)
|
83
|
+
modify_value(name, -amount, options)
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete_matched(matcher, options = nil)
|
87
|
+
options = merged_options(options)
|
88
|
+
instrument(:delete_matched, matcher.inspect) do
|
89
|
+
matcher = key_matcher(matcher, options)
|
90
|
+
keys = synchronize { @data.keys }
|
91
|
+
keys.each do |key|
|
92
|
+
delete_entry(key, options) if key.match(matcher)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def inspect # :nodoc:
|
98
|
+
"<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Synchronize calls to the cache. This should be called wherever the underlying cache implementation
|
102
|
+
# is not thread safe.
|
103
|
+
def synchronize(&block) # :nodoc:
|
104
|
+
@monitor.synchronize(&block)
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
PER_ENTRY_OVERHEAD = 240
|
110
|
+
|
111
|
+
def cached_size(key, entry) # :nodoc:
|
112
|
+
key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_entry(key, options) # :nodoc:
|
116
|
+
entry = @data[key]
|
117
|
+
synchronize do
|
118
|
+
if entry
|
119
|
+
@key_access[key] = Time.now.to_f
|
120
|
+
else
|
121
|
+
@key_access.delete(key)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
entry
|
125
|
+
end
|
126
|
+
|
127
|
+
def write_entry(key, entry, options) # :nodoc:
|
128
|
+
entry.dup_value!
|
129
|
+
synchronize do
|
130
|
+
old_entry = @data[key]
|
131
|
+
return false if @data.key?(key) && options[:unless_exist]
|
132
|
+
if old_entry
|
133
|
+
@cache_size -= (old_entry.size - entry.size)
|
134
|
+
else
|
135
|
+
@cache_size += cached_size(key, entry)
|
136
|
+
end
|
137
|
+
@key_access[key] = Time.now.to_f
|
138
|
+
@data[key] = entry
|
139
|
+
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
|
140
|
+
true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def delete_entry(key, options) # :nodoc:
|
145
|
+
synchronize do
|
146
|
+
@key_access.delete(key)
|
147
|
+
entry = @data.delete(key)
|
148
|
+
@cache_size -= cached_size(key, entry) if entry
|
149
|
+
!!entry
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def modify_value(name, amount, options)
|
156
|
+
synchronize do
|
157
|
+
options = merged_options(options)
|
158
|
+
if num = read(name, options)
|
159
|
+
num = num.to_i + amount
|
160
|
+
write(name, num, options)
|
161
|
+
num
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|