caddy 1.0.1 → 1.5.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: 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