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