caddy 1.5.5 → 1.6.0

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
  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: