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,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # +ActiveSupport::ErrorReporter+ is a common interface for error reporting services.
5
+ #
6
+ # To rescue and report any unhandled error, you can use the +handle+ method:
7
+ #
8
+ # Rails.error.handle do
9
+ # do_something!
10
+ # end
11
+ #
12
+ # If an error is raised, it will be reported and swallowed.
13
+ #
14
+ # Alternatively if you want to report the error but not swallow it, you can use +record+
15
+ #
16
+ # Rails.error.record do
17
+ # do_something!
18
+ # end
19
+ #
20
+ # Both methods can be restricted to only handle a specific exception class
21
+ #
22
+ # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
23
+ #
24
+ # You can also pass some extra context information that may be used by the error subscribers:
25
+ #
26
+ # Rails.error.handle(context: { section: "admin" }) do
27
+ # # ...
28
+ # end
29
+ #
30
+ # Additionally a +severity+ can be passed along to communicate how important the error report is.
31
+ # +severity+ can be one of +:error+, +:warning+, or +:info+. Handled errors default to the +:warning+
32
+ # severity, and unhandled ones to +:error+.
33
+ #
34
+ # Both +handle+ and +record+ pass through the return value from the block. In the case of +handle+
35
+ # rescuing an error, a fallback can be provided. The fallback must be a callable whose result will
36
+ # be returned when the block raises and is handled:
37
+ #
38
+ # user = Rails.error.handle(fallback: -> { User.anonymous }) do
39
+ # User.find_by(params)
40
+ # end
41
+ class ErrorReporter
42
+ SEVERITIES = %i(error warning info)
43
+
44
+ attr_accessor :logger
45
+
46
+ def initialize(*subscribers, logger: nil)
47
+ @subscribers = subscribers.flatten
48
+ @logger = logger
49
+ end
50
+
51
+ # Report any unhandled exception, and swallow it.
52
+ #
53
+ # Rails.error.handle do
54
+ # 1 + '1'
55
+ # end
56
+ #
57
+ def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil)
58
+ yield
59
+ rescue error_class => error
60
+ report(error, handled: true, severity: severity, context: context)
61
+ fallback.call if fallback
62
+ end
63
+
64
+ def record(error_class = StandardError, severity: :error, context: {})
65
+ yield
66
+ rescue error_class => error
67
+ report(error, handled: false, severity: severity, context: context)
68
+ raise
69
+ end
70
+
71
+ # Register a new error subscriber. The subscriber must respond to
72
+ #
73
+ # report(Exception, handled: Boolean, context: Hash)
74
+ #
75
+ # The +report+ method +should+ never raise an error.
76
+ def subscribe(subscriber)
77
+ unless subscriber.respond_to?(:report)
78
+ raise ArgumentError, "Error subscribers must respond to #report"
79
+ end
80
+ @subscribers << subscriber
81
+ end
82
+
83
+ # Update the execution context that is accessible to error subscribers
84
+ #
85
+ # Rails.error.set_context(section: "checkout", user_id: @user.id)
86
+ #
87
+ # See +ActiveSupport::ExecutionContext.set+
88
+ def set_context(...)
89
+ ActiveSupport::ExecutionContext.set(...)
90
+ end
91
+
92
+ # When the block based +handle+ and +record+ methods are not suitable, you can directly use +report+
93
+ #
94
+ # Rails.error.report(error, handled: true)
95
+ def report(error, handled:, severity: handled ? :warning : :error, context: {})
96
+ unless SEVERITIES.include?(severity)
97
+ raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
98
+ end
99
+
100
+ full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
101
+ @subscribers.each do |subscriber|
102
+ subscriber.report(error, handled: handled, severity: severity, context: full_context)
103
+ rescue => subscriber_error
104
+ if logger
105
+ logger.fatal(
106
+ "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" +
107
+ subscriber_error.backtrace.join("\n")
108
+ )
109
+ else
110
+ raise
111
+ end
112
+ end
113
+
114
+ nil
115
+ end
116
+ end
117
+ end
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "set"
2
4
  require "pathname"
3
5
  require "concurrent/atomic/atomic_boolean"
6
+ require "listen"
7
+ require "active_support/fork_tracker"
4
8
 
5
9
  module ActiveSupport
6
10
  # Allows you to "listen" to changes in a file system.
7
- # The evented file updater does not hit disk when checking for updates
8
- # instead it uses platform specific file system events to trigger a change
11
+ # The evented file updater does not hit disk when checking for updates.
12
+ # Instead, it uses platform-specific file system events to trigger a change
9
13
  # in state.
10
14
  #
11
15
  # The file checker takes an array of files to watch or a hash specifying directories
@@ -13,8 +17,6 @@ module ActiveSupport
13
17
  # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
14
18
  # is run and there have been changes to the file system.
15
19
  #
16
- # Note: Forking will cause the first call to `updated?` to return `true`.
17
- #
18
20
  # Example:
19
21
  #
20
22
  # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
@@ -30,54 +32,28 @@ module ActiveSupport
30
32
  # checker.execute_if_updated
31
33
  # # => "changed"
32
34
  #
33
- class EventedFileUpdateChecker #:nodoc: all
35
+ class EventedFileUpdateChecker # :nodoc: all
34
36
  def initialize(files, dirs = {}, &block)
35
37
  unless block
36
38
  raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
37
39
  end
38
40
 
39
- @ph = PathHelper.new
40
- @files = files.map { |f| @ph.xpath(f) }.to_set
41
-
42
- @dirs = {}
43
- dirs.each do |dir, exts|
44
- @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
45
- end
46
-
47
- @block = block
48
- @updated = Concurrent::AtomicBoolean.new(false)
49
- @lcsp = @ph.longest_common_subpath(@dirs.keys)
50
- @pid = Process.pid
51
- @boot_mutex = Mutex.new
52
-
53
- if (@dtw = directories_to_watch).any?
54
- # Loading listen triggers warnings. These are originated by a legit
55
- # usage of attr_* macros for private attributes, but adds a lot of noise
56
- # to our test suite. Thus, we lazy load it and disable warnings locally.
57
- silence_warnings do
58
- begin
59
- require "listen"
60
- rescue LoadError => e
61
- raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
62
- end
63
- end
64
- end
65
- boot!
41
+ @block = block
42
+ @core = Core.new(files, dirs)
43
+ ObjectSpace.define_finalizer(self, @core.finalizer)
66
44
  end
67
45
 
68
46
  def updated?
69
- @boot_mutex.synchronize do
70
- if @pid != Process.pid
71
- boot!
72
- @pid = Process.pid
73
- @updated.make_true
74
- end
47
+ if @core.restart?
48
+ @core.thread_safely(&:restart)
49
+ @core.updated.make_true
75
50
  end
76
- @updated.true?
51
+
52
+ @core.updated.true?
77
53
  end
78
54
 
79
55
  def execute
80
- @updated.make_false
56
+ @core.updated.make_false
81
57
  @block.call
82
58
  end
83
59
 
@@ -89,115 +65,104 @@ module ActiveSupport
89
65
  end
90
66
  end
91
67
 
92
- private
93
- def boot!
94
- Listen.to(*@dtw, &method(:changed)).start
95
- end
68
+ class Core
69
+ attr_reader :updated
96
70
 
97
- def changed(modified, added, removed)
98
- unless updated?
99
- @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
71
+ def initialize(files, dirs)
72
+ @files = files.map { |file| Pathname(file).expand_path }.to_set
73
+
74
+ @dirs = dirs.each_with_object({}) do |(dir, exts), hash|
75
+ hash[Pathname(dir).expand_path] = Array(exts).map { |ext| ext.to_s.sub(/\A\.?/, ".") }.to_set
100
76
  end
101
- end
102
77
 
103
- def watching?(file)
104
- file = @ph.xpath(file)
78
+ @common_path = common_path(@dirs.keys)
105
79
 
106
- if @files.member?(file)
107
- true
108
- elsif file.directory?
109
- false
110
- else
111
- ext = @ph.normalize_extension(file.extname)
80
+ @dtw = directories_to_watch
81
+ @missing = []
112
82
 
113
- file.dirname.ascend do |dir|
114
- if @dirs.fetch(dir, []).include?(ext)
115
- break true
116
- elsif dir == @lcsp || dir.root?
117
- break false
118
- end
119
- end
120
- end
83
+ @updated = Concurrent::AtomicBoolean.new(false)
84
+ @mutex = Mutex.new
85
+
86
+ start
87
+ @after_fork = ActiveSupport::ForkTracker.after_fork { start }
121
88
  end
122
89
 
123
- def directories_to_watch
124
- dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
125
- dtw.compact!
126
- dtw.uniq!
90
+ def finalizer
91
+ proc do
92
+ stop
93
+ ActiveSupport::ForkTracker.unregister(@after_fork)
94
+ end
95
+ end
127
96
 
128
- normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
129
- dtw = dtw.reject do |path|
130
- normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
97
+ def thread_safely
98
+ @mutex.synchronize do
99
+ yield self
131
100
  end
101
+ end
132
102
 
133
- @ph.filter_out_descendants(dtw)
103
+ def start
104
+ normalize_dirs!
105
+ @dtw, @missing = [*@dtw, *@missing].partition(&:exist?)
106
+ @listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil
107
+ @listener&.start
134
108
  end
135
109
 
136
- class PathHelper
137
- def xpath(path)
138
- Pathname.new(path).expand_path
139
- end
110
+ def stop
111
+ @listener&.stop
112
+ end
140
113
 
141
- def normalize_extension(ext)
142
- ext.to_s.sub(/\A\./, "")
143
- end
114
+ def restart
115
+ stop
116
+ start
117
+ end
144
118
 
145
- # Given a collection of Pathname objects returns the longest subpath
146
- # common to all of them, or +nil+ if there is none.
147
- def longest_common_subpath(paths)
148
- return if paths.empty?
149
-
150
- lcsp = Pathname.new(paths[0])
151
-
152
- paths[1..-1].each do |path|
153
- until ascendant_of?(lcsp, path)
154
- if lcsp.root?
155
- # If we get here a root directory is not an ascendant of path.
156
- # This may happen if there are paths in different drives on
157
- # Windows.
158
- return
159
- else
160
- lcsp = lcsp.parent
161
- end
162
- end
163
- end
119
+ def restart?
120
+ @missing.any?(&:exist?)
121
+ end
164
122
 
165
- lcsp
123
+ def normalize_dirs!
124
+ @dirs.transform_keys! do |dir|
125
+ dir.exist? ? dir.realpath : dir
166
126
  end
127
+ end
167
128
 
168
- # Returns the deepest existing ascendant, which could be the argument itself.
169
- def existing_parent(dir)
170
- dir.ascend do |ascendant|
171
- break ascendant if ascendant.directory?
172
- end
129
+ def changed(modified, added, removed)
130
+ unless @updated.true?
131
+ @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
173
132
  end
133
+ end
174
134
 
175
- # Filters out directories which are descendants of others in the collection (stable).
176
- def filter_out_descendants(dirs)
177
- return dirs if dirs.length < 2
135
+ def watching?(file)
136
+ file = Pathname(file)
178
137
 
179
- dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
180
- descendants = []
138
+ if @files.member?(file)
139
+ true
140
+ elsif file.directory?
141
+ false
142
+ else
143
+ ext = file.extname
181
144
 
182
- until dirs_sorted_by_nparts.empty?
183
- dir = dirs_sorted_by_nparts.shift
145
+ file.dirname.ascend do |dir|
146
+ matching = @dirs[dir]
184
147
 
185
- dirs_sorted_by_nparts.reject! do |possible_descendant|
186
- ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
148
+ if matching && (matching.empty? || matching.include?(ext))
149
+ break true
150
+ elsif dir == @common_path || dir.root?
151
+ break false
187
152
  end
188
153
  end
189
-
190
- # Array#- preserves order.
191
- dirs - descendants
192
154
  end
155
+ end
193
156
 
194
- private
157
+ def directories_to_watch
158
+ dtw = @dirs.keys | @files.map(&:dirname)
159
+ accounted_for = dtw.to_set + Gem.path.map { |path| Pathname(path) }
160
+ dtw.reject { |dir| dir.ascend.drop(1).any? { |parent| accounted_for.include?(parent) } }
161
+ end
195
162
 
196
- def ascendant_of?(base, other)
197
- base != other && other.ascend do |ascendant|
198
- break true if base == ascendant
199
- end
200
- end
163
+ def common_path(paths)
164
+ paths.map { |path| path.ascend.to_a }.reduce(&:&)&.first
201
165
  end
166
+ end
202
167
  end
203
168
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::ExecutionContext::TestHelper # :nodoc:
4
+ def before_setup
5
+ ActiveSupport::ExecutionContext.clear
6
+ super
7
+ end
8
+
9
+ def after_teardown
10
+ super
11
+ ActiveSupport::ExecutionContext.clear
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ExecutionContext # :nodoc:
5
+ @after_change_callbacks = []
6
+ class << self
7
+ def after_change(&block)
8
+ @after_change_callbacks << block
9
+ end
10
+
11
+ # Updates the execution context. If a block is given, it resets the provided keys to their
12
+ # previous value once the block exits.
13
+ def set(**options)
14
+ options.symbolize_keys!
15
+ keys = options.keys
16
+
17
+ store = self.store
18
+
19
+ previous_context = keys.zip(store.values_at(*keys)).to_h
20
+
21
+ store.merge!(options)
22
+ @after_change_callbacks.each(&:call)
23
+
24
+ if block_given?
25
+ begin
26
+ yield
27
+ ensure
28
+ store.merge!(previous_context)
29
+ @after_change_callbacks.each(&:call)
30
+ end
31
+ end
32
+ end
33
+
34
+ def []=(key, value)
35
+ store[key.to_sym] = value
36
+ @after_change_callbacks.each(&:call)
37
+ end
38
+
39
+ def to_h
40
+ store.dup
41
+ end
42
+
43
+ def clear
44
+ store.clear
45
+ end
46
+
47
+ private
48
+ def store
49
+ IsolatedExecutionState[:active_support_execution_context] ||= {}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/error_reporter"
1
4
  require "active_support/callbacks"
5
+ require "concurrent/hash"
2
6
 
3
7
  module ActiveSupport
4
8
  class ExecutionWrapper
@@ -60,18 +64,21 @@ module ActiveSupport
60
64
  # after the work has been performed.
61
65
  #
62
66
  # Where possible, prefer +wrap+.
63
- def self.run!
64
- if active?
65
- Null
67
+ def self.run!(reset: false)
68
+ if reset
69
+ lost_instance = IsolatedExecutionState.delete(active_key)
70
+ lost_instance&.complete!
66
71
  else
67
- new.tap do |instance|
68
- success = nil
69
- begin
70
- instance.run!
71
- success = true
72
- ensure
73
- instance.complete! unless success
74
- end
72
+ return Null if active?
73
+ end
74
+
75
+ new.tap do |instance|
76
+ success = nil
77
+ begin
78
+ instance.run!
79
+ success = true
80
+ ensure
81
+ instance.complete! unless success
75
82
  end
76
83
  end
77
84
  end
@@ -83,28 +90,42 @@ module ActiveSupport
83
90
  instance = run!
84
91
  begin
85
92
  yield
93
+ rescue => error
94
+ error_reporter.report(error, handled: false)
95
+ raise
86
96
  ensure
87
97
  instance.complete!
88
98
  end
89
99
  end
90
100
 
91
- class << self # :nodoc:
92
- attr_accessor :active
101
+ def self.perform # :nodoc:
102
+ instance = new
103
+ instance.run
104
+ begin
105
+ yield
106
+ ensure
107
+ instance.complete
108
+ end
93
109
  end
94
110
 
95
- def self.inherited(other) # :nodoc:
96
- super
97
- other.active = Concurrent::Hash.new
111
+ def self.error_reporter
112
+ @error_reporter ||= ActiveSupport::ErrorReporter.new
98
113
  end
99
114
 
100
- self.active = Concurrent::Hash.new
115
+ def self.active_key # :nodoc:
116
+ @active_key ||= :"active_execution_wrapper_#{object_id}"
117
+ end
101
118
 
102
119
  def self.active? # :nodoc:
103
- @active[Thread.current]
120
+ IsolatedExecutionState.key?(active_key)
104
121
  end
105
122
 
106
123
  def run! # :nodoc:
107
- self.class.active[Thread.current] = true
124
+ IsolatedExecutionState[self.class.active_key] = self
125
+ run
126
+ end
127
+
128
+ def run # :nodoc:
108
129
  run_callbacks(:run)
109
130
  end
110
131
 
@@ -113,9 +134,13 @@ module ActiveSupport
113
134
  #
114
135
  # Where possible, prefer +wrap+.
115
136
  def complete!
116
- run_callbacks(:complete)
137
+ complete
117
138
  ensure
118
- self.class.active.delete Thread.current
139
+ IsolatedExecutionState.delete(self.class.active_key)
140
+ end
141
+
142
+ def complete # :nodoc:
143
+ run_callbacks(:complete)
119
144
  end
120
145
 
121
146
  private
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::Executor::TestHelper # :nodoc:
4
+ def run(...)
5
+ Rails.application.executor.perform { super }
6
+ end
7
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/execution_wrapper"
2
4
 
3
5
  module ActiveSupport
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/time/calculations"
2
4
 
3
5
  module ActiveSupport
@@ -96,7 +98,6 @@ module ActiveSupport
96
98
  end
97
99
 
98
100
  private
99
-
100
101
  def watched
101
102
  @watched || begin
102
103
  all = @files.select { |f| File.exist?(f) }
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ForkTracker # :nodoc:
5
+ module ModernCoreExt
6
+ def _fork
7
+ pid = super
8
+ if pid == 0
9
+ ForkTracker.check!
10
+ end
11
+ pid
12
+ end
13
+ end
14
+
15
+ module CoreExt
16
+ def fork(...)
17
+ if block_given?
18
+ super do
19
+ ForkTracker.check!
20
+ yield
21
+ end
22
+ else
23
+ unless pid = super
24
+ ForkTracker.check!
25
+ end
26
+ pid
27
+ end
28
+ end
29
+ end
30
+
31
+ module CoreExtPrivate
32
+ include CoreExt
33
+ private :fork
34
+ end
35
+
36
+ @pid = Process.pid
37
+ @callbacks = []
38
+
39
+ class << self
40
+ def check!
41
+ new_pid = Process.pid
42
+ if @pid != new_pid
43
+ @callbacks.each(&:call)
44
+ @pid = new_pid
45
+ end
46
+ end
47
+
48
+ def hook!
49
+ if Process.respond_to?(:_fork) # Ruby 3.1+
50
+ ::Process.singleton_class.prepend(ModernCoreExt)
51
+ elsif Process.respond_to?(:fork)
52
+ ::Object.prepend(CoreExtPrivate) if RUBY_VERSION < "3.0"
53
+ ::Kernel.prepend(CoreExtPrivate)
54
+ ::Kernel.singleton_class.prepend(CoreExt)
55
+ ::Process.singleton_class.prepend(CoreExt)
56
+ end
57
+ end
58
+
59
+ def after_fork(&block)
60
+ @callbacks << block
61
+ block
62
+ end
63
+
64
+ def unregister(callback)
65
+ @callbacks.delete(callback)
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ ActiveSupport::ForkTracker.hook!
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
- # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of Active Support as a <tt>Gem::Version</tt>.
3
5
  def self.gem_version
4
6
  Gem::Version.new VERSION::STRING
5
7
  end
6
8
 
7
9
  module VERSION
8
- MAJOR = 5
9
- MINOR = 1
10
- TINY = 7
11
- PRE = nil
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 4
13
+ PRE = "1"
12
14
 
13
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
16
  end