lock_and_cache_msgpack 4.0.7.pre1
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 +7 -0
- data/CHANGELOG +215 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +68 -0
- data/LICENSE.txt +23 -0
- data/README.md +236 -0
- data/Rakefile +9 -0
- data/benchmarks/allowed_in_keys.rb +87 -0
- data/lib/lock_and_cache_msgpack.rb +164 -0
- data/lib/lock_and_cache_msgpack/action.rb +109 -0
- data/lib/lock_and_cache_msgpack/key.rb +135 -0
- data/lib/lock_and_cache_msgpack/version.rb +3 -0
- data/lib/messagepack_ext.rb +16 -0
- data/lock_and_cache_msgpack.gemspec +34 -0
- data/spec/lock_and_cache/key_spec.rb +65 -0
- data/spec/lock_and_cache_spec.rb +476 -0
- data/spec/spec_helper.rb +11 -0
- metadata +220 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
class Time
|
2
|
+
|
3
|
+
def to_msgpack(packer=nil)
|
4
|
+
packer ||= MessagePack::Packer.new
|
5
|
+
packer.pack self
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
MessagePack::DefaultFactory.register_type(0x00, Symbol)
|
11
|
+
|
12
|
+
MessagePack::DefaultFactory.register_type(0x01, Time,
|
13
|
+
packer: ->(t){ t.iso8601.to_msgpack },
|
14
|
+
unpacker: ->(d){
|
15
|
+
Time.parse(MessagePack.unpack(d.force_encoding("ASCII-8BIT")))
|
16
|
+
})
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lock_and_cache_msgpack/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lock_and_cache_msgpack"
|
8
|
+
spec.version = LockAndCacheMsgpack::VERSION
|
9
|
+
spec.authors = ["Seamus Abshere", "Matt E. Patterson"]
|
10
|
+
spec.email = ["seamus@abshere.net", "mpatterson@skillsengine.com"]
|
11
|
+
spec.summary = %q{Lock and cache methods, with MessagePack.}
|
12
|
+
spec.description = %q{Lock and cache methods, in case things should only be calculated once across processes.}
|
13
|
+
spec.homepage = "https://github.com/c4eo/lock_and_cache_msgpack"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'activesupport'
|
22
|
+
spec.add_runtime_dependency 'redis'
|
23
|
+
# temporary until https://github.com/leandromoreira/redlock-rb/pull/20 is merged
|
24
|
+
spec.add_runtime_dependency 'redlock', '>=0.1.3'
|
25
|
+
spec.add_runtime_dependency 'msgpack', '~> 1.1.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
30
|
+
spec.add_development_dependency 'rspec'
|
31
|
+
spec.add_development_dependency 'thread'
|
32
|
+
spec.add_development_dependency 'yard'
|
33
|
+
spec.add_development_dependency 'redcarpet'
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class KeyTestId
|
4
|
+
def id
|
5
|
+
'id'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
class KeyTestLockAndCacheMsgpackKey
|
9
|
+
def lock_and_cache_key
|
10
|
+
'lock_and_cache_key'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
class KeyTest1
|
14
|
+
def lock_and_cache_key
|
15
|
+
KeyTestLockAndCacheMsgpackKey.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe LockAndCacheMsgpack::Key do
|
19
|
+
describe 'parts' do
|
20
|
+
it "has a known issue differentiating between {a: 1} and [[:a, 1]]" do
|
21
|
+
expect(described_class.new(a: 1).send(:parts)).to eq(described_class.new([[:a, 1]]).send(:parts))
|
22
|
+
end
|
23
|
+
|
24
|
+
now = Time.now
|
25
|
+
today = Date.today
|
26
|
+
{
|
27
|
+
[1] => [1],
|
28
|
+
['you'] => ['you'],
|
29
|
+
[['you']] => [['you']],
|
30
|
+
[['you'], "person"] => [['you'], "person"],
|
31
|
+
[['you'], {:silly=>:person}] => [['you'], [[:silly, :person]] ],
|
32
|
+
[now] => [now.to_s],
|
33
|
+
[[now]] => [[now.to_s]],
|
34
|
+
[today] => [today.to_s],
|
35
|
+
[[today]] => [[today.to_s]],
|
36
|
+
{ hi: 'you' } => [[:hi, 'you']],
|
37
|
+
{ hi: 123 } => [[:hi, 123]],
|
38
|
+
{ hi: 123.0 } => [[:hi, 123.0]],
|
39
|
+
{ hi: now } => [[:hi, now.to_s]],
|
40
|
+
{ hi: today } => [[:hi, today.to_s]],
|
41
|
+
[KeyTestId.new] => ['id'],
|
42
|
+
[[KeyTestId.new]] => [['id']],
|
43
|
+
{ a: KeyTestId.new } => [[:a, "id"]],
|
44
|
+
[{ a: KeyTestId.new }] => [[[:a, "id"]]],
|
45
|
+
[[{ a: KeyTestId.new }]] => [[ [[:a, "id"]] ]],
|
46
|
+
[[{ a: [ KeyTestId.new ] }]] => [[[[:a, ["id"]]]]],
|
47
|
+
[[{ a: { b: KeyTestId.new } }]] => [[ [[ :a, [[:b, "id"]] ]] ]],
|
48
|
+
[[{ a: { b: [ KeyTestId.new ] } }]] => [[ [[ :a, [[:b, ["id"]]] ]] ]],
|
49
|
+
[KeyTestLockAndCacheMsgpackKey.new] => ['lock_and_cache_key'],
|
50
|
+
[[KeyTestLockAndCacheMsgpackKey.new]] => [['lock_and_cache_key']],
|
51
|
+
{ a: KeyTestLockAndCacheMsgpackKey.new } => [[:a, "lock_and_cache_key"]],
|
52
|
+
[{ a: KeyTestLockAndCacheMsgpackKey.new }] => [[[:a, "lock_and_cache_key"]]],
|
53
|
+
[[{ a: KeyTestLockAndCacheMsgpackKey.new }]] => [[ [[:a, "lock_and_cache_key"]] ]],
|
54
|
+
[[{ a: [ KeyTestLockAndCacheMsgpackKey.new ] }]] => [[[[:a, ["lock_and_cache_key"]]]]],
|
55
|
+
[[{ a: { b: KeyTestLockAndCacheMsgpackKey.new } }]] => [[ [[ :a, [[:b, "lock_and_cache_key"]] ]] ]],
|
56
|
+
[[{ a: { b: [ KeyTestLockAndCacheMsgpackKey.new ] } }]] => [[ [[ :a, [[:b, ["lock_and_cache_key"]]] ]] ]],
|
57
|
+
|
58
|
+
[[{ a: { b: [ KeyTest1.new ] } }]] => [[ [[ :a, [[:b, ["lock_and_cache_key"]]] ]] ]],
|
59
|
+
}.each do |i, o|
|
60
|
+
it "turns #{i} into #{o}" do
|
61
|
+
expect(described_class.new(i).send(:parts)).to eq(o)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,476 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Foo
|
4
|
+
include LockAndCacheMsgpack
|
5
|
+
|
6
|
+
def initialize(id)
|
7
|
+
@id = id
|
8
|
+
@count = 0
|
9
|
+
@count_exp = 0
|
10
|
+
@click_single_hash_arg_as_options = 0
|
11
|
+
@click_last_hash_as_options = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def click
|
15
|
+
lock_and_cache do
|
16
|
+
@count += 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def cached_rand
|
21
|
+
lock_and_cache do
|
22
|
+
rand
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def click_null
|
27
|
+
lock_and_cache do
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def click_exp
|
33
|
+
lock_and_cache(expires: 1) do
|
34
|
+
@count_exp += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# foo will be treated as option, so this is cacheable
|
39
|
+
def click_single_hash_arg_as_options
|
40
|
+
lock_and_cache(foo: rand, expires: 1) do
|
41
|
+
@click_single_hash_arg_as_options += 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# foo will be treated as part of cache key, so this is uncacheable
|
46
|
+
def click_last_hash_as_options
|
47
|
+
lock_and_cache({foo: rand}, expires: 1) do
|
48
|
+
@click_last_hash_as_options += 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def lock_and_cache_key
|
53
|
+
@id
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class FooId
|
58
|
+
include LockAndCacheMsgpack
|
59
|
+
def click
|
60
|
+
lock_and_cache do
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
def id
|
65
|
+
@id ||= rand
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class FooClass
|
70
|
+
class << self
|
71
|
+
include LockAndCacheMsgpack
|
72
|
+
def click
|
73
|
+
lock_and_cache do
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
def id
|
78
|
+
raise "called id"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
require 'set'
|
84
|
+
$clicking = Set.new
|
85
|
+
class Bar
|
86
|
+
include LockAndCacheMsgpack
|
87
|
+
|
88
|
+
def initialize(id)
|
89
|
+
@id = id
|
90
|
+
@count = 0
|
91
|
+
@mutex = Mutex.new
|
92
|
+
end
|
93
|
+
|
94
|
+
def unsafe_click
|
95
|
+
@mutex.synchronize do
|
96
|
+
# puts "clicking bar #{@id} - #{$clicking.to_a} - #{$clicking.include?(@id)} - #{@id == $clicking.to_a[0]}"
|
97
|
+
raise "somebody already clicking Bar #{@id}" if $clicking.include?(@id)
|
98
|
+
$clicking << @id
|
99
|
+
end
|
100
|
+
sleep 1
|
101
|
+
@count += 1
|
102
|
+
$clicking.delete @id
|
103
|
+
@count
|
104
|
+
end
|
105
|
+
|
106
|
+
def click
|
107
|
+
lock_and_cache do
|
108
|
+
unsafe_click
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def slow_click
|
113
|
+
lock_and_cache do
|
114
|
+
sleep 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def lock_and_cache_key
|
119
|
+
@id
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Sleeper
|
124
|
+
include LockAndCacheMsgpack
|
125
|
+
|
126
|
+
def initialize
|
127
|
+
@id = SecureRandom.hex
|
128
|
+
end
|
129
|
+
|
130
|
+
def poke
|
131
|
+
lock_and_cache heartbeat_expires: 2 do
|
132
|
+
sleep
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def lock_and_cache_key
|
137
|
+
@id
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe LockAndCacheMsgpack do
|
142
|
+
before do
|
143
|
+
LockAndCacheMsgpack.flush
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'has a version number' do
|
147
|
+
expect(LockAndCacheMsgpack::VERSION).not_to be nil
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "caching" do
|
151
|
+
let(:foo) { Foo.new(rand.to_s) }
|
152
|
+
it "works" do
|
153
|
+
expect(foo.click).to eq(1)
|
154
|
+
expect(foo.click).to eq(1)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "can be cleared" do
|
158
|
+
expect(foo.click).to eq(1)
|
159
|
+
foo.lock_and_cache_clear :click
|
160
|
+
expect(foo.click).to eq(2)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "can be expired" do
|
164
|
+
expect(foo.click_exp).to eq(1)
|
165
|
+
expect(foo.click_exp).to eq(1)
|
166
|
+
sleep 1.5
|
167
|
+
expect(foo.click_exp).to eq(2)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "can cache null" do
|
171
|
+
expect(foo.click_null).to eq(nil)
|
172
|
+
expect(foo.click_null).to eq(nil)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "treats single hash arg as options" do
|
176
|
+
expect(foo.click_single_hash_arg_as_options).to eq(1)
|
177
|
+
expect(foo.click_single_hash_arg_as_options).to eq(1)
|
178
|
+
sleep 1.1
|
179
|
+
expect(foo.click_single_hash_arg_as_options).to eq(2)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "treats last hash as options" do
|
183
|
+
expect(foo.click_last_hash_as_options).to eq(1)
|
184
|
+
expect(foo.click_last_hash_as_options).to eq(2) # it's uncacheable to prove we're not using as part of options
|
185
|
+
expect(foo.click_last_hash_as_options).to eq(3)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "calls #lock_and_cache_key" do
|
189
|
+
expect(foo).to receive(:lock_and_cache_key)
|
190
|
+
foo.click
|
191
|
+
end
|
192
|
+
|
193
|
+
it "calls #lock_and_cache_key to differentiate" do
|
194
|
+
a = Foo.new 1
|
195
|
+
b = Foo.new 2
|
196
|
+
expect(a.cached_rand).not_to eq(b.cached_rand)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe 'self-identification in context mode' do
|
201
|
+
it "calls #id for non-class" do
|
202
|
+
foo_id = FooId.new
|
203
|
+
expect(foo_id).to receive(:id)
|
204
|
+
foo_id.click
|
205
|
+
end
|
206
|
+
it "calls class name for non-class" do
|
207
|
+
foo_id = FooId.new
|
208
|
+
expect(FooId).to receive(:name)
|
209
|
+
foo_id.click
|
210
|
+
end
|
211
|
+
it "uses class name for class" do
|
212
|
+
expect(FooClass).to receive(:name)
|
213
|
+
expect(FooClass).not_to receive(:id)
|
214
|
+
FooClass.click
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "locking" do
|
219
|
+
let(:bar) { Bar.new(rand.to_s) }
|
220
|
+
|
221
|
+
it "it blows up normally (simple thread)" do
|
222
|
+
a = Thread.new do
|
223
|
+
bar.unsafe_click
|
224
|
+
end
|
225
|
+
b = Thread.new do
|
226
|
+
bar.unsafe_click
|
227
|
+
end
|
228
|
+
expect do
|
229
|
+
a.join
|
230
|
+
b.join
|
231
|
+
end.to raise_error(/somebody/)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "it blows up (pre-existing thread pool, more reliable)" do
|
235
|
+
pool = Thread.pool 2
|
236
|
+
Thread::Pool.abort_on_exception = true
|
237
|
+
expect do
|
238
|
+
pool.process do
|
239
|
+
bar.unsafe_click
|
240
|
+
end
|
241
|
+
pool.process do
|
242
|
+
bar.unsafe_click
|
243
|
+
end
|
244
|
+
pool.shutdown
|
245
|
+
end.to raise_error(/somebody/)
|
246
|
+
end
|
247
|
+
|
248
|
+
it "doesn't blow up if you lock it (simple thread)" do
|
249
|
+
a = Thread.new do
|
250
|
+
bar.click
|
251
|
+
end
|
252
|
+
b = Thread.new do
|
253
|
+
bar.click
|
254
|
+
end
|
255
|
+
a.join
|
256
|
+
b.join
|
257
|
+
end
|
258
|
+
|
259
|
+
it "doesn't blow up if you lock it (pre-existing thread pool, more reliable)" do
|
260
|
+
pool = Thread.pool 2
|
261
|
+
Thread::Pool.abort_on_exception = true
|
262
|
+
pool.process do
|
263
|
+
bar.click
|
264
|
+
end
|
265
|
+
pool.process do
|
266
|
+
bar.click
|
267
|
+
end
|
268
|
+
pool.shutdown
|
269
|
+
end
|
270
|
+
|
271
|
+
it "can set a wait time" do
|
272
|
+
pool = Thread.pool 2
|
273
|
+
Thread::Pool.abort_on_exception = true
|
274
|
+
begin
|
275
|
+
old_max = LockAndCacheMsgpack.max_lock_wait
|
276
|
+
LockAndCacheMsgpack.max_lock_wait = 0.5
|
277
|
+
expect do
|
278
|
+
pool.process do
|
279
|
+
bar.slow_click
|
280
|
+
end
|
281
|
+
pool.process do
|
282
|
+
bar.slow_click
|
283
|
+
end
|
284
|
+
pool.shutdown
|
285
|
+
end.to raise_error(LockAndCacheMsgpack::TimeoutWaitingForLock)
|
286
|
+
ensure
|
287
|
+
LockAndCacheMsgpack.max_lock_wait = old_max
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'unlocks if a process dies' do
|
292
|
+
child = nil
|
293
|
+
begin
|
294
|
+
sleeper = Sleeper.new
|
295
|
+
child = fork do
|
296
|
+
sleeper.poke
|
297
|
+
end
|
298
|
+
sleep 0.1
|
299
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process has it
|
300
|
+
Process.kill 'KILL', child
|
301
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other (dead) process still has it
|
302
|
+
sleep 2
|
303
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(false) # but now it should be cleared because no heartbeat
|
304
|
+
ensure
|
305
|
+
Process.kill('KILL', child) rescue Errno::ESRCH
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
it "pays attention to heartbeats" do
|
310
|
+
child = nil
|
311
|
+
begin
|
312
|
+
sleeper = Sleeper.new
|
313
|
+
child = fork do
|
314
|
+
sleeper.poke
|
315
|
+
end
|
316
|
+
sleep 0.1
|
317
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process has it
|
318
|
+
sleep 2
|
319
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process still has it
|
320
|
+
sleep 2
|
321
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process still has it
|
322
|
+
sleep 2
|
323
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process still has it
|
324
|
+
ensure
|
325
|
+
Process.kill('TERM', child) rescue Errno::ESRCH
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
describe 'standalone' do
|
332
|
+
it 'works like you expect' do
|
333
|
+
count = 0
|
334
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') { count += 1 }).to eq(1)
|
335
|
+
expect(count).to eq(1)
|
336
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') { count += 1 }).to eq(1)
|
337
|
+
expect(count).to eq(1)
|
338
|
+
end
|
339
|
+
|
340
|
+
it "can be queried for cached?" do
|
341
|
+
expect(LockAndCacheMsgpack.cached?('hello')).to be_falsy
|
342
|
+
LockAndCacheMsgpack.lock_and_cache('hello') { nil }
|
343
|
+
expect(LockAndCacheMsgpack.cached?('hello')).to be_truthy
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'allows expiry' do
|
347
|
+
count = 0
|
348
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello', expires: 1) { count += 1 }).to eq(1)
|
349
|
+
expect(count).to eq(1)
|
350
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') { count += 1 }).to eq(1)
|
351
|
+
expect(count).to eq(1)
|
352
|
+
sleep 1.1
|
353
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') { count += 1 }).to eq(2)
|
354
|
+
expect(count).to eq(2)
|
355
|
+
end
|
356
|
+
|
357
|
+
it "allows float expiry" do
|
358
|
+
expect{LockAndCacheMsgpack.lock_and_cache('hello', expires: 1.5) {}}.not_to raise_error
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'can be nested' do
|
362
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') do
|
363
|
+
LockAndCacheMsgpack.lock_and_cache('world') do
|
364
|
+
LockAndCacheMsgpack.lock_and_cache('privyet') do
|
365
|
+
123
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end).to eq(123)
|
369
|
+
end
|
370
|
+
|
371
|
+
it "requires a key" do
|
372
|
+
expect do
|
373
|
+
LockAndCacheMsgpack.lock_and_cache do
|
374
|
+
raise "this won't happen"
|
375
|
+
end
|
376
|
+
end.to raise_error(/need/)
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'allows checking locks' do
|
380
|
+
expect(LockAndCacheMsgpack.locked?(:sleeper)).to be_falsey
|
381
|
+
t = Thread.new do
|
382
|
+
LockAndCacheMsgpack.lock_and_cache(:sleeper) { sleep 1 }
|
383
|
+
end
|
384
|
+
sleep 0.2
|
385
|
+
expect(LockAndCacheMsgpack.locked?(:sleeper)).to be_truthy
|
386
|
+
t.join
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'allows clearing' do
|
390
|
+
count = 0
|
391
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') { count += 1 }).to eq(1)
|
392
|
+
expect(count).to eq(1)
|
393
|
+
LockAndCacheMsgpack.clear('hello')
|
394
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello') { count += 1 }).to eq(2)
|
395
|
+
expect(count).to eq(2)
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'allows clearing (complex keys)' do
|
399
|
+
count = 0
|
400
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello', {world: 1}, expires: 100) { count += 1 }).to eq(1)
|
401
|
+
expect(count).to eq(1)
|
402
|
+
LockAndCacheMsgpack.clear('hello', world: 1)
|
403
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello', {world: 1}, expires: 100) { count += 1 }).to eq(2)
|
404
|
+
expect(count).to eq(2)
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'allows multi-part keys' do
|
408
|
+
count = 0
|
409
|
+
expect(LockAndCacheMsgpack.lock_and_cache(['hello', 1, { target: 'world' }]) { count += 1 }).to eq(1)
|
410
|
+
expect(count).to eq(1)
|
411
|
+
expect(LockAndCacheMsgpack.lock_and_cache(['hello', 1, { target: 'world' }]) { count += 1 }).to eq(1)
|
412
|
+
expect(count).to eq(1)
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'treats a single hash arg as a cache key (not as options)' do
|
416
|
+
count = 0
|
417
|
+
LockAndCacheMsgpack.lock_and_cache(hello: 'world', expires: 100) { count += 1 }
|
418
|
+
expect(count).to eq(1)
|
419
|
+
LockAndCacheMsgpack.lock_and_cache(hello: 'world', expires: 100) { count += 1 }
|
420
|
+
expect(count).to eq(1)
|
421
|
+
LockAndCacheMsgpack.lock_and_cache(hello: 'world', expires: 200) { count += 1 } # expires is being treated as part of cache key
|
422
|
+
expect(count).to eq(2)
|
423
|
+
end
|
424
|
+
|
425
|
+
it "correctly identifies options hash" do
|
426
|
+
count = 0
|
427
|
+
LockAndCacheMsgpack.lock_and_cache({ hello: 'world' }, expires: 1, ignored: rand) { count += 1 }
|
428
|
+
expect(count).to eq(1)
|
429
|
+
LockAndCacheMsgpack.lock_and_cache({ hello: 'world' }, expires: 1, ignored: rand) { count += 1 } # expires is not being treated as part of cache key
|
430
|
+
expect(count).to eq(1)
|
431
|
+
sleep 1.1
|
432
|
+
LockAndCacheMsgpack.lock_and_cache({ hello: 'world' }) { count += 1 }
|
433
|
+
expect(count).to eq(2)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
describe "shorter expiry for null results" do
|
438
|
+
it "optionally caches null for less time" do
|
439
|
+
count = 0
|
440
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; nil }
|
441
|
+
expect(count).to eq(1)
|
442
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; nil }
|
443
|
+
expect(count).to eq(1)
|
444
|
+
sleep 1.1 # this is enough to expire
|
445
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; nil }
|
446
|
+
expect(count).to eq(2)
|
447
|
+
end
|
448
|
+
|
449
|
+
it "normally caches null for the same amount of time" do
|
450
|
+
count = 0
|
451
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello', expires: 1) { count += 1; nil }).to be_nil
|
452
|
+
expect(count).to eq(1)
|
453
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello', expires: 1) { count += 1; nil }).to be_nil
|
454
|
+
expect(count).to eq(1)
|
455
|
+
sleep 1.1
|
456
|
+
expect(LockAndCacheMsgpack.lock_and_cache('hello', expires: 1) { count += 1; nil }).to be_nil
|
457
|
+
expect(count).to eq(2)
|
458
|
+
end
|
459
|
+
|
460
|
+
it "caches non-null for normal time" do
|
461
|
+
count = 0
|
462
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; true }
|
463
|
+
expect(count).to eq(1)
|
464
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; true }
|
465
|
+
expect(count).to eq(1)
|
466
|
+
sleep 1.1
|
467
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; true }
|
468
|
+
expect(count).to eq(1)
|
469
|
+
sleep 1
|
470
|
+
LockAndCacheMsgpack.lock_and_cache('hello', nil_expires: 1, expires: 2) { count += 1; true }
|
471
|
+
expect(count).to eq(2)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
|
476
|
+
end
|