caddy 1.0.1 → 1.5.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: a3e8d643dfca9cc2e978b9ecd7887fea9f5b3158
4
- data.tar.gz: 25837935c927e4cf99d4cac355426a0ccec8d877
3
+ metadata.gz: 4d0bb8c605dbc32dbcb05731e669f1ccf00f58c7
4
+ data.tar.gz: 664c4aca36f43b4b6a441f54eaf9ca7a25c0d9b0
5
5
  SHA512:
6
- metadata.gz: 05ccf0297987d26658f8cd331d750357a92fa6f8258c462a18697e69552e99eff13df4bd33953bfebd27a17f64b5cff2cd1a5bd07f1d4c986a34c657d5f6adae
7
- data.tar.gz: 6527ac465c99634d5eec1eb2f195f7c92fe58d6d310a6b84a4f5bca12975cfd54bdd0b88e053970e5a74f862b1ecf10272a5ea2b9fdb9cfa469df304ed26979e
6
+ metadata.gz: 5420367fd7d24dfe740513085f54e8ec63646507e687deb6b8d9de37463991023c3a417c098ff26123602153d58ec66d4bf3a4b0abb08c40b24c1cb7dc581405
7
+ data.tar.gz: de4e27a4e7e66d956acde7a442943268b44e11a0f1f7c193d0a4e77b96952397600dc9b91c28cd6d7e4850e9ac7594e300e1b7543c01a77317f1d98fcc904849
data/README.md CHANGED
@@ -8,15 +8,18 @@ It's powered by [concurrent-ruby](https://github.com/ruby-concurrency/concurrent
8
8
 
9
9
  ```ruby
10
10
  # in your initializers (caddy.rb would be a wonderful name)
11
- Caddy.refresher = lambda do
12
- {
13
- flags: SomeFlagService.fetch_flags, # this can take a few seconds; it won't block requests when you use it later
14
- cache_keys: SomeCacheKeyService.cache_keys
15
- }
11
+ Caddy[:flags].refresher = lambda do
12
+ SomeFlagService.fetch_flags # this can take a few seconds; it won't block requests when you use it later
16
13
  end
17
14
 
18
- Caddy.refresh_interval = 30.seconds # default is 60 seconds; the actual amount is smoothed slightly
19
- # to avoid a stampeding herd of refreshes
15
+ # you can have multiple cache stores, refreshed at different intervals
16
+ Caddy[:cache_keys].refresher = lambda do
17
+ SomeCacheKeyService.cache_keys
18
+ end
19
+
20
+ Caddy[:flags].refresh_interval = 30.seconds # default is 60 seconds; the actual amount is smoothed slightly
21
+ # to avoid a stampeding herd of refreshes
22
+ Caddy[:cache_keys].refresh_interval = 5.minutes
20
23
 
21
24
  # ... after your application forks (see the guide below for Unicorn, Puma & Spring)
22
25
  Caddy.start
@@ -24,7 +27,7 @@ Caddy.start
24
27
  # ... in a controller
25
28
  def index
26
29
  # Caddy provides a convenience method to access the cache by key; you can also access
27
- # what your refresher returns directly with Caddy.cache[:flags][...]
30
+ # what your refresher returns directly with Caddy[:flags].cache
28
31
  if Caddy[:flags][:fuzz_bizz]
29
32
  Rails.cache.fetch("#{Caddy[:cache_keys][:global_key]}/#{Caddy[:cache_keys][:index_key]}/foo/bar") do
30
33
  # wonderful things happen here
@@ -33,6 +36,16 @@ def index
33
36
  end
34
37
  ```
35
38
 
39
+ ## Error handling
40
+
41
+ As Caddy refreshers are run in threads, exceptions are not normally reported (except by default in the logs). To add a programmatic error handler:
42
+
43
+ ```ruby
44
+ Caddy[:flags].refresher = -> { SomeFlagService.fetch_flags }
45
+ Caddy.error_handler = -> (exception) { ExceptionReporter.bad_thing_happened(exception) } # global (all caches) error handler
46
+ Caddy[:flags].error_handler = -> (exception) { SpecificExceptionReporter.worse_thing_happened(exception) } # cache-specific error reporters also supported
47
+ ```
48
+
36
49
  ## Using Caddy with Unicorn
37
50
 
38
51
  Start Caddy after fork:
@@ -1,75 +1,42 @@
1
1
  # frozen_string_literal: true
2
+ require "concurrent/timer_task"
3
+
2
4
  require "caddy/version"
3
5
  require "caddy/task_observer"
4
- require "concurrent/timer_task"
6
+ require "caddy/cache"
5
7
 
6
8
  module Caddy
7
9
  class << self
8
- attr_accessor :refresher, :error_handler, :refresh_interval
10
+ attr_accessor :error_handler
9
11
  end
10
12
 
11
- DEFAULT_REFRESH_INTERVAL = 60
12
- REFRESH_INTERVAL_JITTER_PCT = 0.15
13
-
14
- @task = nil
15
- @refresh_interval = DEFAULT_REFRESH_INTERVAL
16
- @_started_pid = nil
17
- @_cache = nil
13
+ @started_pid = nil
14
+ @caches = Hash.new { |h, k| h[k] = Caddy::Cache.new(k) }
18
15
 
19
16
  def self.[](k)
20
- cache[k]
17
+ @caches[k]
21
18
  end
22
19
 
23
- def self.cache
24
- raise "Please run `Caddy.start` before attempting to access the cache" unless @task
25
- raise "Caddy cache access before initial load; allow some more time for your app to start up" unless @_cache
26
-
27
- @_cache
20
+ def self.caches
21
+ @caches
28
22
  end
29
23
 
30
24
  def self.start
31
- unless refresher && refresher.respond_to?(:call)
32
- raise "Please set your cache refresher via `Caddy.refresher = -> { <code that returns a value> }`"
33
- end
34
-
35
- raise "`Caddy.refresh_interval` must be > 0" unless refresh_interval > 0
36
-
37
- if @_started_pid && $$ != @_started_pid
25
+ if !@started_pid
26
+ @started_pid = $$
27
+ elsif @started_pid && $$ != @started_pid
38
28
  raise "Please run `Caddy.start` *after* forking, as the refresh thread will get killed after fork"
39
29
  end
40
30
 
41
- jitter_amount = [0.5, refresh_interval * REFRESH_INTERVAL_JITTER_PCT].max
42
- interval = refresh_interval + rand(-jitter_amount...jitter_amount)
43
- timeout_interval = [interval - 1, 0.1].max
44
-
45
- stop # stop any existing task from running
46
-
47
- @task = Concurrent::TimerTask.new(
48
- run_now: true,
49
- execution_interval: interval,
50
- timeout_interval: timeout_interval
51
- ) do
52
- begin
53
- @_cache = refresher.call
54
- @_cache.freeze if @_cache.respond_to?(:freeze)
55
- rescue
56
- raise
57
- end
58
- end
59
-
60
- @task.add_observer(Caddy::TaskObserver.new)
61
- @task.execute
62
-
63
- @_started_pid = $$
64
-
65
- @task.running?
31
+ @caches.values.each(&:start).all?
66
32
  end
67
33
 
68
34
  def self.stop
69
- @task.shutdown if @task && @task.running?
35
+ @caches.values.each(&:stop).all?
70
36
  end
71
37
 
72
38
  def self.restart
73
- stop || start
39
+ stop
40
+ start
74
41
  end
75
42
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ module Caddy
3
+ class Cache
4
+ DEFAULT_REFRESH_INTERVAL = 60
5
+ REFRESH_INTERVAL_JITTER_PCT = 0.15
6
+
7
+ attr_accessor :refresher, :refresh_interval, :error_handler
8
+
9
+ def initialize(key)
10
+ @task = nil
11
+ @refresh_interval = DEFAULT_REFRESH_INTERVAL
12
+ @cache = nil
13
+ @key = key
14
+ end
15
+
16
+ def [](k)
17
+ cache[k]
18
+ end
19
+
20
+ def cache
21
+ raise "Please run `Caddy.start` before attempting to access the cache" unless @task && @task.running?
22
+
23
+ @cache
24
+ end
25
+
26
+ def start
27
+ unless refresher && refresher.respond_to?(:call)
28
+ raise "Please set your cache refresher via `Caddy[:#{@key}].refresher = -> { <code that returns a value> }`"
29
+ end
30
+
31
+ raise "`Caddy[:#{@key}].refresh_interval` must be > 0" unless refresh_interval > 0
32
+
33
+ jitter_amount = [0.1, refresh_interval * REFRESH_INTERVAL_JITTER_PCT].max
34
+ interval = refresh_interval + rand(-jitter_amount...jitter_amount)
35
+ timeout_interval = [interval - 1, 0.1].max
36
+
37
+ stop # stop any existing task from running
38
+
39
+ @task = Concurrent::TimerTask.new(
40
+ run_now: true,
41
+ execution_interval: interval,
42
+ timeout_interval: timeout_interval
43
+ ) do
44
+ begin
45
+ @cache = refresher.call.freeze
46
+ rescue
47
+ raise
48
+ end
49
+ end
50
+
51
+ @task.add_observer(Caddy::TaskObserver.new(error_handler, @key))
52
+ @task.execute
53
+
54
+ @task.running?
55
+ end
56
+
57
+ def stop
58
+ @task.shutdown if @task && @task.running?
59
+ end
60
+ end
61
+ end
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module Caddy
3
3
  class TaskObserver
4
+ def initialize(error_handler, cache_name)
5
+ @error_handler = error_handler || Caddy.error_handler
6
+ @cache_name = cache_name
7
+ end
8
+
4
9
  def update(_, _, boom)
5
10
  return unless boom
6
11
 
7
- if Caddy.error_handler
8
- if Caddy.error_handler.respond_to?(:call)
12
+ if @error_handler
13
+ if @error_handler.respond_to?(:call)
9
14
  begin
10
- Caddy.error_handler.call(boom)
15
+ @error_handler.call(boom)
11
16
  rescue => incepted_boom
12
- puts_exception("Caddy error handler itself errored", incepted_boom)
17
+ puts_exception("Caddy error handler itself errored handling refresh for :#{@cache_name}", incepted_boom)
13
18
  end
14
19
  else
15
20
  # rubocop:disable Style/StringLiterals
@@ -17,12 +22,12 @@ module Caddy
17
22
  ' `Caddy.error_handler = -> (e) { puts "#{e}" }`'
18
23
  # rubocop:enable Style/StringLiterals
19
24
 
20
- puts_exception("Caddy refresher failed with error", boom)
25
+ puts_exception("Caddy refresher for :#{@cache_name} failed with error", boom)
21
26
  end
22
27
  elsif boom.is_a?(Concurrent::TimeoutError)
23
- STDERR.puts "Caddy refresher timed out"
28
+ STDERR.puts "Caddy refresher for :#{@cache_name} timed out"
24
29
  else
25
- puts_exception("Caddy refresher failed with error", boom)
30
+ puts_exception("Caddy refresher for :#{@cache_name} failed with error", boom)
26
31
  end
27
32
  end
28
33
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Caddy
3
- VERSION = "1.0.1".freeze
3
+ VERSION = "1.5.0".freeze
4
4
  end
@@ -3,61 +3,104 @@ require "test_helper"
3
3
  class CaddyTest < Minitest::Test
4
4
  def setup
5
5
  Caddy.stop
6
- Caddy.refresher = -> {}
7
- Caddy.refresh_interval = 30
8
6
  Caddy.error_handler = nil
7
+
8
+ [:test, :test_two].each do |k|
9
+ Caddy[k].refresher = -> {}
10
+ Caddy[k].refresh_interval = 30
11
+ Caddy[k].error_handler = nil
12
+ end
13
+
9
14
  sleep(0.05)
10
15
  end
11
16
 
12
17
  def test_basic_lookup
13
- Caddy.refresher = -> { {foo: "bar"} }
18
+ Caddy[:test].refresher = -> { {foo: "bar"} }
14
19
  Caddy.start
15
20
  sleep(0.1)
16
21
 
17
- assert_equal "bar", Caddy[:foo]
22
+ assert_equal "bar", Caddy[:test][:foo]
18
23
  end
19
24
 
20
25
  def test_basic_interval_updating
21
26
  x = 0
22
- Caddy.refresher = lambda do
27
+ Caddy[:test].refresher = lambda do
23
28
  x += 1
24
29
  {baz: x}
25
30
  end
26
- Caddy.refresh_interval = 2
31
+ Caddy[:test].refresh_interval = 2
27
32
  Caddy.start
28
33
  sleep(3)
29
34
 
30
- assert_operator Caddy[:baz], :>=, 2
35
+ assert_operator Caddy[:test][:baz], :>=, 2
36
+ end
37
+
38
+ def test_multiple_interval_updating
39
+ x = 0
40
+ y = 0
41
+ Caddy[:test].refresher = lambda do
42
+ x += 1
43
+ {baz: x}
44
+ end
45
+ Caddy[:test].refresh_interval = 2
46
+ Caddy[:test_two].refresher = lambda do
47
+ y += 1
48
+ {biz: y}
49
+ end
50
+ Caddy[:test_two].refresh_interval = 1
51
+ Caddy.start
52
+ sleep(4)
53
+
54
+ assert_operator Caddy[:test][:baz], :>=, 2
55
+ assert_operator Caddy[:test_two][:biz], :>=, 4
31
56
  end
32
57
 
33
58
  def test_stale_value
34
59
  ran_once = false
35
- Caddy.refresher = lambda do
60
+ Caddy[:test].refresher = lambda do
36
61
  raise "boom" if ran_once
37
62
  ran_once = true
38
63
  {baz: "bizz"}
39
64
  end
40
- Caddy.refresh_interval = 2
65
+ Caddy[:test].refresh_interval = 2
41
66
  Caddy.start
42
67
  sleep(3)
43
68
 
44
- assert_equal "bizz", Caddy[:baz]
69
+ assert_equal "bizz", Caddy[:test][:baz]
70
+ end
71
+
72
+ def test_many_readers
73
+ x = 0
74
+ Caddy[:test].refresher = -> { {x: x += 1} }
75
+ Caddy[:test].refresh_interval = 0.1
76
+ Caddy.start
77
+ sleep(0.1)
78
+
79
+ Array.new(50) do
80
+ Thread.new do
81
+ 200.times do
82
+ x = Caddy[:test][:x]
83
+ sleep(0.01)
84
+ x = Caddy[:test][:x]
85
+ end
86
+ end
87
+ end.each(&:join)
45
88
  end
46
89
 
47
90
  def test_restart
48
- Caddy.refresher = -> { {foo: "baz"} }
91
+ Caddy[:test].refresher = -> { {foo: "baz"} }
49
92
  Caddy.start
50
93
  sleep(0.1)
51
94
  Caddy.stop
52
95
  Caddy.restart
53
96
  sleep(0.1)
54
97
 
55
- assert_equal "baz", Caddy[:foo]
98
+ assert_equal "baz", Caddy[:test][:foo]
56
99
  end
57
100
 
58
- def test_error_handling
101
+ def test_global_error_handling
59
102
  reported = nil
60
- Caddy.refresher = -> { raise "boom" }
103
+ Caddy[:test].refresher = -> { raise "boom" }
61
104
  Caddy.error_handler = -> (ex) { reported = ex }
62
105
  Caddy.start
63
106
  sleep(0.1)
@@ -65,15 +108,25 @@ class CaddyTest < Minitest::Test
65
108
  assert_equal "boom", reported.message
66
109
  end
67
110
 
111
+ def test_specific_error_handling
112
+ reported = nil
113
+ Caddy[:test].refresher = -> { raise "boom" }
114
+ Caddy[:test].error_handler = -> (ex) { reported = ex }
115
+ Caddy.start
116
+ sleep(0.1)
117
+
118
+ assert_equal "boom", reported.message
119
+ end
120
+
68
121
  def test_incepted_error_handling
69
- Caddy.refresher = -> { raise "boom" }
122
+ Caddy[:test].refresher = -> { raise "boom" }
70
123
  Caddy.error_handler = -> (_) { raise "boomboom" }
71
124
  Caddy.start
72
125
  sleep(0.1)
73
126
  end
74
127
 
75
128
  def test_bad_error_handler
76
- Caddy.refresher = -> { raise "boom" }
129
+ Caddy[:test].refresher = -> { raise "boom" }
77
130
  Caddy.error_handler = "no"
78
131
  Caddy.start
79
132
  sleep(0.1)
@@ -81,9 +134,9 @@ class CaddyTest < Minitest::Test
81
134
 
82
135
  def test_timeout
83
136
  timed_out = nil
84
- Caddy.refresher = -> { sleep 1 }
137
+ Caddy[:test].refresher = -> { sleep 1 }
85
138
  Caddy.error_handler = -> (ex) { timed_out = ex }
86
- Caddy.refresh_interval = 0.5
139
+ Caddy[:test].refresh_interval = 0.5
87
140
  Caddy.start
88
141
  sleep(2)
89
142
 
@@ -93,8 +146,8 @@ class CaddyTest < Minitest::Test
93
146
  end
94
147
 
95
148
  def test_no_handler_timeout
96
- Caddy.refresher = -> { sleep 1 }
97
- Caddy.refresh_interval = 0.5
149
+ Caddy[:test].refresher = -> { sleep 1 }
150
+ Caddy[:test].refresh_interval = 0.5
98
151
  Caddy.start
99
152
  sleep(2)
100
153
  Caddy.stop
@@ -102,19 +155,19 @@ class CaddyTest < Minitest::Test
102
155
  end
103
156
 
104
157
  def test_no_handler
105
- Caddy.refresher = -> { raise "boom" }
158
+ Caddy[:test].refresher = -> { raise "boom" }
106
159
  Caddy.start
107
160
  sleep(0.1)
108
161
  end
109
162
 
110
163
  def test_requires_refesher
111
- Caddy.refresher = nil
164
+ Caddy[:test].refresher = nil
112
165
 
113
166
  assert_raises { Caddy.start }
114
167
  end
115
168
 
116
169
  def test_requires_positive_interval
117
- Caddy.refresh_interval = -2
170
+ Caddy[:test].refresh_interval = -2
118
171
 
119
172
  assert_raises { Caddy.start }
120
173
  end
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.0.1
4
+ version: 1.5.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-08 00:00:00.000000000 Z
11
+ date: 2016-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -85,6 +85,7 @@ files:
85
85
  - docs/architecture.dot
86
86
  - docs/architecture.svg
87
87
  - lib/caddy.rb
88
+ - lib/caddy/cache.rb
88
89
  - lib/caddy/task_observer.rb
89
90
  - lib/caddy/version.rb
90
91
  - test/caddy_test.rb