activesupport 5.1.7 → 7.0.4.1

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.

Files changed (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +259 -585
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -5
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +2 -0
  7. data/lib/active_support/array_inquirer.rb +4 -2
  8. data/lib/active_support/backtrace_cleaner.rb +33 -5
  9. data/lib/active_support/benchmarkable.rb +5 -3
  10. data/lib/active_support/builder.rb +2 -0
  11. data/lib/active_support/cache/file_store.rb +50 -43
  12. data/lib/active_support/cache/mem_cache_store.rb +194 -67
  13. data/lib/active_support/cache/memory_store.rb +70 -34
  14. data/lib/active_support/cache/null_store.rb +18 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +474 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +73 -50
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
  18. data/lib/active_support/cache.rb +556 -220
  19. data/lib/active_support/callbacks.rb +264 -159
  20. data/lib/active_support/code_generator.rb +65 -0
  21. data/lib/active_support/concern.rb +81 -8
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  23. data/lib/active_support/concurrency/share_lock.rb +4 -3
  24. data/lib/active_support/configurable.rb +17 -16
  25. data/lib/active_support/configuration_file.rb +51 -0
  26. data/lib/active_support/core_ext/array/access.rb +18 -8
  27. data/lib/active_support/core_ext/array/conversions.rb +20 -17
  28. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +8 -6
  32. data/lib/active_support/core_ext/array/inquiry.rb +4 -2
  33. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  34. data/lib/active_support/core_ext/array.rb +4 -1
  35. data/lib/active_support/core_ext/benchmark.rb +4 -2
  36. data/lib/active_support/core_ext/big_decimal/conversions.rb +3 -1
  37. data/lib/active_support/core_ext/big_decimal.rb +2 -0
  38. data/lib/active_support/core_ext/class/attribute.rb +50 -47
  39. data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -0
  40. data/lib/active_support/core_ext/class/subclasses.rb +10 -24
  41. data/lib/active_support/core_ext/class.rb +2 -0
  42. data/lib/active_support/core_ext/date/acts_like.rb +2 -0
  43. data/lib/active_support/core_ext/date/blank.rb +3 -1
  44. data/lib/active_support/core_ext/date/calculations.rb +17 -14
  45. data/lib/active_support/core_ext/date/conversions.rb +24 -22
  46. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  47. data/lib/active_support/core_ext/date/zones.rb +2 -0
  48. data/lib/active_support/core_ext/date.rb +3 -0
  49. data/lib/active_support/core_ext/date_and_time/calculations.rb +65 -41
  50. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -1
  51. data/lib/active_support/core_ext/date_and_time/zones.rb +2 -1
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +2 -0
  53. data/lib/active_support/core_ext/date_time/blank.rb +3 -1
  54. data/lib/active_support/core_ext/date_time/calculations.rb +3 -1
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +7 -5
  56. data/lib/active_support/core_ext/date_time/conversions.rb +15 -14
  57. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  58. data/lib/active_support/core_ext/date_time.rb +3 -0
  59. data/lib/active_support/core_ext/digest/uuid.rb +42 -14
  60. data/lib/active_support/core_ext/digest.rb +3 -0
  61. data/lib/active_support/core_ext/enumerable.rb +244 -72
  62. data/lib/active_support/core_ext/file/atomic.rb +6 -2
  63. data/lib/active_support/core_ext/file.rb +2 -0
  64. data/lib/active_support/core_ext/hash/conversions.rb +7 -6
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +4 -2
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
  69. data/lib/active_support/core_ext/hash/keys.rb +4 -31
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  71. data/lib/active_support/core_ext/hash/slice.rb +8 -29
  72. data/lib/active_support/core_ext/hash.rb +3 -2
  73. data/lib/active_support/core_ext/integer/inflections.rb +2 -0
  74. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  75. data/lib/active_support/core_ext/integer/time.rb +7 -14
  76. data/lib/active_support/core_ext/integer.rb +2 -0
  77. data/lib/active_support/core_ext/kernel/concern.rb +2 -0
  78. data/lib/active_support/core_ext/kernel/reporting.rb +6 -4
  79. data/lib/active_support/core_ext/kernel/singleton_class.rb +3 -1
  80. data/lib/active_support/core_ext/kernel.rb +2 -1
  81. data/lib/active_support/core_ext/load_error.rb +3 -8
  82. data/lib/active_support/core_ext/module/aliasing.rb +2 -0
  83. data/lib/active_support/core_ext/module/anonymous.rb +2 -0
  84. data/lib/active_support/core_ext/module/attr_internal.rb +4 -2
  85. data/lib/active_support/core_ext/module/attribute_accessors.rb +46 -56
  86. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +36 -27
  87. data/lib/active_support/core_ext/module/concerning.rb +15 -10
  88. data/lib/active_support/core_ext/module/delegation.rb +97 -58
  89. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  90. data/lib/active_support/core_ext/module/introspection.rb +18 -15
  91. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  93. data/lib/active_support/core_ext/module.rb +3 -1
  94. data/lib/active_support/core_ext/name_error.rb +30 -2
  95. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +134 -129
  97. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +7 -15
  99. data/lib/active_support/core_ext/numeric.rb +3 -1
  100. data/lib/active_support/core_ext/object/acts_like.rb +41 -6
  101. data/lib/active_support/core_ext/object/blank.rb +15 -5
  102. data/lib/active_support/core_ext/object/conversions.rb +2 -0
  103. data/lib/active_support/core_ext/object/deep_dup.rb +3 -1
  104. data/lib/active_support/core_ext/object/duplicable.rb +16 -110
  105. data/lib/active_support/core_ext/object/inclusion.rb +2 -0
  106. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  107. data/lib/active_support/core_ext/object/json.rb +51 -26
  108. data/lib/active_support/core_ext/object/to_param.rb +2 -0
  109. data/lib/active_support/core_ext/object/to_query.rb +4 -2
  110. data/lib/active_support/core_ext/object/try.rb +26 -14
  111. data/lib/active_support/core_ext/object/with_options.rb +24 -3
  112. data/lib/active_support/core_ext/object.rb +2 -0
  113. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  114. data/lib/active_support/core_ext/pathname.rb +3 -0
  115. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  116. data/lib/active_support/core_ext/range/conversions.rb +35 -25
  117. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  118. data/lib/active_support/core_ext/range/each.rb +6 -3
  119. data/lib/active_support/core_ext/range/include_time_with_zone.rb +7 -0
  120. data/lib/active_support/core_ext/range/overlaps.rb +3 -1
  121. data/lib/active_support/core_ext/range.rb +4 -1
  122. data/lib/active_support/core_ext/regexp.rb +10 -5
  123. data/lib/active_support/core_ext/securerandom.rb +25 -3
  124. data/lib/active_support/core_ext/string/access.rb +7 -16
  125. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  126. data/lib/active_support/core_ext/string/conversions.rb +5 -2
  127. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  128. data/lib/active_support/core_ext/string/filters.rb +44 -1
  129. data/lib/active_support/core_ext/string/indent.rb +2 -0
  130. data/lib/active_support/core_ext/string/inflections.rb +69 -16
  131. data/lib/active_support/core_ext/string/inquiry.rb +4 -1
  132. data/lib/active_support/core_ext/string/multibyte.rb +9 -4
  133. data/lib/active_support/core_ext/string/output_safety.rb +135 -27
  134. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  135. data/lib/active_support/core_ext/string/strip.rb +5 -1
  136. data/lib/active_support/core_ext/string/zones.rb +2 -0
  137. data/lib/active_support/core_ext/string.rb +2 -0
  138. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  139. data/lib/active_support/core_ext/symbol.rb +3 -0
  140. data/lib/active_support/core_ext/time/acts_like.rb +2 -0
  141. data/lib/active_support/core_ext/time/calculations.rb +81 -24
  142. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  143. data/lib/active_support/core_ext/time/conversions.rb +17 -12
  144. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  145. data/lib/active_support/core_ext/time/zones.rb +12 -25
  146. data/lib/active_support/core_ext/time.rb +3 -0
  147. data/lib/active_support/core_ext/uri.rb +4 -23
  148. data/lib/active_support/core_ext.rb +4 -1
  149. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  150. data/lib/active_support/current_attributes.rb +226 -0
  151. data/lib/active_support/dependencies/autoload.rb +2 -0
  152. data/lib/active_support/dependencies/interlock.rb +12 -18
  153. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  154. data/lib/active_support/dependencies.rb +59 -715
  155. data/lib/active_support/deprecation/behaviors.rb +48 -13
  156. data/lib/active_support/deprecation/constant_accessor.rb +4 -2
  157. data/lib/active_support/deprecation/disallowed.rb +56 -0
  158. data/lib/active_support/deprecation/instance_delegator.rb +2 -1
  159. data/lib/active_support/deprecation/method_wrappers.rb +29 -21
  160. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -8
  161. data/lib/active_support/deprecation/reporting.rb +54 -9
  162. data/lib/active_support/deprecation.rb +10 -3
  163. data/lib/active_support/descendants_tracker.rb +192 -34
  164. data/lib/active_support/digest.rb +22 -0
  165. data/lib/active_support/duration/iso8601_parser.rb +9 -9
  166. data/lib/active_support/duration/iso8601_serializer.rb +29 -15
  167. data/lib/active_support/duration.rb +158 -72
  168. data/lib/active_support/encrypted_configuration.rb +56 -0
  169. data/lib/active_support/encrypted_file.rb +129 -0
  170. data/lib/active_support/environment_inquirer.rb +20 -0
  171. data/lib/active_support/error_reporter.rb +117 -0
  172. data/lib/active_support/evented_file_update_checker.rb +87 -122
  173. data/lib/active_support/execution_context/test_helper.rb +13 -0
  174. data/lib/active_support/execution_context.rb +53 -0
  175. data/lib/active_support/execution_wrapper.rb +46 -21
  176. data/lib/active_support/executor/test_helper.rb +7 -0
  177. data/lib/active_support/executor.rb +2 -0
  178. data/lib/active_support/file_update_checker.rb +2 -1
  179. data/lib/active_support/fork_tracker.rb +71 -0
  180. data/lib/active_support/gem_version.rb +7 -5
  181. data/lib/active_support/gzip.rb +2 -0
  182. data/lib/active_support/hash_with_indifferent_access.rb +126 -42
  183. data/lib/active_support/html_safe_translation.rb +43 -0
  184. data/lib/active_support/i18n.rb +5 -1
  185. data/lib/active_support/i18n_railtie.rb +19 -14
  186. data/lib/active_support/inflections.rb +2 -0
  187. data/lib/active_support/inflector/inflections.rb +41 -14
  188. data/lib/active_support/inflector/methods.rb +73 -87
  189. data/lib/active_support/inflector/transliterate.rb +56 -18
  190. data/lib/active_support/inflector.rb +2 -0
  191. data/lib/active_support/isolated_execution_state.rb +72 -0
  192. data/lib/active_support/json/decoding.rb +27 -26
  193. data/lib/active_support/json/encoding.rb +16 -6
  194. data/lib/active_support/json.rb +2 -0
  195. data/lib/active_support/key_generator.rb +25 -38
  196. data/lib/active_support/lazy_load_hooks.rb +35 -6
  197. data/lib/active_support/locale/en.rb +33 -0
  198. data/lib/active_support/locale/en.yml +8 -4
  199. data/lib/active_support/log_subscriber/test_helper.rb +4 -2
  200. data/lib/active_support/log_subscriber.rb +54 -13
  201. data/lib/active_support/logger.rb +4 -17
  202. data/lib/active_support/logger_silence.rb +13 -20
  203. data/lib/active_support/logger_thread_safe_level.rb +48 -10
  204. data/lib/active_support/message_encryptor.rb +111 -37
  205. data/lib/active_support/message_verifier.rb +124 -21
  206. data/lib/active_support/messages/metadata.rb +80 -0
  207. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  208. data/lib/active_support/messages/rotator.rb +57 -0
  209. data/lib/active_support/multibyte/chars.rb +19 -76
  210. data/lib/active_support/multibyte/unicode.rb +9 -331
  211. data/lib/active_support/multibyte.rb +3 -1
  212. data/lib/active_support/notifications/fanout.rb +165 -37
  213. data/lib/active_support/notifications/instrumenter.rb +92 -11
  214. data/lib/active_support/notifications.rb +96 -30
  215. data/lib/active_support/number_helper/number_converter.rb +8 -9
  216. data/lib/active_support/number_helper/number_to_currency_converter.rb +14 -12
  217. data/lib/active_support/number_helper/number_to_delimited_converter.rb +6 -3
  218. data/lib/active_support/number_helper/number_to_human_converter.rb +6 -3
  219. data/lib/active_support/number_helper/number_to_human_size_converter.rb +7 -4
  220. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  221. data/lib/active_support/number_helper/number_to_phone_converter.rb +6 -3
  222. data/lib/active_support/number_helper/number_to_rounded_converter.rb +14 -27
  223. data/lib/active_support/number_helper/rounding_helper.rb +16 -34
  224. data/lib/active_support/number_helper.rb +38 -12
  225. data/lib/active_support/option_merger.rb +19 -6
  226. data/lib/active_support/ordered_hash.rb +4 -2
  227. data/lib/active_support/ordered_options.rb +18 -6
  228. data/lib/active_support/parameter_filter.rb +138 -0
  229. data/lib/active_support/per_thread_registry.rb +8 -1
  230. data/lib/active_support/proxy_object.rb +2 -0
  231. data/lib/active_support/rails.rb +3 -10
  232. data/lib/active_support/railtie.rb +112 -11
  233. data/lib/active_support/reloader.rb +12 -11
  234. data/lib/active_support/rescuable.rb +19 -18
  235. data/lib/active_support/ruby_features.rb +7 -0
  236. data/lib/active_support/secure_compare_rotator.rb +51 -0
  237. data/lib/active_support/security_utils.rb +26 -15
  238. data/lib/active_support/string_inquirer.rb +4 -3
  239. data/lib/active_support/subscriber.rb +81 -42
  240. data/lib/active_support/tagged_logging.rb +45 -9
  241. data/lib/active_support/test_case.rb +86 -2
  242. data/lib/active_support/testing/assertions.rb +89 -21
  243. data/lib/active_support/testing/autorun.rb +2 -0
  244. data/lib/active_support/testing/constant_lookup.rb +2 -0
  245. data/lib/active_support/testing/declarative.rb +2 -0
  246. data/lib/active_support/testing/deprecation.rb +54 -2
  247. data/lib/active_support/testing/file_fixtures.rb +4 -0
  248. data/lib/active_support/testing/isolation.rb +6 -4
  249. data/lib/active_support/testing/method_call_assertions.rb +34 -5
  250. data/lib/active_support/testing/parallelization/server.rb +82 -0
  251. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  252. data/lib/active_support/testing/parallelization.rb +55 -0
  253. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  254. data/lib/active_support/testing/setup_and_teardown.rb +12 -7
  255. data/lib/active_support/testing/stream.rb +6 -7
  256. data/lib/active_support/testing/tagged_logging.rb +3 -1
  257. data/lib/active_support/testing/time_helpers.rb +91 -15
  258. data/lib/active_support/time.rb +2 -0
  259. data/lib/active_support/time_with_zone.rb +168 -56
  260. data/lib/active_support/values/time_zone.rb +85 -37
  261. data/lib/active_support/version.rb +3 -1
  262. data/lib/active_support/xml_mini/jdom.rb +6 -5
  263. data/lib/active_support/xml_mini/libxml.rb +9 -7
  264. data/lib/active_support/xml_mini/libxmlsax.rb +7 -5
  265. data/lib/active_support/xml_mini/nokogiri.rb +8 -6
  266. data/lib/active_support/xml_mini/nokogirisax.rb +6 -4
  267. data/lib/active_support/xml_mini/rexml.rb +13 -4
  268. data/lib/active_support/xml_mini.rb +10 -15
  269. data/lib/active_support.rb +30 -9
  270. metadata +76 -35
  271. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  272. data/lib/active_support/core_ext/hash/compact.rb +0 -27
  273. data/lib/active_support/core_ext/hash/transform_values.rb +0 -30
  274. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  275. data/lib/active_support/core_ext/marshal.rb +0 -22
  276. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  277. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
  278. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  279. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -0,0 +1,474 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ gem "redis", ">= 4.0.1"
5
+ require "redis"
6
+ require "redis/distributed"
7
+ rescue LoadError
8
+ warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \">= 4.0.1\"`"
9
+ raise
10
+ end
11
+
12
+ # Prefer the hiredis driver but don't require it.
13
+ begin
14
+ if ::Redis::VERSION < "5"
15
+ require "redis/connection/hiredis"
16
+ else
17
+ require "hiredis-client"
18
+ end
19
+ rescue LoadError
20
+ end
21
+
22
+ require "active_support/digest"
23
+
24
+ module ActiveSupport
25
+ module Cache
26
+ module ConnectionPoolLike
27
+ def with
28
+ yield self
29
+ end
30
+ end
31
+
32
+ ::Redis.include(ConnectionPoolLike)
33
+ ::Redis::Distributed.include(ConnectionPoolLike)
34
+
35
+ # Redis cache store.
36
+ #
37
+ # Deployment note: Take care to use a *dedicated Redis cache* rather
38
+ # than pointing this at your existing Redis server. It won't cope well
39
+ # with mixed usage patterns and it won't expire cache entries by default.
40
+ #
41
+ # Redis cache server setup guide: https://redis.io/topics/lru-cache
42
+ #
43
+ # * Supports vanilla Redis, hiredis, and Redis::Distributed.
44
+ # * Supports Memcached-like sharding across Redises with Redis::Distributed.
45
+ # * Fault tolerant. If the Redis server is unavailable, no exceptions are
46
+ # raised. Cache fetches are all misses and writes are dropped.
47
+ # * Local cache. Hot in-memory primary cache within block/middleware scope.
48
+ # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed
49
+ # 4.0.1+ for distributed mget support.
50
+ # * +delete_matched+ support for Redis KEYS globs.
51
+ class RedisCacheStore < Store
52
+ # Keys are truncated with the ActiveSupport digest if they exceed 1kB
53
+ MAX_KEY_BYTESIZE = 1024
54
+
55
+ DEFAULT_REDIS_OPTIONS = {
56
+ connect_timeout: 20,
57
+ read_timeout: 1,
58
+ write_timeout: 1,
59
+ reconnect_attempts: 0,
60
+ }
61
+
62
+ DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
63
+ if logger
64
+ logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
65
+ end
66
+ end
67
+
68
+ # The maximum number of entries to receive per SCAN call.
69
+ SCAN_BATCH_SIZE = 1000
70
+ private_constant :SCAN_BATCH_SIZE
71
+
72
+ # Advertise cache versioning support.
73
+ def self.supports_cache_versioning?
74
+ true
75
+ end
76
+
77
+ prepend Strategy::LocalCache
78
+
79
+ class << self
80
+ # Factory method to create a new Redis instance.
81
+ #
82
+ # Handles four options: :redis block, :redis instance, single :url
83
+ # string, and multiple :url strings.
84
+ #
85
+ # Option Class Result
86
+ # :redis Proc -> options[:redis].call
87
+ # :redis Object -> options[:redis]
88
+ # :url String -> Redis.new(url: …)
89
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
90
+ #
91
+ def build_redis(redis: nil, url: nil, **redis_options) # :nodoc:
92
+ urls = Array(url)
93
+
94
+ if redis.is_a?(Proc)
95
+ redis.call
96
+ elsif redis
97
+ redis
98
+ elsif urls.size > 1
99
+ build_redis_distributed_client(urls: urls, **redis_options)
100
+ elsif urls.empty?
101
+ build_redis_client(**redis_options)
102
+ else
103
+ build_redis_client(url: urls.first, **redis_options)
104
+ end
105
+ end
106
+
107
+ private
108
+ def build_redis_distributed_client(urls:, **redis_options)
109
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
110
+ urls.each { |u| dist.add_node url: u }
111
+ end
112
+ end
113
+
114
+ def build_redis_client(**redis_options)
115
+ ::Redis.new(DEFAULT_REDIS_OPTIONS.merge(redis_options))
116
+ end
117
+ end
118
+
119
+ attr_reader :redis_options
120
+ attr_reader :max_key_bytesize
121
+
122
+ # Creates a new Redis cache store.
123
+ #
124
+ # Handles four options: :redis block, :redis instance, single :url
125
+ # string, and multiple :url strings.
126
+ #
127
+ # Option Class Result
128
+ # :redis Proc -> options[:redis].call
129
+ # :redis Object -> options[:redis]
130
+ # :url String -> Redis.new(url: …)
131
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
132
+ #
133
+ # No namespace is set by default. Provide one if the Redis cache
134
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
135
+ #
136
+ # Compression is enabled by default with a 1kB threshold, so cached
137
+ # values larger than 1kB are automatically compressed. Disable by
138
+ # passing <tt>compress: false</tt> or change the threshold by passing
139
+ # <tt>compress_threshold: 4.kilobytes</tt>.
140
+ #
141
+ # No expiry is set on cache entries by default. Redis is expected to
142
+ # be configured with an eviction policy that automatically deletes
143
+ # least-recently or -frequently used keys when it reaches max memory.
144
+ # See https://redis.io/topics/lru-cache for cache server setup.
145
+ #
146
+ # Race condition TTL is not set by default. This can be used to avoid
147
+ # "thundering herd" cache writes when hot cache entries are expired.
148
+ # See ActiveSupport::Cache::Store#fetch for more.
149
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: default_coder, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
150
+ @redis_options = redis_options
151
+
152
+ @max_key_bytesize = MAX_KEY_BYTESIZE
153
+ @error_handler = error_handler
154
+
155
+ super namespace: namespace,
156
+ compress: compress, compress_threshold: compress_threshold,
157
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl,
158
+ coder: coder
159
+ end
160
+
161
+ def redis
162
+ @redis ||= begin
163
+ pool_options = self.class.send(:retrieve_pool_options, redis_options)
164
+
165
+ if pool_options.any?
166
+ self.class.send(:ensure_connection_pool_added!)
167
+ ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
168
+ else
169
+ self.class.build_redis(**redis_options)
170
+ end
171
+ end
172
+ end
173
+
174
+ def inspect
175
+ instance = @redis || @redis_options
176
+ "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
177
+ end
178
+
179
+ # Cache Store API implementation.
180
+ #
181
+ # Read multiple values at once. Returns a hash of requested keys ->
182
+ # fetched values.
183
+ def read_multi(*names)
184
+ if mget_capable?
185
+ instrument(:read_multi, names, options) do |payload|
186
+ read_multi_mget(*names).tap do |results|
187
+ payload[:hits] = results.keys
188
+ end
189
+ end
190
+ else
191
+ super
192
+ end
193
+ end
194
+
195
+ # Cache Store API implementation.
196
+ #
197
+ # Supports Redis KEYS glob patterns:
198
+ #
199
+ # h?llo matches hello, hallo and hxllo
200
+ # h*llo matches hllo and heeeello
201
+ # h[ae]llo matches hello and hallo, but not hillo
202
+ # h[^e]llo matches hallo, hbllo, ... but not hello
203
+ # h[a-b]llo matches hallo and hbllo
204
+ #
205
+ # Use \ to escape special characters if you want to match them verbatim.
206
+ #
207
+ # See https://redis.io/commands/KEYS for more.
208
+ #
209
+ # Failsafe: Raises errors.
210
+ def delete_matched(matcher, options = nil)
211
+ instrument :delete_matched, matcher do
212
+ unless String === matcher
213
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
214
+ end
215
+ redis.with do |c|
216
+ pattern = namespace_key(matcher, options)
217
+ cursor = "0"
218
+ # Fetch keys in batches using SCAN to avoid blocking the Redis server.
219
+ nodes = c.respond_to?(:nodes) ? c.nodes : [c]
220
+
221
+ nodes.each do |node|
222
+ begin
223
+ cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
224
+ node.del(*keys) unless keys.empty?
225
+ end until cursor == "0"
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ # Cache Store API implementation.
232
+ #
233
+ # Increment a cached value. This method uses the Redis incr atomic
234
+ # operator and can only be used on values written with the +:raw+ option.
235
+ # Calling it on a value not stored with +:raw+ will initialize that value
236
+ # to zero.
237
+ #
238
+ # Failsafe: Raises errors.
239
+ def increment(name, amount = 1, options = nil)
240
+ instrument :increment, name, amount: amount do
241
+ failsafe :increment do
242
+ options = merged_options(options)
243
+ key = normalize_key(name, options)
244
+
245
+ redis.with do |c|
246
+ c.incrby(key, amount).tap do
247
+ write_key_expiry(c, key, options)
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ # Cache Store API implementation.
255
+ #
256
+ # Decrement a cached value. This method uses the Redis decr atomic
257
+ # operator and can only be used on values written with the +:raw+ option.
258
+ # Calling it on a value not stored with +:raw+ will initialize that value
259
+ # to zero.
260
+ #
261
+ # Failsafe: Raises errors.
262
+ def decrement(name, amount = 1, options = nil)
263
+ instrument :decrement, name, amount: amount do
264
+ failsafe :decrement do
265
+ options = merged_options(options)
266
+ key = normalize_key(name, options)
267
+
268
+ redis.with do |c|
269
+ c.decrby(key, amount).tap do
270
+ write_key_expiry(c, key, options)
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+
277
+ # Cache Store API implementation.
278
+ #
279
+ # Removes expired entries. Handled natively by Redis least-recently-/
280
+ # least-frequently-used expiry, so manual cleanup is not supported.
281
+ def cleanup(options = nil)
282
+ super
283
+ end
284
+
285
+ # Clear the entire cache on all Redis servers. Safe to use on
286
+ # shared servers if the cache is namespaced.
287
+ #
288
+ # Failsafe: Raises errors.
289
+ def clear(options = nil)
290
+ failsafe :clear do
291
+ if namespace = merged_options(options)[:namespace]
292
+ delete_matched "*", namespace: namespace
293
+ else
294
+ redis.with { |c| c.flushdb }
295
+ end
296
+ end
297
+ end
298
+
299
+ # Get info from redis servers.
300
+ def stats
301
+ redis.with { |c| c.info }
302
+ end
303
+
304
+ def mget_capable? # :nodoc:
305
+ set_redis_capabilities unless defined? @mget_capable
306
+ @mget_capable
307
+ end
308
+
309
+ def mset_capable? # :nodoc:
310
+ set_redis_capabilities unless defined? @mset_capable
311
+ @mset_capable
312
+ end
313
+
314
+ private
315
+ def set_redis_capabilities
316
+ case redis
317
+ when Redis::Distributed
318
+ @mget_capable = true
319
+ @mset_capable = false
320
+ else
321
+ @mget_capable = true
322
+ @mset_capable = true
323
+ end
324
+ end
325
+
326
+ # Store provider interface:
327
+ # Read an entry from the cache.
328
+ def read_entry(key, **options)
329
+ deserialize_entry(read_serialized_entry(key, **options), **options)
330
+ end
331
+
332
+ def read_serialized_entry(key, raw: false, **options)
333
+ failsafe :read_entry do
334
+ redis.with { |c| c.get(key) }
335
+ end
336
+ end
337
+
338
+ def read_multi_entries(names, **options)
339
+ if mget_capable?
340
+ read_multi_mget(*names, **options)
341
+ else
342
+ super
343
+ end
344
+ end
345
+
346
+ def read_multi_mget(*names)
347
+ options = names.extract_options!
348
+ options = merged_options(options)
349
+ return {} if names == []
350
+ raw = options&.fetch(:raw, false)
351
+
352
+ keys = names.map { |name| normalize_key(name, options) }
353
+
354
+ values = failsafe(:read_multi_mget, returning: {}) do
355
+ redis.with { |c| c.mget(*keys) }
356
+ end
357
+
358
+ names.zip(values).each_with_object({}) do |(name, value), results|
359
+ if value
360
+ entry = deserialize_entry(value, raw: raw)
361
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
362
+ results[name] = entry.value
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ # Write an entry to the cache.
369
+ #
370
+ # Requires Redis 2.6.12+ for extended SET options.
371
+ def write_entry(key, entry, raw: false, **options)
372
+ write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
373
+ end
374
+
375
+ def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options)
376
+ # If race condition TTL is in use, ensure that cache entries
377
+ # stick around a bit longer after they would have expired
378
+ # so we can purposefully serve stale entries.
379
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
380
+ expires_in += 5.minutes
381
+ end
382
+
383
+ modifiers = {}
384
+ if unless_exist || expires_in
385
+ modifiers[:nx] = unless_exist
386
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
387
+ end
388
+
389
+ failsafe :write_entry, returning: false do
390
+ redis.with { |c| c.set key, payload, **modifiers }
391
+ end
392
+ end
393
+
394
+ def write_key_expiry(client, key, options)
395
+ if options[:expires_in] && client.ttl(key).negative?
396
+ client.expire key, options[:expires_in].to_i
397
+ end
398
+ end
399
+
400
+ # Delete an entry from the cache.
401
+ def delete_entry(key, options)
402
+ failsafe :delete_entry, returning: false do
403
+ redis.with { |c| c.del key }
404
+ end
405
+ end
406
+
407
+ # Deletes multiple entries in the cache. Returns the number of entries deleted.
408
+ def delete_multi_entries(entries, **_options)
409
+ redis.with { |c| c.del(entries) }
410
+ end
411
+
412
+ # Nonstandard store provider API to write multiple values at once.
413
+ def write_multi_entries(entries, expires_in: nil, **options)
414
+ if entries.any?
415
+ if mset_capable? && expires_in.nil?
416
+ failsafe :write_multi_entries do
417
+ payload = serialize_entries(entries, **options)
418
+ redis.with do |c|
419
+ c.mapped_mset(payload)
420
+ end
421
+ end
422
+ else
423
+ super
424
+ end
425
+ end
426
+ end
427
+
428
+ # Truncate keys that exceed 1kB.
429
+ def normalize_key(key, options)
430
+ truncate_key super&.b
431
+ end
432
+
433
+ def truncate_key(key)
434
+ if key && key.bytesize > max_key_bytesize
435
+ suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
436
+ truncate_at = max_key_bytesize - suffix.bytesize
437
+ "#{key.byteslice(0, truncate_at)}#{suffix}"
438
+ else
439
+ key
440
+ end
441
+ end
442
+
443
+ def deserialize_entry(payload, raw: false, **)
444
+ if raw && !payload.nil?
445
+ Entry.new(payload)
446
+ else
447
+ super(payload)
448
+ end
449
+ end
450
+
451
+ def serialize_entry(entry, raw: false, **options)
452
+ if raw
453
+ entry.value.to_s
454
+ else
455
+ super(entry, raw: raw, **options)
456
+ end
457
+ end
458
+
459
+ def serialize_entries(entries, **options)
460
+ entries.transform_values do |entry|
461
+ serialize_entry(entry, **options)
462
+ end
463
+ end
464
+
465
+ def failsafe(method, returning: nil)
466
+ yield
467
+ rescue ::Redis::BaseError => error
468
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
469
+ @error_handler&.call(method: method, exception: error, returning: returning)
470
+ returning
471
+ end
472
+ end
473
+ end
474
+ end
@@ -1,6 +1,6 @@
1
- require "active_support/core_ext/object/duplicable"
1
+ # frozen_string_literal: true
2
+
2
3
  require "active_support/core_ext/string/inflections"
3
- require "active_support/per_thread_registry"
4
4
 
5
5
  module ActiveSupport
6
6
  module Cache
@@ -12,63 +12,56 @@ module ActiveSupport
12
12
  autoload :Middleware, "active_support/cache/strategy/local_cache_middleware"
13
13
 
14
14
  # Class for storing and registering the local caches.
15
- class LocalCacheRegistry # :nodoc:
16
- extend ActiveSupport::PerThreadRegistry
17
-
18
- def initialize
19
- @registry = {}
20
- end
15
+ module LocalCacheRegistry # :nodoc:
16
+ extend self
21
17
 
22
18
  def cache_for(local_cache_key)
23
- @registry[local_cache_key]
19
+ registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {}
20
+ registry[local_cache_key]
24
21
  end
25
22
 
26
23
  def set_cache_for(local_cache_key, value)
27
- @registry[local_cache_key] = value
24
+ registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {}
25
+ registry[local_cache_key] = value
28
26
  end
29
-
30
- def self.set_cache_for(l, v); instance.set_cache_for l, v; end
31
- def self.cache_for(l); instance.cache_for l; end
32
27
  end
33
28
 
34
29
  # Simple memory backed cache. This cache is not thread safe and is intended only
35
30
  # for serving as a temporary memory cache for a single thread.
36
- class LocalStore < Store
31
+ class LocalStore
37
32
  def initialize
38
- super
39
33
  @data = {}
40
34
  end
41
35
 
42
- # Don't allow synchronizing since it isn't thread safe.
43
- def synchronize # :nodoc:
44
- yield
45
- end
46
-
47
36
  def clear(options = nil)
48
37
  @data.clear
49
38
  end
50
39
 
51
- def read_entry(key, options)
40
+ def read_entry(key)
52
41
  @data[key]
53
42
  end
54
43
 
55
- def write_entry(key, value, options)
56
- @data[key] = value
44
+ def read_multi_entries(keys)
45
+ @data.slice(*keys)
46
+ end
47
+
48
+ def write_entry(key, entry)
49
+ @data[key] = entry
57
50
  true
58
51
  end
59
52
 
60
- def delete_entry(key, options)
53
+ def delete_entry(key)
61
54
  !!@data.delete(key)
62
55
  end
63
56
 
64
- def fetch_entry(key, options = nil) # :nodoc:
57
+ def fetch_entry(key) # :nodoc:
65
58
  @data.fetch(key) { @data[key] = yield }
66
59
  end
67
60
  end
68
61
 
69
62
  # Use a local cache for the duration of block.
70
- def with_local_cache
71
- use_temporary_local_cache(LocalStore.new) { yield }
63
+ def with_local_cache(&block)
64
+ use_temporary_local_cache(LocalStore.new, &block)
72
65
  end
73
66
 
74
67
  # Middleware class can be inserted as a Rack handler to be local cache for the
@@ -79,60 +72,90 @@ module ActiveSupport
79
72
  local_cache_key)
80
73
  end
81
74
 
82
- def clear(options = nil) # :nodoc:
75
+ def clear(**options) # :nodoc:
83
76
  return super unless cache = local_cache
84
77
  cache.clear(options)
85
78
  super
86
79
  end
87
80
 
88
- def cleanup(options = nil) # :nodoc:
81
+ def cleanup(**options) # :nodoc:
89
82
  return super unless cache = local_cache
90
- cache.clear(options)
83
+ cache.clear
84
+ super
85
+ end
86
+
87
+ def delete_matched(matcher, options = nil) # :nodoc:
88
+ return super unless cache = local_cache
89
+ cache.clear
91
90
  super
92
91
  end
93
92
 
94
- def increment(name, amount = 1, options = nil) # :nodoc:
93
+ def increment(name, amount = 1, **options) # :nodoc:
95
94
  return super unless local_cache
96
95
  value = bypass_local_cache { super }
97
- write_cache_value(name, value, options)
96
+ write_cache_value(name, value, raw: true, **options)
98
97
  value
99
98
  end
100
99
 
101
- def decrement(name, amount = 1, options = nil) # :nodoc:
100
+ def decrement(name, amount = 1, **options) # :nodoc:
102
101
  return super unless local_cache
103
102
  value = bypass_local_cache { super }
104
- write_cache_value(name, value, options)
103
+ write_cache_value(name, value, raw: true, **options)
105
104
  value
106
105
  end
107
106
 
108
107
  private
109
- def read_entry(key, options)
108
+ def read_serialized_entry(key, raw: false, **options)
110
109
  if cache = local_cache
111
- cache.fetch_entry(key) { super }
110
+ hit = true
111
+ entry = cache.fetch_entry(key) do
112
+ hit = false
113
+ super
114
+ end
115
+ options[:event][:store] = cache.class.name if hit && options[:event]
116
+ entry
112
117
  else
113
118
  super
114
119
  end
115
120
  end
116
121
 
117
- def write_entry(key, entry, options)
118
- local_cache.write_entry(key, entry, options) if local_cache
119
- super
122
+ def read_multi_entries(keys, **options)
123
+ return super unless local_cache
124
+
125
+ local_entries = local_cache.read_multi_entries(keys)
126
+ local_entries.transform_values! do |payload|
127
+ deserialize_entry(payload).value
128
+ end
129
+ missed_keys = keys - local_entries.keys
130
+
131
+ if missed_keys.any?
132
+ local_entries.merge!(super(missed_keys, **options))
133
+ else
134
+ local_entries
135
+ end
120
136
  end
121
137
 
122
- def delete_entry(key, options)
123
- local_cache.delete_entry(key, options) if local_cache
138
+ def write_serialized_entry(key, payload, **)
139
+ if return_value = super
140
+ local_cache.write_entry(key, payload) if local_cache
141
+ else
142
+ local_cache.delete_entry(key) if local_cache
143
+ end
144
+ return_value
145
+ end
146
+
147
+ def delete_entry(key, **)
148
+ local_cache.delete_entry(key) if local_cache
124
149
  super
125
150
  end
126
151
 
127
- def write_cache_value(name, value, options)
152
+ def write_cache_value(name, value, **options)
128
153
  name = normalize_key(name, options)
129
154
  cache = local_cache
130
- cache.mute do
131
- if value
132
- cache.write(name, value, options)
133
- else
134
- cache.delete(name, options)
135
- end
155
+ if value
156
+ cache.write_entry(name, serialize_entry(new_entry(value, **options), **options))
157
+ else
158
+ cache.delete_entry(name)
136
159
  end
137
160
  end
138
161
 
@@ -144,8 +167,8 @@ module ActiveSupport
144
167
  LocalCacheRegistry.cache_for(local_cache_key)
145
168
  end
146
169
 
147
- def bypass_local_cache
148
- use_temporary_local_cache(nil) { yield }
170
+ def bypass_local_cache(&block)
171
+ use_temporary_local_cache(nil, &block)
149
172
  end
150
173
 
151
174
  def use_temporary_local_cache(temporary_cache)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack/body_proxy"
2
4
  require "rack/utils"
3
5