sinatra-redis-cache 0.1.3 → 0.2.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 +4 -4
- data/README.md +2 -0
- data/Rakefile +6 -1
- data/lib/sinatra/redis-cache.rb +128 -35
- data/lib/sinatra/redis-cache.rb.bak +251 -0
- 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: b87cc8855b185fcd8ed06c830fa184a1d175941e
|
4
|
+
data.tar.gz: 6a335f63d0d2fa50caaafd8d3e3a3422f101d428
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca83aa4e02139b50bb1d284ce0f1ee6a2f61bed119bba06a77eeeda0858ef1c352769d902b7c30671f00c679d2d7533b49453e77751cf4e3576284cb5e4a7924
|
7
|
+
data.tar.gz: a4326ed7771f25d79f5da08779eca7ecc542b1d40eabc07c329ac5c0135465d836a950ea4e243fd6eabdc5f89891d839bf5f5de2e2ca82635c964bc40c1813c3
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -7,7 +7,12 @@ namespace :sinatra_cache do
|
|
7
7
|
|
8
8
|
desc 'Print configured namespace'
|
9
9
|
task :namespace do
|
10
|
+
puts Sinatra::RedisCache::Config.namespace
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Show all cache keys'
|
14
|
+
task :list_keys do
|
10
15
|
include Sinatra::RedisCache
|
11
|
-
|
16
|
+
puts cache_list_keys.map{|k| "#{k} [age: #{cache_key_age(k).to_i}, ttl: #{cache_key_ttl(k)}]"}
|
12
17
|
end
|
13
18
|
end
|
data/lib/sinatra/redis-cache.rb
CHANGED
@@ -28,17 +28,36 @@ module Sinatra
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
class
|
32
|
-
def do(key, expires
|
33
|
-
|
31
|
+
class Cache
|
32
|
+
def do(key, expires=config.default_expires, block)
|
33
|
+
debug_log "do #{key_without_namespace(key)}"
|
34
34
|
if Sinatra::RedisCache::Config.environments.include? Sinatra::Base.settings.environment
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
try = 0
|
36
|
+
begin
|
37
|
+
redis.watch(key = key_with_namespace(key)) do |watched|
|
38
|
+
object = get(key)
|
39
|
+
unless object.empty?
|
40
|
+
if object['locked']
|
41
|
+
raise SinatraRedisCacheKeyLocked
|
42
|
+
end
|
43
|
+
object['object']
|
44
|
+
else
|
45
|
+
lock_key(key, watched, config.lock_timeout)
|
46
|
+
new_object = block.call
|
47
|
+
store(key, new_object, expires)
|
48
|
+
new_object
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue SinatraRedisCacheKeyLocked
|
52
|
+
sleeptime = (((try += 1)*50 + rand(100).to_f)/1000)
|
53
|
+
debug_log "key is locked, waiting #{sleeptime}s for retry ##{try}."
|
54
|
+
sleep sleeptime
|
55
|
+
retry
|
56
|
+
rescue SinatraRedisCacheKeyAlreadyLocked
|
57
|
+
sleeptime = (((try += 1)*50 + rand(100).to_f)/1000)
|
58
|
+
debug_log "failed to obtain lock, waiting #{sleeptime}s for retry ##{try}."
|
59
|
+
sleep sleeptime
|
60
|
+
retry
|
42
61
|
end
|
43
62
|
else
|
44
63
|
# Just run the block without cache if we're not in an allowed environment
|
@@ -46,25 +65,51 @@ module Sinatra
|
|
46
65
|
end
|
47
66
|
end
|
48
67
|
|
49
|
-
def get(key
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
deserialize(string)
|
68
|
+
def get(key)
|
69
|
+
debug_log "get #{key_without_namespace(key)}"
|
70
|
+
unless (hash = redis.hgetall(key_with_namespace(key))).nil?
|
71
|
+
hash.each{|k,v| hash[k]=deserialize(v)}
|
54
72
|
else
|
55
73
|
false
|
56
74
|
end
|
57
75
|
end
|
58
76
|
|
59
|
-
def store(key, object, expires
|
60
|
-
|
61
|
-
|
62
|
-
redis.
|
63
|
-
|
77
|
+
def store(key, object, expires=config.default_expires)
|
78
|
+
debug_log "store #{key_without_namespace(key)}"
|
79
|
+
properties = { created_at: Time.now.utc.to_i }
|
80
|
+
redis.watch(key = key_with_namespace(key)) do |watched|
|
81
|
+
watched.multi do |multi|
|
82
|
+
multi.hset(key, 'object', serialize(object))
|
83
|
+
multi.hset(key, 'properties', serialize(properties))
|
84
|
+
multi.hdel(key, 'locked')
|
85
|
+
multi.expire(key, expires)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def properties(key)
|
91
|
+
unless (string = redis.hget(key_with_namespace(key), 'properties')).nil?
|
92
|
+
deserialize(string)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def ttl(key)
|
97
|
+
redis.ttl(key_with_namespace(key))
|
98
|
+
end
|
99
|
+
|
100
|
+
def all_keys(params={with_namespace: true})
|
101
|
+
redis.keys("#{namespace}:*").map{|k| params[:with_namespace] ? k : key_without_namespace(k) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def del(keys)
|
105
|
+
debug_log "del #{keys.map{|k| key_without_namespace(k)}}"
|
106
|
+
redis.del(keys)
|
64
107
|
end
|
65
108
|
|
66
109
|
def flush
|
67
|
-
|
110
|
+
unless (keys = all_keys).empty?
|
111
|
+
del(keys)
|
112
|
+
end
|
68
113
|
end
|
69
114
|
|
70
115
|
private
|
@@ -73,6 +118,12 @@ module Sinatra
|
|
73
118
|
Sinatra::RedisCache::Config
|
74
119
|
end
|
75
120
|
|
121
|
+
def debug_log(message)
|
122
|
+
if config.logger
|
123
|
+
config.logger.debug("sinatra-redis-cache/#{Process.pid}/#{Thread.current.__id__}") { message }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
76
127
|
def redis
|
77
128
|
config.redis_conn
|
78
129
|
end
|
@@ -97,10 +148,6 @@ module Sinatra
|
|
97
148
|
end
|
98
149
|
end
|
99
150
|
|
100
|
-
def all_keys
|
101
|
-
redis.keys(namespace + '*')
|
102
|
-
end
|
103
|
-
|
104
151
|
def serialize(object)
|
105
152
|
Marshal.dump(object)
|
106
153
|
end
|
@@ -108,25 +155,61 @@ module Sinatra
|
|
108
155
|
def deserialize(string)
|
109
156
|
Marshal.load(string)
|
110
157
|
end
|
158
|
+
|
159
|
+
def lock_key(key, redis, timeout=config.lock_timeout)
|
160
|
+
debug_log "locking #{key_without_namespace(key)} for #{timeout}s"
|
161
|
+
unless redis.multi do |multi|
|
162
|
+
multi.hsetnx(key, 'locked', serialize(true))
|
163
|
+
multi.expire(key, timeout)
|
164
|
+
end.eql? [true,true]
|
165
|
+
raise SinatraRedisCacheKeyAlreadyLocked
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
111
169
|
end
|
112
170
|
|
113
|
-
def cache_do(key, expires=nil,
|
114
|
-
cache =
|
115
|
-
cache.do(key, expires,
|
171
|
+
def cache_do(key, expires=nil, &block)
|
172
|
+
cache = Cache.new
|
173
|
+
cache.do(key, expires, block)
|
116
174
|
end
|
117
175
|
|
118
|
-
def cache_get(key
|
119
|
-
cache =
|
120
|
-
cache.get(key
|
176
|
+
def cache_get(key)
|
177
|
+
cache = Cache.new
|
178
|
+
cache.get(key)
|
121
179
|
end
|
122
180
|
|
123
|
-
def
|
124
|
-
cache =
|
125
|
-
cache.
|
181
|
+
def cache_key_properties(key)
|
182
|
+
cache = Cache.new
|
183
|
+
cache.properties(key)
|
184
|
+
end
|
185
|
+
|
186
|
+
def cache_key_age(key)
|
187
|
+
cache = Cache.new
|
188
|
+
Time.now.utc.to_i - cache.properties(key)[:created_at]
|
189
|
+
end
|
190
|
+
|
191
|
+
def cache_key_ttl(key)
|
192
|
+
cache = Cache.new
|
193
|
+
cache.ttl(key)
|
194
|
+
end
|
195
|
+
|
196
|
+
def cache_store(key, value, expires=nil)
|
197
|
+
cache = Cache.new
|
198
|
+
cache.store(key, value, expires)
|
199
|
+
end
|
200
|
+
|
201
|
+
def cache_list_keys
|
202
|
+
cache = Cache.new
|
203
|
+
cache.all_keys(with_namespace: false)
|
204
|
+
end
|
205
|
+
|
206
|
+
def cache_del(keys)
|
207
|
+
cache = Cache.new
|
208
|
+
cache.del(keys)
|
126
209
|
end
|
127
210
|
|
128
211
|
def cache_flush
|
129
|
-
cache =
|
212
|
+
cache = Cache.new
|
130
213
|
cache.flush
|
131
214
|
end
|
132
215
|
|
@@ -135,12 +218,20 @@ module Sinatra
|
|
135
218
|
helpers RedisCache
|
136
219
|
end
|
137
220
|
|
221
|
+
class SinatraRedisCacheKeyLocked < Exception
|
222
|
+
end
|
223
|
+
|
224
|
+
class SinatraRedisCacheKeyAlreadyLocked < Exception
|
225
|
+
end
|
226
|
+
|
138
227
|
Sinatra::RedisCache::Config.config do
|
139
228
|
# Set up configurable values
|
140
229
|
parameter :redis_conn
|
141
230
|
parameter :namespace
|
142
231
|
parameter :default_expires
|
232
|
+
parameter :lock_timeout
|
143
233
|
parameter :environments
|
234
|
+
parameter :logger
|
144
235
|
end
|
145
236
|
|
146
237
|
Sinatra::RedisCache::Config.config do
|
@@ -148,7 +239,9 @@ Sinatra::RedisCache::Config.config do
|
|
148
239
|
redis_conn Redis.new
|
149
240
|
namespace 'sinatra_cache'
|
150
241
|
default_expires 3600
|
242
|
+
lock_timeout 5
|
151
243
|
environments [:production]
|
244
|
+
logger Logger.new(STDERR)
|
152
245
|
end
|
153
246
|
|
154
247
|
unless defined?(Rake).nil?
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'redis'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
|
7
|
+
module RedisCache
|
8
|
+
|
9
|
+
module Config
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def parameter(*names)
|
13
|
+
names.each do |name|
|
14
|
+
attr_accessor name
|
15
|
+
|
16
|
+
define_method name do |*values|
|
17
|
+
value = values.first
|
18
|
+
if value
|
19
|
+
self.send("#{name}=", value)
|
20
|
+
else
|
21
|
+
instance_variable_get("@#{name}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def config(&block)
|
28
|
+
instance_eval &block
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Cache
|
33
|
+
def do(key, expires, lock, block)
|
34
|
+
log "def do #{key}"
|
35
|
+
if Sinatra::RedisCache::Config.environments.include? Sinatra::Base.settings.environment
|
36
|
+
begin
|
37
|
+
redis.watch(key = key_with_namespace(key)) do
|
38
|
+
log " in watch"
|
39
|
+
object = redis.hgetall(key)
|
40
|
+
unless object.empty?
|
41
|
+
log " object is not empty"
|
42
|
+
if lock && object['locked']
|
43
|
+
log " object is locked"
|
44
|
+
raise SinatraRedisCacheKeyLocked
|
45
|
+
end
|
46
|
+
log " returning object"
|
47
|
+
deserialize(object['object'])
|
48
|
+
else
|
49
|
+
log " apparently object is empty"
|
50
|
+
if lock
|
51
|
+
log " locking"
|
52
|
+
lock_key(key, lock.class == Integer ? lock : 2)
|
53
|
+
end
|
54
|
+
new_object = block.call
|
55
|
+
store(key, new_object, expires)
|
56
|
+
new_object
|
57
|
+
end
|
58
|
+
end
|
59
|
+
rescue SinatraRedisCacheKeyLocked
|
60
|
+
log " raise SinatraRedisCacheKeyLocked"
|
61
|
+
sleep ((50 + rand(100).to_f)/1000)
|
62
|
+
retry
|
63
|
+
rescue SinatraRedisCacheKeyAlreadyLocked
|
64
|
+
log " raise SinatraRedisCacheKeyAlreadyLocked"
|
65
|
+
sleep ((50 + rand(100).to_f)/1000)
|
66
|
+
retry
|
67
|
+
end
|
68
|
+
else
|
69
|
+
# Just run the block without cache if we're not in an allowed environment
|
70
|
+
block.call
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get(key)
|
75
|
+
log "def get #{key}"
|
76
|
+
unless (string = redis.hget(key_with_namespace(key),'object')).nil?
|
77
|
+
deserialize(string)
|
78
|
+
else
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def store(key, object, expires=config.default_expires)
|
84
|
+
log "def store #{key}"
|
85
|
+
redis.watch(key = key_with_namespace(key)) do
|
86
|
+
redis.multi do |multi|
|
87
|
+
multi.hset(key, 'object', serialize(object))
|
88
|
+
multi.expire(key, expires)
|
89
|
+
multi.hdel(key, 'locked')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def properties(key)
|
95
|
+
log "def properties #{key}"
|
96
|
+
unless (string = redis.hget(key_with_namespace(key), 'properties')).nil?
|
97
|
+
deserialize(string)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def ttl(key)
|
102
|
+
redis.ttl(key_with_namespace(key))
|
103
|
+
end
|
104
|
+
|
105
|
+
def all_keys(params={with_namespace: true})
|
106
|
+
redis.keys("#{namespace}:*").map{|k| params[:with_namespace] ? k : key_without_namespace(k) }
|
107
|
+
end
|
108
|
+
|
109
|
+
def del(keys)
|
110
|
+
redis.del(keys)
|
111
|
+
end
|
112
|
+
|
113
|
+
def flush
|
114
|
+
unless (keys = all_keys).empty?
|
115
|
+
del(keys)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def config
|
122
|
+
Sinatra::RedisCache::Config
|
123
|
+
end
|
124
|
+
|
125
|
+
def log(message)
|
126
|
+
if config.logger
|
127
|
+
config.logger.debug('sinatra-redis-cache') { message }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def redis
|
132
|
+
config.redis_conn
|
133
|
+
end
|
134
|
+
|
135
|
+
def namespace
|
136
|
+
config.namespace
|
137
|
+
end
|
138
|
+
|
139
|
+
def key_with_namespace(key)
|
140
|
+
if key.start_with? namespace
|
141
|
+
key
|
142
|
+
else
|
143
|
+
"#{namespace}:#{key}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def key_without_namespace(key)
|
148
|
+
if key.start_with? namespace
|
149
|
+
key.gsub(/^#{namespace}:/,'')
|
150
|
+
else
|
151
|
+
key
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def serialize(object)
|
156
|
+
Marshal.dump(object)
|
157
|
+
end
|
158
|
+
|
159
|
+
def deserialize(string)
|
160
|
+
Marshal.load(string)
|
161
|
+
end
|
162
|
+
|
163
|
+
def lock_key(key, timeout=2)
|
164
|
+
log " def lock"
|
165
|
+
unless redis.multi do |multi|
|
166
|
+
multi.hsetnx(key, 'locked', true)
|
167
|
+
multi.hsetnx(key, 'created_by', "#{Socket.gethostname}/#{Process.pid.to_s}/#{Thread.current.to_s}")
|
168
|
+
multi.expire(key, timeout)
|
169
|
+
end[0]
|
170
|
+
raise SinatraRedisCacheKeyAlreadyLocked
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def cache_do(key, expires=nil, lock=false, &block)
|
176
|
+
cache = Cache.new
|
177
|
+
cache.do(key, expires, lock, block)
|
178
|
+
end
|
179
|
+
|
180
|
+
def cache_get(key)
|
181
|
+
cache = Cache.new
|
182
|
+
cache.get(key)
|
183
|
+
end
|
184
|
+
|
185
|
+
def cache_key_properties(key)
|
186
|
+
cache = Cache.new
|
187
|
+
cache.properties(key)
|
188
|
+
end
|
189
|
+
|
190
|
+
def cache_key_age(key)
|
191
|
+
cache = Cache.new
|
192
|
+
Time.now.utc.to_i - cache.properties(key)[:created_at]
|
193
|
+
end
|
194
|
+
|
195
|
+
def cache_key_ttl(key)
|
196
|
+
cache = Cache.new
|
197
|
+
cache.ttl(key)
|
198
|
+
end
|
199
|
+
|
200
|
+
def cache_store(key, value, expires=nil)
|
201
|
+
cache = Cache.new
|
202
|
+
cache.store(key, value, expires)
|
203
|
+
end
|
204
|
+
|
205
|
+
def cache_list_keys
|
206
|
+
cache = Cache.new
|
207
|
+
cache.all_keys(with_namespace: false)
|
208
|
+
end
|
209
|
+
|
210
|
+
def cache_del(keys)
|
211
|
+
cache = Cache.new
|
212
|
+
cache.del(keys)
|
213
|
+
end
|
214
|
+
|
215
|
+
def cache_flush
|
216
|
+
cache = Cache.new
|
217
|
+
cache.flush
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
helpers RedisCache
|
223
|
+
end
|
224
|
+
|
225
|
+
class SinatraRedisCacheKeyLocked < Exception
|
226
|
+
end
|
227
|
+
|
228
|
+
class SinatraRedisCacheKeyAlreadyLocked < Exception
|
229
|
+
end
|
230
|
+
|
231
|
+
Sinatra::RedisCache::Config.config do
|
232
|
+
# Set up configurable values
|
233
|
+
parameter :redis_conn
|
234
|
+
parameter :namespace
|
235
|
+
parameter :default_expires
|
236
|
+
parameter :environments
|
237
|
+
parameter :logger
|
238
|
+
end
|
239
|
+
|
240
|
+
Sinatra::RedisCache::Config.config do
|
241
|
+
# Default values
|
242
|
+
redis_conn Redis.new
|
243
|
+
namespace 'sinatra_cache'
|
244
|
+
default_expires 3600
|
245
|
+
environments [:production]
|
246
|
+
logger Logger.new(STDOUT)
|
247
|
+
end
|
248
|
+
|
249
|
+
unless defined?(Rake).nil?
|
250
|
+
Rake.load_rakefile("#{File.dirname(__FILE__)}/../../Rakefile")
|
251
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-redis-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Warren Guy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- README.md
|
63
63
|
- Rakefile
|
64
64
|
- lib/sinatra/redis-cache.rb
|
65
|
+
- lib/sinatra/redis-cache.rb.bak
|
65
66
|
homepage: https://github.com/warrenguy/sinatra-redis-cache
|
66
67
|
licenses:
|
67
68
|
- MIT
|