activesupport 6.0.0

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 (250) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +572 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support.rb +96 -0
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/all.rb +5 -0
  8. data/lib/active_support/array_inquirer.rb +48 -0
  9. data/lib/active_support/backtrace_cleaner.rb +132 -0
  10. data/lib/active_support/benchmarkable.rb +51 -0
  11. data/lib/active_support/builder.rb +8 -0
  12. data/lib/active_support/cache.rb +830 -0
  13. data/lib/active_support/cache/file_store.rb +196 -0
  14. data/lib/active_support/cache/mem_cache_store.rb +212 -0
  15. data/lib/active_support/cache/memory_store.rb +174 -0
  16. data/lib/active_support/cache/null_store.rb +48 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +488 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +194 -0
  19. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  20. data/lib/active_support/callbacks.rb +856 -0
  21. data/lib/active_support/concern.rb +171 -0
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  23. data/lib/active_support/concurrency/share_lock.rb +227 -0
  24. data/lib/active_support/configurable.rb +146 -0
  25. data/lib/active_support/core_ext.rb +5 -0
  26. data/lib/active_support/core_ext/array.rb +9 -0
  27. data/lib/active_support/core_ext/array/access.rb +104 -0
  28. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -0
  34. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  35. data/lib/active_support/core_ext/benchmark.rb +16 -0
  36. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  37. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  38. data/lib/active_support/core_ext/class.rb +4 -0
  39. data/lib/active_support/core_ext/class/attribute.rb +141 -0
  40. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  41. data/lib/active_support/core_ext/class/subclasses.rb +54 -0
  42. data/lib/active_support/core_ext/date.rb +7 -0
  43. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  44. data/lib/active_support/core_ext/date/blank.rb +14 -0
  45. data/lib/active_support/core_ext/date/calculations.rb +146 -0
  46. data/lib/active_support/core_ext/date/conversions.rb +96 -0
  47. data/lib/active_support/core_ext/date/zones.rb +8 -0
  48. data/lib/active_support/core_ext/date_and_time/calculations.rb +351 -0
  49. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  50. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  51. data/lib/active_support/core_ext/date_time.rb +7 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +211 -0
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +107 -0
  57. data/lib/active_support/core_ext/digest.rb +3 -0
  58. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  59. data/lib/active_support/core_ext/enumerable.rb +188 -0
  60. data/lib/active_support/core_ext/file.rb +3 -0
  61. data/lib/active_support/core_ext/file/atomic.rb +70 -0
  62. data/lib/active_support/core_ext/hash.rb +10 -0
  63. data/lib/active_support/core_ext/hash/compact.rb +5 -0
  64. data/lib/active_support/core_ext/hash/conversions.rb +263 -0
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +34 -0
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +24 -0
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  69. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  71. data/lib/active_support/core_ext/hash/slice.rb +26 -0
  72. data/lib/active_support/core_ext/hash/transform_values.rb +5 -0
  73. data/lib/active_support/core_ext/integer.rb +5 -0
  74. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  75. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  76. data/lib/active_support/core_ext/integer/time.rb +22 -0
  77. data/lib/active_support/core_ext/kernel.rb +5 -0
  78. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  79. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  80. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  81. data/lib/active_support/core_ext/load_error.rb +9 -0
  82. data/lib/active_support/core_ext/marshal.rb +24 -0
  83. data/lib/active_support/core_ext/module.rb +13 -0
  84. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  85. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  86. data/lib/active_support/core_ext/module/attr_internal.rb +38 -0
  87. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  88. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +144 -0
  89. data/lib/active_support/core_ext/module/concerning.rb +134 -0
  90. data/lib/active_support/core_ext/module/delegation.rb +313 -0
  91. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  92. data/lib/active_support/core_ext/module/introspection.rb +86 -0
  93. data/lib/active_support/core_ext/module/reachable.rb +6 -0
  94. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  95. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  96. data/lib/active_support/core_ext/name_error.rb +38 -0
  97. data/lib/active_support/core_ext/numeric.rb +5 -0
  98. data/lib/active_support/core_ext/numeric/bytes.rb +66 -0
  99. data/lib/active_support/core_ext/numeric/conversions.rb +136 -0
  100. data/lib/active_support/core_ext/numeric/inquiry.rb +5 -0
  101. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  102. data/lib/active_support/core_ext/object.rb +16 -0
  103. data/lib/active_support/core_ext/object/acts_like.rb +21 -0
  104. data/lib/active_support/core_ext/object/blank.rb +155 -0
  105. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  106. data/lib/active_support/core_ext/object/deep_dup.rb +55 -0
  107. data/lib/active_support/core_ext/object/duplicable.rb +49 -0
  108. data/lib/active_support/core_ext/object/inclusion.rb +29 -0
  109. data/lib/active_support/core_ext/object/instance_variables.rb +30 -0
  110. data/lib/active_support/core_ext/object/json.rb +228 -0
  111. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  112. data/lib/active_support/core_ext/object/to_query.rb +89 -0
  113. data/lib/active_support/core_ext/object/try.rb +156 -0
  114. data/lib/active_support/core_ext/object/with_options.rb +82 -0
  115. data/lib/active_support/core_ext/range.rb +7 -0
  116. data/lib/active_support/core_ext/range/compare_range.rb +70 -0
  117. data/lib/active_support/core_ext/range/conversions.rb +41 -0
  118. data/lib/active_support/core_ext/range/each.rb +25 -0
  119. data/lib/active_support/core_ext/range/include_range.rb +9 -0
  120. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  121. data/lib/active_support/core_ext/range/overlaps.rb +10 -0
  122. data/lib/active_support/core_ext/regexp.rb +7 -0
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string.rb +15 -0
  125. data/lib/active_support/core_ext/string/access.rb +114 -0
  126. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  127. data/lib/active_support/core_ext/string/conversions.rb +59 -0
  128. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  129. data/lib/active_support/core_ext/string/filters.rb +145 -0
  130. data/lib/active_support/core_ext/string/indent.rb +45 -0
  131. data/lib/active_support/core_ext/string/inflections.rb +259 -0
  132. data/lib/active_support/core_ext/string/inquiry.rb +15 -0
  133. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  134. data/lib/active_support/core_ext/string/output_safety.rb +314 -0
  135. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  136. data/lib/active_support/core_ext/string/strip.rb +27 -0
  137. data/lib/active_support/core_ext/string/zones.rb +16 -0
  138. data/lib/active_support/core_ext/time.rb +7 -0
  139. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  140. data/lib/active_support/core_ext/time/calculations.rb +344 -0
  141. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  142. data/lib/active_support/core_ext/time/conversions.rb +72 -0
  143. data/lib/active_support/core_ext/time/zones.rb +113 -0
  144. data/lib/active_support/core_ext/uri.rb +25 -0
  145. data/lib/active_support/current_attributes.rb +203 -0
  146. data/lib/active_support/dependencies.rb +806 -0
  147. data/lib/active_support/dependencies/autoload.rb +79 -0
  148. data/lib/active_support/dependencies/interlock.rb +57 -0
  149. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  150. data/lib/active_support/deprecation.rb +46 -0
  151. data/lib/active_support/deprecation/behaviors.rb +109 -0
  152. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  153. data/lib/active_support/deprecation/instance_delegator.rb +39 -0
  154. data/lib/active_support/deprecation/method_wrappers.rb +78 -0
  155. data/lib/active_support/deprecation/proxy_wrappers.rb +173 -0
  156. data/lib/active_support/deprecation/reporting.rb +114 -0
  157. data/lib/active_support/descendants_tracker.rb +109 -0
  158. data/lib/active_support/digest.rb +20 -0
  159. data/lib/active_support/duration.rb +433 -0
  160. data/lib/active_support/duration/iso8601_parser.rb +124 -0
  161. data/lib/active_support/duration/iso8601_serializer.rb +54 -0
  162. data/lib/active_support/encrypted_configuration.rb +45 -0
  163. data/lib/active_support/encrypted_file.rb +100 -0
  164. data/lib/active_support/evented_file_update_checker.rb +235 -0
  165. data/lib/active_support/execution_wrapper.rb +129 -0
  166. data/lib/active_support/executor.rb +8 -0
  167. data/lib/active_support/file_update_checker.rb +163 -0
  168. data/lib/active_support/gem_version.rb +17 -0
  169. data/lib/active_support/gzip.rb +38 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +399 -0
  171. data/lib/active_support/i18n.rb +16 -0
  172. data/lib/active_support/i18n_railtie.rb +126 -0
  173. data/lib/active_support/inflections.rb +72 -0
  174. data/lib/active_support/inflector.rb +9 -0
  175. data/lib/active_support/inflector/inflections.rb +257 -0
  176. data/lib/active_support/inflector/methods.rb +398 -0
  177. data/lib/active_support/inflector/transliterate.rb +147 -0
  178. data/lib/active_support/json.rb +4 -0
  179. data/lib/active_support/json/decoding.rb +76 -0
  180. data/lib/active_support/json/encoding.rb +134 -0
  181. data/lib/active_support/key_generator.rb +41 -0
  182. data/lib/active_support/lazy_load_hooks.rb +82 -0
  183. data/lib/active_support/locale/en.rb +31 -0
  184. data/lib/active_support/locale/en.yml +135 -0
  185. data/lib/active_support/log_subscriber.rb +135 -0
  186. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  187. data/lib/active_support/logger.rb +93 -0
  188. data/lib/active_support/logger_silence.rb +45 -0
  189. data/lib/active_support/logger_thread_safe_level.rb +56 -0
  190. data/lib/active_support/message_encryptor.rb +227 -0
  191. data/lib/active_support/message_verifier.rb +205 -0
  192. data/lib/active_support/messages/metadata.rb +71 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  194. data/lib/active_support/messages/rotator.rb +56 -0
  195. data/lib/active_support/multibyte.rb +23 -0
  196. data/lib/active_support/multibyte/chars.rb +216 -0
  197. data/lib/active_support/multibyte/unicode.rb +157 -0
  198. data/lib/active_support/notifications.rb +253 -0
  199. data/lib/active_support/notifications/fanout.rb +244 -0
  200. data/lib/active_support/notifications/instrumenter.rb +164 -0
  201. data/lib/active_support/number_helper.rb +378 -0
  202. data/lib/active_support/number_helper/number_converter.rb +184 -0
  203. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  204. data/lib/active_support/number_helper/number_to_delimited_converter.rb +31 -0
  205. data/lib/active_support/number_helper/number_to_human_converter.rb +70 -0
  206. data/lib/active_support/number_helper/number_to_human_size_converter.rb +61 -0
  207. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  208. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  209. data/lib/active_support/number_helper/number_to_rounded_converter.rb +56 -0
  210. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  211. data/lib/active_support/option_merger.rb +27 -0
  212. data/lib/active_support/ordered_hash.rb +50 -0
  213. data/lib/active_support/ordered_options.rb +85 -0
  214. data/lib/active_support/parameter_filter.rb +129 -0
  215. data/lib/active_support/per_thread_registry.rb +60 -0
  216. data/lib/active_support/proxy_object.rb +15 -0
  217. data/lib/active_support/rails.rb +29 -0
  218. data/lib/active_support/railtie.rb +80 -0
  219. data/lib/active_support/reloader.rb +130 -0
  220. data/lib/active_support/rescuable.rb +174 -0
  221. data/lib/active_support/security_utils.rb +31 -0
  222. data/lib/active_support/string_inquirer.rb +34 -0
  223. data/lib/active_support/subscriber.rb +169 -0
  224. data/lib/active_support/tagged_logging.rb +88 -0
  225. data/lib/active_support/test_case.rb +163 -0
  226. data/lib/active_support/testing/assertions.rb +228 -0
  227. data/lib/active_support/testing/autorun.rb +7 -0
  228. data/lib/active_support/testing/constant_lookup.rb +51 -0
  229. data/lib/active_support/testing/declarative.rb +28 -0
  230. data/lib/active_support/testing/deprecation.rb +38 -0
  231. data/lib/active_support/testing/file_fixtures.rb +38 -0
  232. data/lib/active_support/testing/isolation.rb +110 -0
  233. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  234. data/lib/active_support/testing/parallelization.rb +128 -0
  235. data/lib/active_support/testing/setup_and_teardown.rb +55 -0
  236. data/lib/active_support/testing/stream.rb +44 -0
  237. data/lib/active_support/testing/tagged_logging.rb +27 -0
  238. data/lib/active_support/testing/time_helpers.rb +200 -0
  239. data/lib/active_support/time.rb +20 -0
  240. data/lib/active_support/time_with_zone.rb +561 -0
  241. data/lib/active_support/values/time_zone.rb +570 -0
  242. data/lib/active_support/version.rb +10 -0
  243. data/lib/active_support/xml_mini.rb +202 -0
  244. data/lib/active_support/xml_mini/jdom.rb +183 -0
  245. data/lib/active_support/xml_mini/libxml.rb +80 -0
  246. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  247. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  248. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  249. data/lib/active_support/xml_mini/rexml.rb +130 -0
  250. metadata +385 -0
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # A typical module looks like this:
5
+ #
6
+ # module M
7
+ # def self.included(base)
8
+ # base.extend ClassMethods
9
+ # base.class_eval do
10
+ # scope :disabled, -> { where(disabled: true) }
11
+ # end
12
+ # end
13
+ #
14
+ # module ClassMethods
15
+ # ...
16
+ # end
17
+ # end
18
+ #
19
+ # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
20
+ # written as:
21
+ #
22
+ # require 'active_support/concern'
23
+ #
24
+ # module M
25
+ # extend ActiveSupport::Concern
26
+ #
27
+ # included do
28
+ # scope :disabled, -> { where(disabled: true) }
29
+ # end
30
+ #
31
+ # class_methods do
32
+ # ...
33
+ # end
34
+ # end
35
+ #
36
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
37
+ # and a +Bar+ module which depends on the former, we would typically write the
38
+ # following:
39
+ #
40
+ # module Foo
41
+ # def self.included(base)
42
+ # base.class_eval do
43
+ # def self.method_injected_by_foo
44
+ # ...
45
+ # end
46
+ # end
47
+ # end
48
+ # end
49
+ #
50
+ # module Bar
51
+ # def self.included(base)
52
+ # base.method_injected_by_foo
53
+ # end
54
+ # end
55
+ #
56
+ # class Host
57
+ # include Foo # We need to include this dependency for Bar
58
+ # include Bar # Bar is the module that Host really needs
59
+ # end
60
+ #
61
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
62
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
63
+ #
64
+ # module Bar
65
+ # include Foo
66
+ # def self.included(base)
67
+ # base.method_injected_by_foo
68
+ # end
69
+ # end
70
+ #
71
+ # class Host
72
+ # include Bar
73
+ # end
74
+ #
75
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
76
+ # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
77
+ # module dependencies are properly resolved:
78
+ #
79
+ # require 'active_support/concern'
80
+ #
81
+ # module Foo
82
+ # extend ActiveSupport::Concern
83
+ # included do
84
+ # def self.method_injected_by_foo
85
+ # ...
86
+ # end
87
+ # end
88
+ # end
89
+ #
90
+ # module Bar
91
+ # extend ActiveSupport::Concern
92
+ # include Foo
93
+ #
94
+ # included do
95
+ # self.method_injected_by_foo
96
+ # end
97
+ # end
98
+ #
99
+ # class Host
100
+ # include Bar # It works, now Bar takes care of its dependencies
101
+ # end
102
+ module Concern
103
+ class MultipleIncludedBlocks < StandardError #:nodoc:
104
+ def initialize
105
+ super "Cannot define multiple 'included' blocks for a Concern"
106
+ end
107
+ end
108
+
109
+ def self.extended(base) #:nodoc:
110
+ base.instance_variable_set(:@_dependencies, [])
111
+ end
112
+
113
+ def append_features(base) #:nodoc:
114
+ if base.instance_variable_defined?(:@_dependencies)
115
+ base.instance_variable_get(:@_dependencies) << self
116
+ false
117
+ else
118
+ return false if base < self
119
+ @_dependencies.each { |dep| base.include(dep) }
120
+ super
121
+ base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
122
+ base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
123
+ end
124
+ end
125
+
126
+ # Evaluate given block in context of base class,
127
+ # so that you can write class macros here.
128
+ # When you define more than one +included+ block, it raises an exception.
129
+ def included(base = nil, &block)
130
+ if base.nil?
131
+ if instance_variable_defined?(:@_included_block)
132
+ if @_included_block.source_location != block.source_location
133
+ raise MultipleIncludedBlocks
134
+ end
135
+ else
136
+ @_included_block = block
137
+ end
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ # Define class methods from given block.
144
+ # You can define private class methods as well.
145
+ #
146
+ # module Example
147
+ # extend ActiveSupport::Concern
148
+ #
149
+ # class_methods do
150
+ # def foo; puts 'foo'; end
151
+ #
152
+ # private
153
+ # def bar; puts 'bar'; end
154
+ # end
155
+ # end
156
+ #
157
+ # class Buzz
158
+ # include Example
159
+ # end
160
+ #
161
+ # Buzz.foo # => "foo"
162
+ # Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
163
+ def class_methods(&class_methods_module_definition)
164
+ mod = const_defined?(:ClassMethods, false) ?
165
+ const_get(:ClassMethods) :
166
+ const_set(:ClassMethods, Module.new)
167
+
168
+ mod.module_eval(&class_methods_module_definition)
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module ActiveSupport
6
+ module Concurrency
7
+ # A monitor that will permit dependency loading while blocked waiting for
8
+ # the lock.
9
+ class LoadInterlockAwareMonitor < Monitor
10
+ # Enters an exclusive section, but allows dependency loading while blocked
11
+ def mon_enter
12
+ mon_try_enter ||
13
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "monitor"
5
+
6
+ module ActiveSupport
7
+ module Concurrency
8
+ # A share/exclusive lock, otherwise known as a read/write lock.
9
+ #
10
+ # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
11
+ class ShareLock
12
+ include MonitorMixin
13
+
14
+ # We track Thread objects, instead of just using counters, because
15
+ # we need exclusive locks to be reentrant, and we need to be able
16
+ # to upgrade share locks to exclusive.
17
+
18
+ def raw_state # :nodoc:
19
+ synchronize do
20
+ threads = @sleeping.keys | @sharing.keys | @waiting.keys
21
+ threads |= [@exclusive_thread] if @exclusive_thread
22
+
23
+ data = {}
24
+
25
+ threads.each do |thread|
26
+ purpose, compatible = @waiting[thread]
27
+
28
+ data[thread] = {
29
+ thread: thread,
30
+ sharing: @sharing[thread],
31
+ exclusive: @exclusive_thread == thread,
32
+ purpose: purpose,
33
+ compatible: compatible,
34
+ waiting: !!@waiting[thread],
35
+ sleeper: @sleeping[thread],
36
+ }
37
+ end
38
+
39
+ # NB: Yields while holding our *internal* synchronize lock,
40
+ # which is supposed to be used only for a few instructions at
41
+ # a time. This allows the caller to inspect additional state
42
+ # without things changing out from underneath, but would have
43
+ # disastrous effects upon normal operation. Fortunately, this
44
+ # method is only intended to be called when things have
45
+ # already gone wrong.
46
+ yield data
47
+ end
48
+ end
49
+
50
+ def initialize
51
+ super()
52
+
53
+ @cv = new_cond
54
+
55
+ @sharing = Hash.new(0)
56
+ @waiting = {}
57
+ @sleeping = {}
58
+ @exclusive_thread = nil
59
+ @exclusive_depth = 0
60
+ end
61
+
62
+ # Returns false if +no_wait+ is set and the lock is not
63
+ # immediately available. Otherwise, returns true after the lock
64
+ # has been acquired.
65
+ #
66
+ # +purpose+ and +compatible+ work together; while this thread is
67
+ # waiting for the exclusive lock, it will yield its share (if any)
68
+ # to any other attempt whose +purpose+ appears in this attempt's
69
+ # +compatible+ list. This allows a "loose" upgrade, which, being
70
+ # less strict, prevents some classes of deadlocks.
71
+ #
72
+ # For many resources, loose upgrades are sufficient: if a thread
73
+ # is awaiting a lock, it is not running any other code. With
74
+ # +purpose+ matching, it is possible to yield only to other
75
+ # threads whose activity will not interfere.
76
+ def start_exclusive(purpose: nil, compatible: [], no_wait: false)
77
+ synchronize do
78
+ unless @exclusive_thread == Thread.current
79
+ if busy_for_exclusive?(purpose)
80
+ return false if no_wait
81
+
82
+ yield_shares(purpose: purpose, compatible: compatible, block_share: true) do
83
+ wait_for(:start_exclusive) { busy_for_exclusive?(purpose) }
84
+ end
85
+ end
86
+ @exclusive_thread = Thread.current
87
+ end
88
+ @exclusive_depth += 1
89
+
90
+ true
91
+ end
92
+ end
93
+
94
+ # Relinquish the exclusive lock. Must only be called by the thread
95
+ # that called start_exclusive (and currently holds the lock).
96
+ def stop_exclusive(compatible: [])
97
+ synchronize do
98
+ raise "invalid unlock" if @exclusive_thread != Thread.current
99
+
100
+ @exclusive_depth -= 1
101
+ if @exclusive_depth == 0
102
+ @exclusive_thread = nil
103
+
104
+ if eligible_waiters?(compatible)
105
+ yield_shares(compatible: compatible, block_share: true) do
106
+ wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) }
107
+ end
108
+ end
109
+ @cv.broadcast
110
+ end
111
+ end
112
+ end
113
+
114
+ def start_sharing
115
+ synchronize do
116
+ if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current
117
+ # We already hold a lock; nothing to wait for
118
+ elsif @waiting[Thread.current]
119
+ # We're nested inside a +yield_shares+ call: we'll resume as
120
+ # soon as there isn't an exclusive lock in our way
121
+ wait_for(:start_sharing) { @exclusive_thread }
122
+ else
123
+ # This is an initial / outermost share call: any outstanding
124
+ # requests for an exclusive lock get to go first
125
+ wait_for(:start_sharing) { busy_for_sharing?(false) }
126
+ end
127
+ @sharing[Thread.current] += 1
128
+ end
129
+ end
130
+
131
+ def stop_sharing
132
+ synchronize do
133
+ if @sharing[Thread.current] > 1
134
+ @sharing[Thread.current] -= 1
135
+ else
136
+ @sharing.delete Thread.current
137
+ @cv.broadcast
138
+ end
139
+ end
140
+ end
141
+
142
+ # Execute the supplied block while holding the Exclusive lock. If
143
+ # +no_wait+ is set and the lock is not immediately available,
144
+ # returns +nil+ without yielding. Otherwise, returns the result of
145
+ # the block.
146
+ #
147
+ # See +start_exclusive+ for other options.
148
+ def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
149
+ if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
150
+ begin
151
+ yield
152
+ ensure
153
+ stop_exclusive(compatible: after_compatible)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Execute the supplied block while holding the Share lock.
159
+ def sharing
160
+ start_sharing
161
+ begin
162
+ yield
163
+ ensure
164
+ stop_sharing
165
+ end
166
+ end
167
+
168
+ # Temporarily give up all held Share locks while executing the
169
+ # supplied block, allowing any +compatible+ exclusive lock request
170
+ # to proceed.
171
+ def yield_shares(purpose: nil, compatible: [], block_share: false)
172
+ loose_shares = previous_wait = nil
173
+ synchronize do
174
+ if loose_shares = @sharing.delete(Thread.current)
175
+ if previous_wait = @waiting[Thread.current]
176
+ purpose = nil unless purpose == previous_wait[0]
177
+ compatible &= previous_wait[1]
178
+ end
179
+ compatible |= [false] unless block_share
180
+ @waiting[Thread.current] = [purpose, compatible]
181
+ end
182
+
183
+ @cv.broadcast
184
+ end
185
+
186
+ begin
187
+ yield
188
+ ensure
189
+ synchronize do
190
+ wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current }
191
+
192
+ if previous_wait
193
+ @waiting[Thread.current] = previous_wait
194
+ else
195
+ @waiting.delete Thread.current
196
+ end
197
+ @sharing[Thread.current] = loose_shares if loose_shares
198
+ end
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ # Must be called within synchronize
205
+ def busy_for_exclusive?(purpose)
206
+ busy_for_sharing?(purpose) ||
207
+ @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
208
+ end
209
+
210
+ def busy_for_sharing?(purpose)
211
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
212
+ @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
213
+ end
214
+
215
+ def eligible_waiters?(compatible)
216
+ @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
217
+ end
218
+
219
+ def wait_for(method)
220
+ @sleeping[Thread.current] = method
221
+ @cv.wait_while { yield }
222
+ ensure
223
+ @sleeping.delete Thread.current
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/ordered_options"
5
+
6
+ module ActiveSupport
7
+ # Configurable provides a <tt>config</tt> method to store and retrieve
8
+ # configuration options as an <tt>OrderedHash</tt>.
9
+ module Configurable
10
+ extend ActiveSupport::Concern
11
+
12
+ class Configuration < ActiveSupport::InheritableOptions
13
+ def compile_methods!
14
+ self.class.compile_methods!(keys)
15
+ end
16
+
17
+ # Compiles reader methods so we don't have to go through method_missing.
18
+ def self.compile_methods!(keys)
19
+ keys.reject { |m| method_defined?(m) }.each do |key|
20
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
21
+ def #{key}; _get(#{key.inspect}); end
22
+ RUBY
23
+ end
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def config
29
+ @_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
30
+ superclass.config.inheritable_copy
31
+ else
32
+ # create a new "anonymous" class that will host the compiled reader methods
33
+ Class.new(Configuration).new
34
+ end
35
+ end
36
+
37
+ def configure
38
+ yield config
39
+ end
40
+
41
+ # Allows you to add shortcut so that you don't have to refer to attribute
42
+ # through config. Also look at the example for config to contrast.
43
+ #
44
+ # Defines both class and instance config accessors.
45
+ #
46
+ # class User
47
+ # include ActiveSupport::Configurable
48
+ # config_accessor :allowed_access
49
+ # end
50
+ #
51
+ # User.allowed_access # => nil
52
+ # User.allowed_access = false
53
+ # User.allowed_access # => false
54
+ #
55
+ # user = User.new
56
+ # user.allowed_access # => false
57
+ # user.allowed_access = true
58
+ # user.allowed_access # => true
59
+ #
60
+ # User.allowed_access # => false
61
+ #
62
+ # The attribute name must be a valid method name in Ruby.
63
+ #
64
+ # class User
65
+ # include ActiveSupport::Configurable
66
+ # config_accessor :"1_Badname"
67
+ # end
68
+ # # => NameError: invalid config attribute name
69
+ #
70
+ # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
71
+ # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
72
+ #
73
+ # class User
74
+ # include ActiveSupport::Configurable
75
+ # config_accessor :allowed_access, instance_reader: false, instance_writer: false
76
+ # end
77
+ #
78
+ # User.allowed_access = false
79
+ # User.allowed_access # => false
80
+ #
81
+ # User.new.allowed_access = true # => NoMethodError
82
+ # User.new.allowed_access # => NoMethodError
83
+ #
84
+ # Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
85
+ #
86
+ # class User
87
+ # include ActiveSupport::Configurable
88
+ # config_accessor :allowed_access, instance_accessor: false
89
+ # end
90
+ #
91
+ # User.allowed_access = false
92
+ # User.allowed_access # => false
93
+ #
94
+ # User.new.allowed_access = true # => NoMethodError
95
+ # User.new.allowed_access # => NoMethodError
96
+ #
97
+ # Also you can pass a block to set up the attribute with a default value.
98
+ #
99
+ # class User
100
+ # include ActiveSupport::Configurable
101
+ # config_accessor :hair_colors do
102
+ # [:brown, :black, :blonde, :red]
103
+ # end
104
+ # end
105
+ #
106
+ # User.hair_colors # => [:brown, :black, :blonde, :red]
107
+ def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :doc:
108
+ names.each do |name|
109
+ raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name)
110
+
111
+ reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
112
+ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
113
+
114
+ singleton_class.class_eval reader, __FILE__, reader_line
115
+ singleton_class.class_eval writer, __FILE__, writer_line
116
+
117
+ if instance_accessor
118
+ class_eval reader, __FILE__, reader_line if instance_reader
119
+ class_eval writer, __FILE__, writer_line if instance_writer
120
+ end
121
+ send("#{name}=", yield) if block_given?
122
+ end
123
+ end
124
+ private :config_accessor
125
+ end
126
+
127
+ # Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
128
+ #
129
+ # require 'active_support/configurable'
130
+ #
131
+ # class User
132
+ # include ActiveSupport::Configurable
133
+ # end
134
+ #
135
+ # user = User.new
136
+ #
137
+ # user.config.allowed_access = true
138
+ # user.config.level = 1
139
+ #
140
+ # user.config.allowed_access # => true
141
+ # user.config.level # => 1
142
+ def config
143
+ @_config ||= self.class.config.inheritable_copy
144
+ end
145
+ end
146
+ end