process_settings 0.14.0 → 0.18.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6719811e3b27382e1c3ab6872a6dc3775b6a06c1971ac980d62f0130252d66e
4
- data.tar.gz: d206be34980dd24cf6dd413d8db0e8e63a4b49409ff392135fada36bb68c4295
3
+ metadata.gz: 15a15a0c5c7ea497858038e4b4be06e63aea209c8e3033662c28963d19839d39
4
+ data.tar.gz: ba4e23e5216b60ecc7e5e999dcac7d1679c90fd2cf6b712624da9a04aa629c1a
5
5
  SHA512:
6
- metadata.gz: bc5ba17baca2d071bd68aed1a685d223e0febe073463e46344d314003004948463e7e9da400198ad45a075a599aff7bd34a6989f4223e598ea014026e611ab18
7
- data.tar.gz: cee3d3980e62c840c061df1e8f5d44aec487c855d49b467d81b7bf52f5d48cf61f02ef97759a7eb1b43b575db39beee0f73849fe36f5069976adb35f17deff0b
6
+ metadata.gz: 44058ab68dbb74cdf889bd57ac0bff0324f35f4608a27f67623da3b17a8ee0b4f9bfef24311395e969f941db28c2a5137c79c3233db75659fcc9e1f4239797e1
7
+ data.tar.gz: 3310afc60c343999207561d0e2820dc6ff4e985f71b1e25b8402fe7f3f39c762dd560f83cf78a5ccd57fa937a13c9dfdb43f41e57e8aa3326eb3a63d05f628bd
data/README.md CHANGED
@@ -167,6 +167,19 @@ This will be applied in any process that has (`service_name == "frontend"` OR `s
167
167
  ### Precedence
168
168
  The settings YAML files are always combined in alphabetical order by file path. Later settings take precedence over the earlier ones.
169
169
 
170
+ ### Forked Processes
171
+ When using `ProcessSettings` within an environment that is forking threads (like `unicorn` web servers), you can restart the `FileMonitor`
172
+ after the fork with `restart_after_fork`.
173
+ ```ruby
174
+ # unicorn.rb
175
+
176
+ preload_app true
177
+
178
+ after_fork do
179
+ ProcessSettings.instance.restart_after_fork
180
+ end
181
+ ```
182
+
170
183
  ### Testing
171
184
  For testing, it is often necessary to set a specific override hash for the process_settings values to use in
172
185
  that use case. The `ProcessSettings::Testing::RSpec::Helpers` and `ProcessSettings::Testing::Minitest::Helpers` modules are provided for this purpose.
@@ -181,7 +194,7 @@ require 'process_settings/testing/helpers'
181
194
  RSpec.configure do |config|
182
195
  # ...
183
196
 
184
- include ProcessSettings::Testing::RSpec::Helpers
197
+ config.include ProcessSettings::Testing::RSpec::Helpers
185
198
 
186
199
  # Note: the include above will automatically register a global after block that will reset process_settings to their initial values.
187
200
  # ...
@@ -92,6 +92,26 @@ def warn_if_old_libyaml_version
92
92
  end
93
93
  end
94
94
 
95
+ def settings_files_match?(filename_1, filename_2)
96
+ filename_1 && filename_2 &&
97
+ File.exist?(filename_1) && File.exist?(filename_2) &&
98
+ diff_process_settings(filename_1, filename_2)
99
+ end
100
+
101
+ def diff_process_settings(filename_1, filename_2)
102
+ system(<<~EOS)
103
+ bundle exec diff_process_settings --silent "#{filename_1}" "#{filename_2}"
104
+ EOS
105
+ status_code = $?.exitstatus
106
+ case status_code
107
+ when 0
108
+ true
109
+ when 1
110
+ false
111
+ else
112
+ raise "diff_process_settings failed with code #{status_code}"
113
+ end
114
+ end
95
115
 
96
116
  #
97
117
  # main
@@ -115,14 +135,17 @@ tmp_output_filename = "#{output_filename}.tmp"
115
135
  system("rm -f #{tmp_output_filename}")
116
136
  File.write(tmp_output_filename, yaml_with_warning_comment)
117
137
 
118
- system(<<~EOS)
119
- if bundle exec diff_process_settings --silent #{tmp_output_filename} #{output_filename} ; then
120
- #{"echo #{options.root_folder}: unchanged;" if options.verbose}
121
- rm -f #{tmp_output_filename};
138
+ if settings_files_match?(options.initial_filename, tmp_output_filename)
139
+ if settings_files_match?(output_filename, tmp_output_filename)
140
+ puts "#{options.root_folder}: unchanged" if options.verbose
141
+ FileUtils.rm_f(tmp_output_filename)
122
142
  else
123
- #{"echo #{options.root_folder}: UPDATING;" if options.verbose}
124
- mv #{tmp_output_filename} #{output_filename};
125
- fi
126
- EOS
143
+ puts "#{options.root_folder}: UPDATING (changed now)" if options.verbose
144
+ FileUtils.mv(tmp_output_filename, output_filename)
145
+ end
146
+ else
147
+ puts "#{options.root_folder}: UPDATING (unchanged now, but changed from initial)" if options.verbose
148
+ FileUtils.mv(tmp_output_filename, output_filename)
149
+ end
127
150
 
128
151
  exit(0)
@@ -34,10 +34,7 @@ input_files =
34
34
  if path == '-'
35
35
  ''
36
36
  else
37
- unless File.exists?(path)
38
- warn "#{path} not found--must be a path to combined_process_settings.yml"
39
- exit 1
40
- end
37
+ File.exist?(path) or path = '/dev/null'
41
38
  "< #{path}"
42
39
  end
43
40
  end
@@ -11,14 +11,16 @@ module ProcessSettings
11
11
  OnChangeDeprecation = ActiveSupport::Deprecation.new('1.0', 'ProcessSettings::Monitor')
12
12
 
13
13
  class AbstractMonitor
14
+
14
15
  attr_reader :min_polling_seconds, :logger
15
- attr_reader :static_context, :statically_targeted_settings
16
+ attr_reader :static_context, :statically_targeted_settings, :full_context_cache
16
17
 
17
18
  def initialize(logger:)
18
19
  @logger = logger or raise ArgumentError, "logger must be not be nil"
19
20
  @on_change_callbacks = []
20
21
  @when_updated_blocks = Set.new
21
22
  @static_context = {}
23
+ @full_context_cache = {}
22
24
  end
23
25
 
24
26
  # This is the main entry point for looking up settings on the Monitor instance.
@@ -94,10 +96,20 @@ module ProcessSettings
94
96
  def targeted_value(*path, dynamic_context:, required: true)
95
97
  # Merging the static context in is necessary to make sure that the static context isn't shifting
96
98
  # this can be rather costly to do every time if the dynamic context is not changing
97
- # TODO: Warn in the case where dynamic context was attempting to change a static value
98
- # TODO: Cache the last used dynamic context as a potential optimization to avoid unnecessary deep merges
99
- # TECH-4402 was created to address these todos
100
- full_context = dynamic_context.deep_merge(static_context)
99
+
100
+ # Warn in the case where dynamic context was attempting to change a static value
101
+ changes = dynamic_context.each_with_object({}) do |(key, dynamic_value), result|
102
+ if static_context.has_key?(key)
103
+ static_value = static_context[key]
104
+ if static_value != dynamic_value
105
+ result[key] = [static_value, dynamic_value]
106
+ end
107
+ end
108
+ end
109
+
110
+ changes.empty? or warn("WARNING: static context overwritten by dynamic!\n#{changes.inspect}")
111
+
112
+ full_context = full_context_from_cache(dynamic_context)
101
113
  result = statically_targeted_settings.reduce(:not_found) do |latest_result, target_and_settings|
102
114
  # find last value from matching targets
103
115
  if target_and_settings.target.target_key_matches?(full_context)
@@ -119,6 +131,18 @@ module ProcessSettings
119
131
  end
120
132
  end
121
133
 
134
+ def full_context_from_cache(dynamic_context)
135
+ if (full_context = full_context_cache[dynamic_context])
136
+ full_context
137
+ else
138
+ dynamic_context.deep_merge(static_context).tap do |full_context|
139
+ if full_context_cache.size <= 1000
140
+ full_context_cache[dynamic_context] = full_context
141
+ end
142
+ end
143
+ end
144
+ end
145
+
122
146
  private
123
147
 
124
148
  class << self
@@ -8,6 +8,8 @@ require 'active_support/deprecation'
8
8
  require 'process_settings/abstract_monitor'
9
9
  require 'process_settings/targeted_settings'
10
10
  require 'process_settings/hash_path'
11
+ require 'process_settings/helpers/watchdog'
12
+ require 'exception_handling'
11
13
 
12
14
  module ProcessSettings
13
15
  class FileMonitor < AbstractMonitor
@@ -24,10 +26,39 @@ module ProcessSettings
24
26
  start_internal(enable_listen_thread?(environment))
25
27
  end
26
28
 
29
+ def start_watchdog_thread(file_path = :missing)
30
+ if file_path == :missing
31
+ ActiveSupport::Deprecation.warn("ProcessEnvSetup.start_watchdog_thread with no arguments is deprecated")
32
+ file_path = ProcessSettings::Monitor.file_path
33
+ end
34
+
35
+ @watchdog_thread and raise ArgumentError, "watchdog thread already running!"
36
+ @watchdog_thread = Thread.new do
37
+ watchdog = ProcessSettings::Watchdog.new(file_path)
38
+ loop do
39
+ ExceptionHandling.ensure_safe("ProcessSettings::Watchdog thread") do
40
+ sleep(1.minute)
41
+ watchdog.check
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def stop_watchdog_thread
48
+ if @watchdog_thread
49
+ Thread.kill(@watchdog_thread)
50
+ @watchdog_thread = nil
51
+ end
52
+ end
53
+
27
54
  def start
28
55
  start_internal(enable_listen_thread?)
29
56
  end
30
- deprecate :start, deprecator: ActiveSupport::Deprecation.new('1.0', 'ProcessSettings') # will become private
57
+ deprecate start: :restart_after_fork, deprecator: ActiveSupport::Deprecation.new('1.0', 'ProcessSettings')
58
+
59
+ def restart_after_fork
60
+ start_internal(enable_listen_thread?)
61
+ end
31
62
 
32
63
  def listen_thread_running?
33
64
  !@listener.nil?
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/numeric/time'
4
+
5
+ module ProcessSettings
6
+ class Watchdog
7
+ class OutOfSync < StandardError; end
8
+
9
+ MAX_MTIME_DIFFERENCE = 2.minutes
10
+
11
+ def initialize(process_settings_file_path)
12
+ @process_settings_file_path = process_settings_file_path or raise ArgumentError, "process_settings_file_path must be passed"
13
+ end
14
+
15
+ def check
16
+ if version_from_memory != version_from_disk && (Time.now - mtime_from_disk) > MAX_MTIME_DIFFERENCE
17
+ raise ProcessSettings::OutOfSync.new("ProcessSettings versions are out of sync!\n Version from Disk: #{version_from_disk}\n Version from Memory: #{version_from_memory}\n mtime of file: #{mtime_from_disk}")
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :process_settings_file_path
24
+
25
+ def version_from_memory
26
+ ProcessSettings.instance.untargeted_settings.version
27
+ end
28
+
29
+ def version_from_disk
30
+ ProcessSettings::TargetedSettings.from_file(process_settings_file_path, only_meta: true).version
31
+ end
32
+
33
+ def mtime_from_disk
34
+ File.mtime(process_settings_file_path)
35
+ end
36
+ end
37
+ end
@@ -46,7 +46,7 @@ module ProcessSettings
46
46
  def logger=(new_logger)
47
47
  ActiveSupport::Deprecation.warn("ProcessSettings::Monitor.logger is deprecated and will be removed in v1.0.")
48
48
  @logger = new_logger
49
- Listen.logger ||= new_logger
49
+ Listen.logger = new_logger unless Listen.instance_variable_get(:@logger)
50
50
  end
51
51
 
52
52
  deprecate :logger, :logger=, :file_path, :file_path=, deprecator: ActiveSupport::Deprecation.new('1.0', 'ProcessSettings')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProcessSettings
4
- VERSION = '0.14.0'
4
+ VERSION = '0.18.0.pre.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.18.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca
@@ -94,6 +94,7 @@ files:
94
94
  - lib/process_settings/file_monitor.rb
95
95
  - lib/process_settings/hash_path.rb
96
96
  - lib/process_settings/hash_with_hash_path.rb
97
+ - lib/process_settings/helpers/watchdog.rb
97
98
  - lib/process_settings/monitor.rb
98
99
  - lib/process_settings/replace_versioned_file.rb
99
100
  - lib/process_settings/settings.rb
@@ -122,9 +123,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
123
  version: '0'
123
124
  required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  requirements:
125
- - - ">="
126
+ - - ">"
126
127
  - !ruby/object:Gem::Version
127
- version: '0'
128
+ version: 1.3.1
128
129
  requirements: []
129
130
  rubygems_version: 3.0.3
130
131
  signing_key: