caddy 0.0.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +16 -0
- data/README.md +44 -9
- data/caddy.gemspec +6 -3
- data/lib/caddy/task_observer.rb +36 -0
- data/lib/caddy/version.rb +1 -1
- data/lib/caddy.rb +34 -48
- data/test/caddy_test.rb +83 -2
- data/test/test_helper.rb +9 -0
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58eb0f80fe77ef64649d618a10effebcaf5faadb
|
4
|
+
data.tar.gz: 100273def6d0617b57a299124d7ae2306a4ef19e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ea464f973e823fca35892ada3d959c3354a1518b5bac24dca2d0a47cbd8162601a931b03bb774a20f2ffa429cce46f860ab3e45828e8325d473597d96d181c6
|
7
|
+
data.tar.gz: d978a63436d6c3b8f7f79ff49909f5282edb33a5786c7b25ae1ce834e7595c9276031e17fe13ba19a5102db5693d9f439721e8cb0e384d5054b86e1953a361e0
|
data/.codeclimate.yml
ADDED
data/README.md
CHANGED
@@ -1,35 +1,70 @@
|
|
1
|
-
# Caddy (
|
1
|
+
# Caddy [![Build Status](https://travis-ci.org/nickelser/caddy.svg?branch=master)](https://travis-ci.org/nickelser/caddy) [![Code Climate](https://codeclimate.com/github/nickelser/caddy/badges/gpa.svg)](https://codeclimate.com/github/nickelser/caddy) [![Test Coverage](https://codeclimate.com/github/nickelser/caddy/badges/coverage.svg)](https://codeclimate.com/github/nickelser/caddy) [![Gem Version](https://badge.fury.io/rb/caddy.svg)](http://badge.fury.io/rb/caddy)
|
2
2
|
|
3
|
-
|
3
|
+
Caddy is an asynchronously updated store that is updated on an interval to store objects that you can access quickly during requests. The cache refresher function can be as slow as you would like and it will not affect your request-time performance.
|
4
|
+
|
5
|
+
It's powered by [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby), a battle-tested and comprehensive thread-based (& thread-safe) concurrency library.
|
4
6
|
|
5
7
|
```ruby
|
6
8
|
# in your initializers (caddy.rb would be a wonderful name)
|
7
|
-
Caddy.refresher =
|
9
|
+
Caddy.refresher = lambda do
|
10
|
+
{
|
11
|
+
flags: SomeFlagService.fetch_flags, # this can take a few seconds; it won't block requests when you use it later
|
12
|
+
cache_keys: SomeCacheKeyService.cache_keys
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
8
16
|
Caddy.refresh_interval = 5.minutes # default is 60 seconds
|
9
17
|
|
10
|
-
# ...
|
18
|
+
# ... after your application forks
|
11
19
|
Caddy.start
|
12
20
|
|
13
21
|
# ... in a controller
|
14
22
|
def index
|
15
23
|
# the Caddy requests are instant, and are up-to-date (as of 5 minutes ago, as specified above)
|
16
24
|
# you could use this for high-level feature flags, cache dumping
|
17
|
-
if Caddy[:
|
18
|
-
|
25
|
+
if Caddy[:flags][:fuzz_bizz] # Caddy provides a convenience method to access the cache by key; you can also access
|
26
|
+
# it directly with Caddy.cache[:flags][...]
|
27
|
+
Rails.cache.fetch("#{Caddy[:cache_keys][:global_key]}/#{Caddy[:cache_keys][:index_key]}/foo/bar") do
|
19
28
|
# wonderful things happen here
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
23
32
|
```
|
24
33
|
|
25
|
-
## Using Caddy with
|
34
|
+
## Using Caddy with Unicorn
|
35
|
+
|
36
|
+
You need to start Caddy after fork:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# in your unicorn.rb initializer
|
40
|
+
after_fork do |server, worker|
|
41
|
+
Caddy.start
|
42
|
+
|
43
|
+
# ... your magic here
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
## Using Caddy with Puma
|
48
|
+
|
49
|
+
Similarly, after work boot:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# in your puma.rb initializer
|
53
|
+
on_worker_boot do |server, worker|
|
54
|
+
Caddy.start
|
55
|
+
|
56
|
+
# ... your magic here
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
## Using Caddy with Spring
|
26
61
|
|
27
|
-
|
62
|
+
In the same vein, with developing with Spring, you need to have Caddy start after fork:
|
28
63
|
|
29
64
|
```ruby
|
30
65
|
# in your caddy.rb initializer, perhaps
|
31
66
|
|
32
|
-
if Rails.env.development?
|
67
|
+
if Rails.env.development? && defined?(Spring)
|
33
68
|
Spring.after_fork do
|
34
69
|
Caddy.start
|
35
70
|
end
|
data/caddy.gemspec
CHANGED
@@ -8,18 +8,21 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.version = Caddy::VERSION
|
9
9
|
s.authors = ["Nick Elser"]
|
10
10
|
s.email = ["nick.elser@gmail.com"]
|
11
|
-
s.description = %q
|
12
|
-
s.summary = %q
|
11
|
+
s.description = %q(Caddy gives you a magical auto-updating global cache.)
|
12
|
+
s.summary = %q(Caddy gives you a magical auto-updating global cache.)
|
13
13
|
s.homepage = "http://github.com/nickelser/caddy"
|
14
14
|
s.licenses = ["MIT"]
|
15
15
|
|
16
16
|
s.files = `git ls-files`.split($/)
|
17
|
-
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
18
18
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
+
s.required_ruby_version = "~> 2.0"
|
22
|
+
|
21
23
|
s.add_dependency "concurrent-ruby"
|
22
24
|
|
23
25
|
s.add_development_dependency "rake", "~> 10.5"
|
24
26
|
s.add_development_dependency "minitest", "~> 5.0"
|
27
|
+
s.add_development_dependency "codeclimate-test-reporter", "~> 0.4.7"
|
25
28
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Caddy
|
3
|
+
class TaskObserver
|
4
|
+
def update(_, _, boom)
|
5
|
+
return unless boom
|
6
|
+
|
7
|
+
if Caddy.error_handler
|
8
|
+
if Caddy.error_handler.respond_to?(:call)
|
9
|
+
begin
|
10
|
+
Caddy.error_handler.call(boom)
|
11
|
+
rescue => incepted_boom
|
12
|
+
puts_exception("Caddy error handler itself errored", incepted_boom)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
# rubocop:disable Style/StringLiterals
|
16
|
+
STDERR.puts 'Caddy error handler not callable. Please set the error handler like:'\
|
17
|
+
' `Caddy.error_handler = -> (e) { puts "#{e}" }`'
|
18
|
+
# rubocop:enable Style/StringLiterals
|
19
|
+
|
20
|
+
puts_exception("Caddy refresher failed with error", boom)
|
21
|
+
end
|
22
|
+
elsif boom.is_a?(Concurrent::TimeoutError)
|
23
|
+
STDERR.puts "Caddy refresher timed out"
|
24
|
+
else
|
25
|
+
puts_exception("Caddy refresher failed with error", boom)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def puts_exception(msg, boom)
|
32
|
+
STDERR.puts "\n#{msg}: #{boom}"
|
33
|
+
STDERR.puts "\t#{boom.backtrace.join("\n\t")}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/caddy/version.rb
CHANGED
data/lib/caddy.rb
CHANGED
@@ -1,81 +1,67 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "caddy/version"
|
3
|
+
require "caddy/task_observer"
|
3
4
|
require "concurrent/timer_task"
|
4
5
|
|
5
6
|
module Caddy
|
6
7
|
class << self
|
7
|
-
attr_accessor :refresher, :
|
8
|
-
attr_writer :refresh_interval
|
8
|
+
attr_accessor :refresher, :error_handler, :refresh_interval
|
9
9
|
end
|
10
10
|
|
11
11
|
DEFAULT_REFRESH_INTERVAL = 60
|
12
12
|
REFRESH_INTERVAL_JITTER_PCT = 0.15
|
13
13
|
|
14
|
+
@task = nil
|
15
|
+
@refresh_interval = DEFAULT_REFRESH_INTERVAL
|
16
|
+
@_started_pid = nil
|
17
|
+
|
14
18
|
def self.[](k)
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
cache[k]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.cache
|
23
|
+
raise "Please run `Caddy.start` before attempting to access the cache" unless @task
|
24
|
+
raise "Caddy cache access before initial load; allow some more time for your app to start up" unless @task.value
|
25
|
+
|
26
|
+
@task.value
|
18
27
|
end
|
19
28
|
|
20
29
|
def self.start
|
21
|
-
unless refresher
|
30
|
+
unless refresher && refresher.respond_to?(:call)
|
22
31
|
raise "Please set your cache refresher via `Caddy.refresher = -> { <code that returns a value> }`"
|
23
32
|
end
|
24
33
|
|
25
|
-
|
34
|
+
raise "`Caddy.refresh_interval` must be > 0" unless refresh_interval > 0
|
35
|
+
|
36
|
+
if @_started_pid && $$ != @_started_pid
|
37
|
+
raise "Please run `Caddy.start` *after* forking, as the refresh thread will get killed after fork"
|
38
|
+
end
|
39
|
+
|
40
|
+
jitter_amount = [0.5, refresh_interval * REFRESH_INTERVAL_JITTER_PCT].max
|
26
41
|
interval = refresh_interval + rand(-jitter_amount...jitter_amount)
|
42
|
+
timeout_interval = [interval - 1, 0.1].max
|
27
43
|
|
28
|
-
task
|
44
|
+
stop # stop any existing task from running
|
45
|
+
|
46
|
+
@task = Concurrent::TimerTask.new(
|
29
47
|
run_now: true,
|
30
48
|
execution_interval: interval,
|
31
|
-
timeout_interval:
|
49
|
+
timeout_interval: timeout_interval
|
50
|
+
) { refresher.call }
|
32
51
|
|
33
|
-
task.add_observer(
|
34
|
-
task.execute
|
52
|
+
@task.add_observer(Caddy::TaskObserver.new)
|
53
|
+
@task.execute
|
35
54
|
|
36
|
-
|
55
|
+
@_started_pid = $$
|
37
56
|
|
38
|
-
@task
|
57
|
+
@task.running?
|
39
58
|
end
|
40
59
|
|
41
60
|
def self.stop
|
42
|
-
raise "Please run `Caddy.start` before running `Caddy.stop`" unless @task
|
43
|
-
|
44
|
-
_stop_internal
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.refresh_interval
|
48
|
-
@refresh_interval || DEFAULT_REFRESH_INTERVAL
|
49
|
-
end
|
50
|
-
|
51
|
-
private_class_method def self._stop_internal
|
52
61
|
@task.shutdown if @task && @task.running?
|
53
62
|
end
|
54
63
|
|
55
|
-
|
56
|
-
|
57
|
-
return unless boom
|
58
|
-
|
59
|
-
if Caddy.refresher_error_handler
|
60
|
-
if Caddy.refresher_error_handler.respond_to?(:call)
|
61
|
-
begin
|
62
|
-
Caddy.refresher_error_handler.call(boom)
|
63
|
-
rescue => incepted_boom
|
64
|
-
STDERR.puts "(#{time}) Caddy error handler itself errored: #{incepted_boom}"
|
65
|
-
end
|
66
|
-
else
|
67
|
-
# rubocop:disable Style/StringLiterals
|
68
|
-
STDERR.puts 'Caddy error handler not callable. Please set the error handler like:'\
|
69
|
-
' `Caddy.refresher_error_handler = -> (e) { puts "#{e}" }`'
|
70
|
-
# rubocop:enable Style/StringLiterals
|
71
|
-
|
72
|
-
STDERR.puts "(#{time}) Caddy refresher failed with error #{boom}"
|
73
|
-
end
|
74
|
-
elsif boom.is_a?(Concurrent::TimeoutError)
|
75
|
-
STDERR.puts "(#{time}) Caddy refresher timed out"
|
76
|
-
else
|
77
|
-
STDERR.puts "(#{time}) Caddy refresher failed with error #{boom}"
|
78
|
-
end
|
79
|
-
end
|
64
|
+
def self.restart
|
65
|
+
stop || start
|
80
66
|
end
|
81
67
|
end
|
data/test/caddy_test.rb
CHANGED
@@ -1,5 +1,86 @@
|
|
1
|
-
require "
|
2
|
-
require "caddy"
|
1
|
+
require "test_helper"
|
3
2
|
|
4
3
|
class CaddyTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
Caddy.stop
|
6
|
+
Caddy.refresher = -> {}
|
7
|
+
Caddy.refresh_interval = 30
|
8
|
+
Caddy.error_handler = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_basic_lookup
|
12
|
+
Caddy.refresher = -> { {foo: "bar"} }
|
13
|
+
Caddy.start
|
14
|
+
sleep(0.1)
|
15
|
+
|
16
|
+
assert_equal "bar", Caddy[:foo]
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_basic_interval_updating
|
20
|
+
x = 0
|
21
|
+
Caddy.refresher = lambda do
|
22
|
+
x += 1
|
23
|
+
{baz: x}
|
24
|
+
end
|
25
|
+
Caddy.refresh_interval = 2
|
26
|
+
Caddy.start
|
27
|
+
sleep(3)
|
28
|
+
|
29
|
+
assert_operator Caddy[:baz], :>=, 2
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_restart
|
33
|
+
Caddy.refresher = -> { {foo: "baz"} }
|
34
|
+
Caddy.start
|
35
|
+
sleep(0.1)
|
36
|
+
Caddy.stop
|
37
|
+
Caddy.restart
|
38
|
+
|
39
|
+
assert_equal "baz", Caddy[:foo]
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_error_handling
|
43
|
+
reported = nil
|
44
|
+
Caddy.refresher = -> { raise "boom" }
|
45
|
+
Caddy.error_handler = -> (ex) { reported = ex }
|
46
|
+
Caddy.start
|
47
|
+
sleep(0.1)
|
48
|
+
|
49
|
+
assert_equal "boom", reported.message
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_incepted_error_handling
|
53
|
+
Caddy.refresher = -> { raise "boom" }
|
54
|
+
Caddy.error_handler = -> (_) { raise "boomboom" }
|
55
|
+
Caddy.start
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_bad_error_handler
|
59
|
+
Caddy.refresher = -> { raise "boom" }
|
60
|
+
Caddy.error_handler = "no"
|
61
|
+
Caddy.start
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_timeout
|
65
|
+
Caddy.refresher = -> { sleep 5 }
|
66
|
+
Caddy.refresh_interval = 1
|
67
|
+
Caddy.start
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_no_handler
|
71
|
+
Caddy.refresher = -> { raise "boom" }
|
72
|
+
Caddy.start
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_requires_refesher
|
76
|
+
Caddy.refresher = nil
|
77
|
+
|
78
|
+
assert_raises { Caddy.start }
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_requires_positive_interval
|
82
|
+
Caddy.refresh_interval = -2
|
83
|
+
|
84
|
+
assert_raises { Caddy.start }
|
85
|
+
end
|
5
86
|
end
|
data/test/test_helper.rb
ADDED
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: 0.0
|
4
|
+
version: 1.0.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-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: codeclimate-test-reporter
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.4.7
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.4.7
|
55
69
|
description: Caddy gives you a magical auto-updating global cache.
|
56
70
|
email:
|
57
71
|
- nick.elser@gmail.com
|
@@ -59,6 +73,7 @@ executables: []
|
|
59
73
|
extensions: []
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
76
|
+
- ".codeclimate.yml"
|
62
77
|
- ".gitignore"
|
63
78
|
- ".rubocop.yml"
|
64
79
|
- ".travis.yml"
|
@@ -68,8 +83,10 @@ files:
|
|
68
83
|
- Rakefile
|
69
84
|
- caddy.gemspec
|
70
85
|
- lib/caddy.rb
|
86
|
+
- lib/caddy/task_observer.rb
|
71
87
|
- lib/caddy/version.rb
|
72
88
|
- test/caddy_test.rb
|
89
|
+
- test/test_helper.rb
|
73
90
|
homepage: http://github.com/nickelser/caddy
|
74
91
|
licenses:
|
75
92
|
- MIT
|
@@ -80,9 +97,9 @@ require_paths:
|
|
80
97
|
- lib
|
81
98
|
required_ruby_version: !ruby/object:Gem::Requirement
|
82
99
|
requirements:
|
83
|
-
- - "
|
100
|
+
- - "~>"
|
84
101
|
- !ruby/object:Gem::Version
|
85
|
-
version: '0'
|
102
|
+
version: '2.0'
|
86
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
104
|
requirements:
|
88
105
|
- - ">="
|
@@ -96,4 +113,5 @@ specification_version: 4
|
|
96
113
|
summary: Caddy gives you a magical auto-updating global cache.
|
97
114
|
test_files:
|
98
115
|
- test/caddy_test.rb
|
116
|
+
- test/test_helper.rb
|
99
117
|
has_rdoc:
|