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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37c9cbaa3ab3273b12d27046c3723417437e1e6d
4
- data.tar.gz: 4111cd80d00e965f691e0eb312d397c7c80bb28a
3
+ metadata.gz: 58eb0f80fe77ef64649d618a10effebcaf5faadb
4
+ data.tar.gz: 100273def6d0617b57a299124d7ae2306a4ef19e
5
5
  SHA512:
6
- metadata.gz: bbbcaed1cee1a6d39f4eec77632679360386de76933e3dabf4160e04948891cc2ef71afa13d8f0afda68509d038034df8d3537b75453c3b66c6f5d057f3c19f7
7
- data.tar.gz: 8227f5b289ccb13b667ca407a05ad71aa30d36a195677de1063dc0f3bad7b7144dc95795fcfc35d057ead60b08906580b0a3bc0b2d6a76fb8fcb09c58ebbdeea
6
+ metadata.gz: 1ea464f973e823fca35892ada3d959c3354a1518b5bac24dca2d0a47cbd8162601a931b03bb774a20f2ffa429cce46f860ab3e45828e8325d473597d96d181c6
7
+ data.tar.gz: d978a63436d6c3b8f7f79ff49909f5282edb33a5786c7b25ae1ce834e7595c9276031e17fe13ba19a5102db5693d9f439721e8cb0e384d5054b86e1953a361e0
data/.codeclimate.yml ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ config:
6
+ languages:
7
+ - ruby
8
+ fixme:
9
+ enabled: true
10
+ rubocop:
11
+ enabled: true
12
+ ratings:
13
+ paths:
14
+ - "**.rb"
15
+ exclude_paths:
16
+ - test/
data/README.md CHANGED
@@ -1,35 +1,70 @@
1
- # Caddy (WORK IN PROGRESS)
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
- Holds your stuff, and keeps it up to date.
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 = -> { Hash[SomeKeyValueModel.all.map { |skvm| [skvm.key.to_sym, skvm.value } }
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
- # ... in your unicorn.rb/puma.rb after fork/start
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[:use_the_fast_index]
18
- Rails.cache.fetch("#{Caddy[:global_cache_version}/#{Caddy[:foo_index_cache_version}/foo/bar") do
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 Spring (in development)
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
- For testing Caddy in development with Spring, you need to have Caddy start after fork:
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{Caddy gives you a magical auto-updating global cache.}
12
- s.summary = %q{Caddy gives you a magical auto-updating global cache.}
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Caddy
3
- VERSION = "0.0.2".freeze
3
+ VERSION = "1.0.0".freeze
4
4
  end
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, :refresher_error_handler
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
- raise "Please run `Caddy.start` before attempting to access values" unless @task
16
- raise "Caddy variable access before initial load; allow some more time for your app to start up" unless @task.value
17
- @task.value[k]
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
- jitter_amount = [1, refresh_interval * REFRESH_INTERVAL_JITTER_PCT].max
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 = Concurrent::TimerTask.new(
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: interval - 1) { refresher.call }
49
+ timeout_interval: timeout_interval
50
+ ) { refresher.call }
32
51
 
33
- task.add_observer(InternalCaddyTaskObserver.new)
34
- task.execute
52
+ @task.add_observer(Caddy::TaskObserver.new)
53
+ @task.execute
35
54
 
36
- _stop_internal # stop any existing task from running
55
+ @_started_pid = $$
37
56
 
38
- @task = task # and transfer over the new 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
- class InternalCaddyTaskObserver
56
- def update(time, _, boom)
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 "minitest/autorun"
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
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ if ENV["CODECLIMATE_REPO_TOKEN"]
4
+ require "codeclimate-test-reporter"
5
+ CodeClimate::TestReporter.start
6
+ end
7
+
8
+ require "minitest/autorun"
9
+ require "caddy"
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.2
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-02 00:00:00.000000000 Z
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: