caddy 1.5.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1fcc04e3d12bf33c0268a418a46e4d255d8f9e38
4
- data.tar.gz: bae67e7130cb5ce2dfd39086c5757e72acf53217
3
+ metadata.gz: ed9c2bf138fdfa093e81a888f423ade1a55890af
4
+ data.tar.gz: 0cb897a71cb7bb5345059a80c56c9f93cb25e29d
5
5
  SHA512:
6
- metadata.gz: e4fbdcfbf10f665d85497e149f35b418e1f90c79db76e32d78221abdea6a4c66415e5117c64d65fc3c4be5dc6d956ab3b3102f2f39823958d5b415b67978057b
7
- data.tar.gz: 5cc6776a9d1060785c3d58f306a53157f9994706799905ab445503e23c6a96bc60fde75c56f6d4f9e7f0d102a35a113c3adf0282a72182208bfcb591c116fb49
6
+ metadata.gz: 6ebff58f7afbfcaa321a3f69158c92636cd545946df87d11eb660911f54ae938256fcb3c7b77b94793296dc40db66aad3a654dc0ee3563e82ad335970f9bf824
7
+ data.tar.gz: b6f5786869e114c14d3374f086c7b439c6312b637ee45079d7fd7f8d99dcb012804a64fb000df9409b29220db793ffa1fb2f336fe247fa57e3aa7db6f1baf700
@@ -0,0 +1 @@
1
+ --no-private
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "caddy"
5
+ require "irb"
6
+
7
+ IRB.start
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -5,23 +5,31 @@ require "caddy/version"
5
5
  require "caddy/task_observer"
6
6
  require "caddy/cache"
7
7
 
8
+ # Caddy gives you a auto-updating global cache to speed up requests
8
9
  module Caddy
9
10
  class << self
11
+ # @!attribute error_handler
12
+ # @return [Proc] called when any cache refresher throws an exception (or times out)
10
13
  attr_accessor :error_handler
14
+
15
+ # see {#logger}
16
+ attr_writer :logger
11
17
  end
12
18
 
13
19
  @started_pid = nil
14
20
  @caches = Hash.new { |h, k| h[k] = Caddy::Cache.new(k) }
21
+ @needs_fork_check = !Concurrent::TimerSet.private_method_defined?(:ns_reset_if_forked)
15
22
 
16
- ##
17
- # Returns the cache object for key +k+.
23
+ # Returns the cache object at a key.
18
24
  #
19
25
  # If the cache at +k+ does not exist yet, Caddy will initialize an empty one.
26
+ #
27
+ # @param k [Symbol] the cache key.
28
+ # @return [Caddy::Cache] the cache object at key +k+.
20
29
  def self.[](k)
21
30
  @caches[k]
22
31
  end
23
32
 
24
- ##
25
33
  # Starts the Caddy refresh processes for all caches.
26
34
  #
27
35
  # If the refresh process was started pre-fork, Caddy will error out, as this means
@@ -30,27 +38,47 @@ module Caddy
30
38
  # Caddy freezes the hash of caches at this point, so no more further caches can be
31
39
  # added after start.
32
40
  def self.start
33
- if !@started_pid
34
- @started_pid = $$
35
- elsif @started_pid && $$ != @started_pid
36
- raise "Please run `Caddy.start` *after* forking, as the refresh thread will get killed after fork"
37
- end
38
-
39
- @caches.freeze
41
+ raise_if_forked if @needs_fork_check
42
+ logger.info "Starting Caddy with refreshers: #{@caches.keys.join(', ')}"
40
43
 
41
44
  @caches.values.each(&:start).all?
42
45
  end
43
46
 
44
- ##
45
47
  # Cleanly shut down all currently running refreshers.
46
48
  def self.stop
49
+ logger.info "Stopping Caddy refreshers"
50
+
47
51
  @caches.values.each(&:stop).all?
48
52
  end
49
53
 
50
- ##
51
- # Start and then stop again all refreshers. Useful for triggering an immediate refresh of all caches.
54
+ # Start and then stop all refreshers. Useful for triggering an immediate refresh of all caches.
52
55
  def self.restart
53
56
  stop
54
57
  start
55
58
  end
59
+
60
+ # @!attribute logger
61
+ # @return [Logger] logger used for all non-fatals; defaults to the Rails logger if it exists
62
+ def self.logger
63
+ @logger ||= begin
64
+ if defined?(Rails.logger)
65
+ Rails.logger
66
+ else
67
+ @logger ||= Logger.new(STDOUT).tap do |logger|
68
+ logger.formatter = -> (_, datetime, _, msg) { "#{datetime}: #{msg}\n" }
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ # TODO: when https://github.com/ruby-concurrency/concurrent-ruby/pull/573 is merged, remove this whole block
75
+ # or add a warning
76
+ # @private
77
+ def self.raise_if_forked
78
+ if !@started_pid
79
+ @started_pid = $$
80
+ elsif @started_pid && $$ != @started_pid
81
+ raise "Please run `Caddy.start` *after* forking, as the refresh thread will get killed after fork"
82
+ end
83
+ end
56
84
  end
@@ -1,13 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
  module Caddy
3
3
  class Cache
4
+ # Default refresh interval, in seconds
4
5
  DEFAULT_REFRESH_INTERVAL = 60
6
+
7
+ # Percentage to randomly smooth the refresh interval to avoid stampeding herd on expiration
5
8
  REFRESH_INTERVAL_JITTER_PCT = 0.15
6
9
 
7
- attr_accessor :refresher, :refresh_interval, :error_handler
10
+ # @!attribute refresher
11
+ # @return [Proc] called on interval {#refresh_interval} with the returned object used as the cache
12
+ attr_accessor :refresher
13
+
14
+ # @!attribute refresh_interval
15
+ # @return [Numeric] number of seconds between calls to {#refresher}; timeout is set to <tt>{#refresher} - 0.1</tt>
16
+ attr_accessor :refresh_interval
8
17
 
9
- ##
10
- # Create a new cache with key +key+.
18
+ # @!attribute error_handler
19
+ # @return [Proc] if unset, defaults to the global error handler (see #{Caddy.error_handler});
20
+ # called when exceptions or timeouts happen within the refresher
21
+ attr_accessor :error_handler
22
+
23
+ # Create a new periodically updated cache.
24
+ # @param key [Symbol] the name of this cache
11
25
  def initialize(key)
12
26
  @task = nil
13
27
  @refresh_interval = DEFAULT_REFRESH_INTERVAL
@@ -15,25 +29,28 @@ module Caddy
15
29
  @key = key
16
30
  end
17
31
 
18
- ##
19
32
  # Convenience method for getting the value of the refresher-returned object at path +k+,
20
33
  # assuming the refresher-returned value responds to <tt>[]</tt>.
21
34
  #
22
- # If not, #cache can be used instead to access the refresher-returned object.
35
+ # If not, {#cache} can be used instead to access the refresher-returned object.
36
+ # @param k key to access from the refresher-returned cache.
23
37
  def [](k)
24
38
  cache[k]
25
39
  end
26
40
 
27
- ##
28
41
  # Returns the refresher-produced value that is used as the cache.
29
42
  def cache
30
43
  raise "Please run `Caddy.start` before attempting to access the cache" unless @task && @task.running?
31
- raise "Caddy cache access of :#{@key} before initial load; allow some more time for your app to start up" unless @cache
44
+
45
+ unless @cache
46
+ logger.warn "Caddy cache access of :#{@key} before initial load; doing synchronous load."\
47
+ "Please allow some more time for your app to start up."
48
+ refresh
49
+ end
32
50
 
33
51
  @cache
34
52
  end
35
53
 
36
- ##
37
54
  # Starts the period refresh cycle.
38
55
  #
39
56
  # Every +refresh_interval+ seconds -- smoothed by a jitter amount (a random amount +/- +REFRESH_INTERVAL_JITTER_PCT+) --
@@ -58,22 +75,38 @@ module Caddy
58
75
  execution_interval: interval,
59
76
  timeout_interval: timeout_interval
60
77
  ) do
61
- @cache = refresher.call.freeze
62
- nil # no need to save the value internally to TimerTask
78
+ refresh
79
+ nil # no need for the {#Concurrent::TimerTask} to keep a reference to the value
63
80
  end
64
81
 
65
82
  @task.add_observer(Caddy::TaskObserver.new(error_handler, @key))
83
+
84
+ logger.debug "Starting Caddy refresher for :#{@key}, updating every #{interval.round(1)}s."
85
+
66
86
  @task.execute
67
87
 
68
88
  @task.running?
69
89
  end
70
90
 
71
- ##
72
91
  # Stops the current executing refresher.
73
92
  #
74
93
  # The current cache value is persisted even if the task is stopped.
75
94
  def stop
76
95
  @task.shutdown if @task && @task.running?
77
96
  end
97
+
98
+ # Updates the internal cache object.
99
+ #
100
+ # Freezes the result to avoid mutation errors.
101
+ def refresh
102
+ @cache = refresher.call.freeze
103
+ end
104
+
105
+ private
106
+
107
+ # Delegates logging to the module logger
108
+ def logger
109
+ Caddy.logger
110
+ end
78
111
  end
79
112
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Caddy
3
- class TaskObserver #:nodoc:
3
+ # {TaskObserver} is used internally to monitor the status of the running refreshers
4
+ # @private
5
+ class TaskObserver
4
6
  def initialize(error_handler, cache_name)
5
7
  @error_handler = error_handler || Caddy.error_handler
6
8
  @cache_name = cache_name
@@ -14,28 +16,32 @@ module Caddy
14
16
  begin
15
17
  @error_handler.call(boom)
16
18
  rescue => incepted_boom
17
- puts_exception("Caddy error handler itself errored handling refresh for :#{@cache_name}", incepted_boom)
19
+ log_exception("Caddy error handler itself errored handling refresh for :#{@cache_name}", incepted_boom)
18
20
  end
19
21
  else
20
22
  # rubocop:disable Style/StringLiterals
21
- STDERR.puts 'Caddy error handler not callable. Please set the error handler like:'\
22
- ' `Caddy.error_handler = -> (e) { puts "#{e}" }`'
23
+ logger.error 'Caddy error handler not callable. Please set the error handler like:'\
24
+ ' `Caddy.error_handler = -> (e) { puts "#{e}" }`'
23
25
  # rubocop:enable Style/StringLiterals
24
26
 
25
- puts_exception("Caddy refresher for :#{@cache_name} failed with error", boom)
27
+ log_exception("Caddy refresher for :#{@cache_name} failed with error", boom)
26
28
  end
27
29
  elsif boom.is_a?(Concurrent::TimeoutError)
28
- STDERR.puts "Caddy refresher for :#{@cache_name} timed out"
30
+ logger.error "Caddy refresher for :#{@cache_name} timed out"
29
31
  else
30
- puts_exception("Caddy refresher for :#{@cache_name} failed with error", boom)
32
+ log_exception("Caddy refresher for :#{@cache_name} failed with error", boom)
31
33
  end
32
34
  end
33
35
 
34
36
  private
35
37
 
36
- def puts_exception(msg, boom)
37
- STDERR.puts "\n#{msg}: #{boom}"
38
- STDERR.puts "\t#{boom.backtrace.join("\n\t")}"
38
+ def log_exception(msg, boom)
39
+ logger.error "\n#{msg}: #{boom}"
40
+ logger.error "\t#{boom.backtrace.join("\n\t")}"
41
+ end
42
+
43
+ def logger
44
+ Caddy.logger
39
45
  end
40
46
  end
41
47
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Caddy
3
- VERSION = "1.5.5".freeze
3
+ # Current version
4
+ VERSION = "1.6.0".freeze
4
5
  end
@@ -14,6 +14,14 @@ class CaddyTest < Minitest::Test
14
14
  sleep(0.05)
15
15
  end
16
16
 
17
+ def with_test_logger
18
+ sio = StringIO.new
19
+ Caddy.logger = Logger.new(sio)
20
+ yield
21
+ Caddy.logger = $test_logger
22
+ sio.string
23
+ end
24
+
17
25
  def test_basic_lookup
18
26
  Caddy[:test].refresher = -> { {foo: "bar"} }
19
27
  Caddy.start
@@ -122,15 +130,23 @@ class CaddyTest < Minitest::Test
122
130
  def test_incepted_error_handling
123
131
  Caddy[:test].refresher = -> { raise "boom" }
124
132
  Caddy.error_handler = -> (_) { raise "boomboom" }
125
- Caddy.start
126
- sleep(0.1)
133
+ log_output = with_test_logger do
134
+ Caddy.start
135
+ sleep(0.1)
136
+ end
137
+
138
+ assert_match(/Caddy error handler itself errored/, log_output)
127
139
  end
128
140
 
129
141
  def test_bad_error_handler
130
142
  Caddy[:test].refresher = -> { raise "boom" }
131
143
  Caddy.error_handler = "no"
132
- Caddy.start
133
- sleep(0.1)
144
+ log_output = with_test_logger do
145
+ Caddy.start
146
+ sleep(0.1)
147
+ end
148
+
149
+ assert_match(/Caddy error handler not callable/, log_output)
134
150
  end
135
151
 
136
152
  def test_timeout
@@ -149,16 +165,27 @@ class CaddyTest < Minitest::Test
149
165
  def test_no_handler_timeout
150
166
  Caddy[:test].refresher = -> { sleep 1 }
151
167
  Caddy[:test].refresh_interval = 0.5
152
- Caddy.start
153
- sleep(2)
154
- Caddy.stop
155
- sleep(2)
168
+
169
+ log_output = with_test_logger do
170
+ Caddy.start
171
+ sleep(2)
172
+ Caddy.stop
173
+ sleep(2)
174
+ end
175
+
176
+ assert_match(/timed out/, log_output)
156
177
  end
157
178
 
158
179
  def test_no_handler
159
180
  Caddy[:test].refresher = -> { raise "boom" }
160
181
  Caddy.start
161
182
  sleep(0.1)
183
+ log_output = with_test_logger do
184
+ Caddy.start
185
+ sleep(0.1)
186
+ end
187
+
188
+ assert_match(/failed with error/, log_output)
162
189
  end
163
190
 
164
191
  def test_requires_refesher
@@ -173,6 +200,19 @@ class CaddyTest < Minitest::Test
173
200
  assert_raises { Caddy[:nope][:not_there] }
174
201
  end
175
202
 
203
+ def test_does_synchronous_load
204
+ Caddy[:sync].refresher = -> { sleep 1; "sync" }
205
+
206
+ assert_raises { Caddy[:sync].cache }
207
+
208
+ log_output = with_test_logger do
209
+ Caddy.start
210
+ assert_equal "sync", Caddy[:sync].cache
211
+ end
212
+
213
+ assert_match(/doing synchronous load./, log_output)
214
+ end
215
+
176
216
  def test_requires_positive_interval
177
217
  Caddy[:test].refresh_interval = -2
178
218
 
@@ -7,3 +7,13 @@ end
7
7
 
8
8
  require "minitest/autorun"
9
9
  require "caddy"
10
+
11
+ ENV["RACK_ENV"] = ENV["RAILS_ENV"] = "test"
12
+
13
+ $test_logger = begin
14
+ l = Logger.new(STDOUT)
15
+ l.level = Logger::ERROR
16
+ l
17
+ end
18
+
19
+ Caddy.logger = $test_logger
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caddy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.5
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Elser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-10 00:00:00.000000000 Z
11
+ date: 2016-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -72,7 +72,9 @@ description: |2
72
72
  Caddy is great for storing information like feature flags -- accessed extremely frequently during many requests, updated relatively rarely and usually safe to be stale by some small duration.
73
73
  email:
74
74
  - nick.elser@gmail.com
75
- executables: []
75
+ executables:
76
+ - console
77
+ - setup
76
78
  extensions: []
77
79
  extra_rdoc_files: []
78
80
  files:
@@ -80,11 +82,14 @@ files:
80
82
  - ".gitignore"
81
83
  - ".rubocop.yml"
82
84
  - ".travis.yml"
85
+ - ".yardopts"
83
86
  - CHANGELOG.md
84
87
  - Gemfile
85
88
  - LICENSE.txt
86
89
  - README.md
87
90
  - Rakefile
91
+ - bin/console
92
+ - bin/setup
88
93
  - caddy.gemspec
89
94
  - docs/architecture.dot
90
95
  - docs/architecture.svg
@@ -121,4 +126,3 @@ summary: Caddy gives you a auto-updating global cache to speed up requests.
121
126
  test_files:
122
127
  - test/caddy_test.rb
123
128
  - test/test_helper.rb
124
- has_rdoc: