cachext 0.3.2 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dcf7bc76ec3652f1cd422829ce48eab33dcca9fa
4
- data.tar.gz: c0b1da10550b40c1b8a3b177ad3af8db1e6e1a6d
3
+ metadata.gz: efbd13ef3c2d5d01d835978b0344adfb772a01f1
4
+ data.tar.gz: a134b132ea3aa34818e3063374ae6f3c2f608db5
5
5
  SHA512:
6
- metadata.gz: baedf859f3f071c3f8d55d3dce7f23017bc220eb66a9c4c1d38f0eb4b07d1cd463aec1d158b83f0d277be26ccfdcaf167630122ab002b32ea0348080758d3103
7
- data.tar.gz: 70b82bbbec5dcca57cf5a5b2d894b13c17f359fb5a195f3918836a0998a8b1f812c83650c3c428d0821adbf80329ebe101901f8749e8d9dc2ba5b811d49d28a2
6
+ metadata.gz: 9879eebaa0620bc3719e97f96ec2eb2cd4b5ee56c7673f56b906e8c5d4657e3dd15e0f54051621e831e1d57706d36b3ec24ec7c786cc5718b1553d8421ce6c23
7
+ data.tar.gz: 27a8916181d7f7e10e2fae4eda9e3372be97d9fa854cda833cee8d50a9d14aee7fbcc259e1e9f75ad634f46e8e2eb3f489c4cfa04401cdab881fd853bdf59d11
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "activesupport"
22
+ spec.add_dependency "activesupport", "~> 4.2"
23
23
  spec.add_dependency "redis"
24
24
  spec.add_dependency "redis-namespace"
25
25
  spec.add_dependency "redlock"
@@ -31,4 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rspec"
32
32
  spec.add_development_dependency "pry"
33
33
  spec.add_development_dependency "thread"
34
+ spec.add_development_dependency "timecop"
34
35
  end
@@ -2,6 +2,7 @@ require "cachext/version"
2
2
  require "faraday/error"
3
3
 
4
4
  module Cachext
5
+ autoload :Breaker, "cachext/breaker"
5
6
  autoload :Client, "cachext/client"
6
7
  autoload :Configuration, "cachext/configuration"
7
8
  autoload :Features, "cachext/features"
@@ -40,7 +41,8 @@ module Cachext
40
41
 
41
42
  def self.flush
42
43
  config.cache.clear
43
- config.redis.del "cachext:*"
44
+ keys = config.redis.keys("cachext:*")
45
+ config.redis.del(*keys) if keys.length > 0
44
46
  end
45
47
 
46
48
  def self.multi klass, ids, options = {}, &block
@@ -49,5 +51,6 @@ module Cachext
49
51
 
50
52
  def self.configure &block
51
53
  @config = Configuration.setup(&block)
54
+ @client = Client.new @config
52
55
  end
53
56
  end
@@ -0,0 +1,90 @@
1
+ require "thread"
2
+
3
+ module Cachext
4
+ class Breaker
5
+ attr_reader :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def for(key)
12
+ WithKey.new(config, key)
13
+ end
14
+
15
+ class WithKey
16
+ attr_reader :config, :key
17
+
18
+ def initialize(config, key)
19
+ @config = config
20
+ @key = key
21
+ end
22
+
23
+ def increment_failure
24
+ redis.pipelined do
25
+ redis.set key_str(:last_failure), Time.now.to_f
26
+ redis.incr key_str(:monitor)
27
+ end
28
+ end
29
+
30
+ def check_health
31
+ if half_open?
32
+ if health_check >= config.failure_threshold
33
+ reset!
34
+ else
35
+ increment_health_check
36
+ end
37
+ end
38
+ end
39
+
40
+ def reset!
41
+ redis.del key_str(:monitor),
42
+ key_str(:health_check),
43
+ key_str(:last_failure)
44
+ end
45
+
46
+ def open?
47
+ state == :open
48
+ end
49
+
50
+ def half_open?
51
+ state == :half_open
52
+ end
53
+
54
+ def state
55
+ if (lf = last_failure) && (lf + config.breaker_timeout < Time.now.to_f)
56
+ :half_open
57
+ elsif monitor >= config.failure_threshold
58
+ :open
59
+ else
60
+ :close
61
+ end
62
+ end
63
+
64
+ def monitor
65
+ redis.get(key_str(:monitor)).to_i
66
+ end
67
+
68
+ def last_failure
69
+ lf = redis.get key_str(:last_failure)
70
+ lf.nil? ? nil : lf.to_f
71
+ end
72
+
73
+ def health_check
74
+ redis.get(key_str(:health_check)).to_i
75
+ end
76
+
77
+ def increment_health_check
78
+ redis.incr key_str(:health_check)
79
+ end
80
+
81
+ def key_str(name)
82
+ "#{name}:#{key.raw.map(&:to_s).join(":")}"
83
+ end
84
+
85
+ def redis
86
+ config.lock_redis
87
+ end
88
+ end
89
+ end
90
+ end
@@ -5,6 +5,7 @@ module Cachext
5
5
  prepend Features::DebugLogging
6
6
  prepend Features::Lock
7
7
  prepend Features::Backup
8
+ prepend Features::CircuitBreaker
8
9
  prepend Features::Default
9
10
 
10
11
  def initialize config
@@ -45,7 +46,7 @@ module Cachext
45
46
  end
46
47
 
47
48
  def read key, options
48
- key.read
49
+ key.read if options.cache?
49
50
  end
50
51
 
51
52
  def write key, fresh, options
@@ -13,7 +13,9 @@ module Cachext
13
13
  :not_found_errors, # array of errors where we delete the backup and reraise
14
14
  :max_lock_wait, # time in seconds to wait for a lock
15
15
  :debug, # output debug messages to STDERR
16
- :heartbeat_expires # time in seconds for process heardbeat to expire
16
+ :heartbeat_expires, # time in seconds for process heardbeat to expire
17
+ :failure_threshold, # Number of tries before tripping circuit breaker
18
+ :breaker_timeout # time in seconds to wait before switching breaker to half-open
17
19
 
18
20
  MissingConfiguration = Class.new(StandardError)
19
21
 
@@ -47,6 +49,7 @@ module Cachext
47
49
  self.max_lock_wait = 5
48
50
  self.debug = ENV['CACHEXT_DEBUG'] == "true"
49
51
  self.heartbeat_expires = 2
52
+ self.failure_threshold = 3
50
53
  @debug_mutex = Mutex.new
51
54
  end
52
55
 
@@ -1,6 +1,7 @@
1
1
  module Cachext
2
2
  module Features
3
3
  autoload :Backup, "cachext/features/backup"
4
+ autoload :CircuitBreaker, "cachext/features/circuit_breaker"
4
5
  autoload :DebugLogging, "cachext/features/debug_logging"
5
6
  autoload :Default, "cachext/features/default"
6
7
  autoload :Lock, "cachext/features/lock"
@@ -0,0 +1,27 @@
1
+ module Cachext
2
+ module Features
3
+ module CircuitBreaker
4
+ attr_reader :breaker
5
+
6
+ def initialize config
7
+ super
8
+ @breaker = Breaker.new(config)
9
+ end
10
+
11
+ def read key, options
12
+ circuit = breaker.for(key)
13
+ if circuit.open?
14
+ key.read_backup
15
+ else
16
+ circuit.check_health
17
+ super
18
+ end
19
+ end
20
+
21
+ def handle_error key, options, error
22
+ breaker.for(key).increment_failure
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
@@ -39,7 +39,7 @@ module Cachext
39
39
 
40
40
  block.call
41
41
  ensure
42
- @config.lock_manager.unlock @lock_info
42
+ @config.lock_manager.unlock @lock_info if @lock_info
43
43
  done = true
44
44
  end
45
45
 
@@ -54,7 +54,7 @@ module Cachext
54
54
  end
55
55
 
56
56
  def wait_for_lock key, start_time
57
- sleep rand
57
+ sleep rand(0..(@config.max_lock_wait / 2))
58
58
  if Time.now - start_time > @config.max_lock_wait
59
59
  raise TimeoutWaitingForLock
60
60
  end
@@ -14,7 +14,8 @@ module Cachext
14
14
  errors: config.default_errors,
15
15
  reraise_errors: true,
16
16
  not_found_error: config.not_found_errors,
17
- heartbeat_expires: config.heartbeat_expires
17
+ heartbeat_expires: config.heartbeat_expires,
18
+ cache: true
18
19
 
19
20
  @expires_in = expires_in
20
21
  @default = default
@@ -22,6 +23,11 @@ module Cachext
22
23
  @reraise_errors = reraise_errors
23
24
  @not_found_error = not_found_error
24
25
  @heartbeat_expires = heartbeat_expires
26
+ @cache = cache
27
+ end
28
+
29
+ def cache?
30
+ @cache
25
31
  end
26
32
  end
27
33
  end
@@ -1,3 +1,3 @@
1
1
  module Cachext
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cachext
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Donald Plummer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-11 00:00:00.000000000 Z
11
+ date: 2016-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '4.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '4.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: redis
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: timecop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  description: Don't calculate the cached value twice at the same time. Use a backup
168
182
  of the data if the service is down.
169
183
  email:
@@ -183,10 +197,12 @@ files:
183
197
  - bin/setup
184
198
  - cachext.gemspec
185
199
  - lib/cachext.rb
200
+ - lib/cachext/breaker.rb
186
201
  - lib/cachext/client.rb
187
202
  - lib/cachext/configuration.rb
188
203
  - lib/cachext/features.rb
189
204
  - lib/cachext/features/backup.rb
205
+ - lib/cachext/features/circuit_breaker.rb
190
206
  - lib/cachext/features/debug_logging.rb
191
207
  - lib/cachext/features/default.rb
192
208
  - lib/cachext/features/lock.rb