cachext 0.3.2 → 0.4.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: 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