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 +4 -4
- data/README.md +21 -8
- data/lib/caddy.rb +16 -49
- data/lib/caddy/cache.rb +61 -0
- data/lib/caddy/task_observer.rb +12 -7
- data/lib/caddy/version.rb +1 -1
- data/test/caddy_test.rb +76 -23
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d0bb8c605dbc32dbcb05731e669f1ccf00f58c7
|
4
|
+
data.tar.gz: 664c4aca36f43b4b6a441f54eaf9ca7a25c0d9b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
19
|
-
|
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
|
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:
|
data/lib/caddy.rb
CHANGED
@@ -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 "
|
6
|
+
require "caddy/cache"
|
5
7
|
|
6
8
|
module Caddy
|
7
9
|
class << self
|
8
|
-
attr_accessor :
|
10
|
+
attr_accessor :error_handler
|
9
11
|
end
|
10
12
|
|
11
|
-
|
12
|
-
|
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
|
-
|
17
|
+
@caches[k]
|
21
18
|
end
|
22
19
|
|
23
|
-
def self.
|
24
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
-
@
|
35
|
+
@caches.values.each(&:stop).all?
|
70
36
|
end
|
71
37
|
|
72
38
|
def self.restart
|
73
|
-
stop
|
39
|
+
stop
|
40
|
+
start
|
74
41
|
end
|
75
42
|
end
|
data/lib/caddy/cache.rb
ADDED
@@ -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
|
data/lib/caddy/task_observer.rb
CHANGED
@@ -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
|
8
|
-
if
|
12
|
+
if @error_handler
|
13
|
+
if @error_handler.respond_to?(:call)
|
9
14
|
begin
|
10
|
-
|
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
|
|
data/lib/caddy/version.rb
CHANGED
data/test/caddy_test.rb
CHANGED
@@ -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
|
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
|
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-
|
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
|