lock_and_cache_msgpack 4.0.7.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'date'
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
ALLOWED_IN_KEYS = [
|
6
|
+
::String,
|
7
|
+
::Symbol,
|
8
|
+
::Numeric,
|
9
|
+
::TrueClass,
|
10
|
+
::FalseClass,
|
11
|
+
::NilClass,
|
12
|
+
::Integer,
|
13
|
+
::Float,
|
14
|
+
::Date,
|
15
|
+
::DateTime,
|
16
|
+
::Time,
|
17
|
+
].to_set
|
18
|
+
parts = RUBY_VERSION.split('.').map(&:to_i)
|
19
|
+
unless parts[0] >= 2 and parts[1] >= 4
|
20
|
+
ALLOWED_IN_KEYS << ::Fixnum
|
21
|
+
ALLOWED_IN_KEYS << ::Bignum
|
22
|
+
end
|
23
|
+
|
24
|
+
EXAMPLES = [
|
25
|
+
'hi',
|
26
|
+
:there,
|
27
|
+
123,
|
28
|
+
123.54,
|
29
|
+
1e99,
|
30
|
+
123456789 ** 2,
|
31
|
+
1e999,
|
32
|
+
true,
|
33
|
+
false,
|
34
|
+
nil,
|
35
|
+
Date.new(2015,1,1),
|
36
|
+
Time.now,
|
37
|
+
DateTime.now,
|
38
|
+
Mutex,
|
39
|
+
Mutex.new,
|
40
|
+
Benchmark,
|
41
|
+
{ hi: :world },
|
42
|
+
[[]],
|
43
|
+
Fixnum,
|
44
|
+
Struct,
|
45
|
+
Struct.new(:a),
|
46
|
+
Struct.new(:a).new(123)
|
47
|
+
]
|
48
|
+
EXAMPLES.each do |example|
|
49
|
+
puts "#{example} -> #{example.class}"
|
50
|
+
end
|
51
|
+
|
52
|
+
puts
|
53
|
+
|
54
|
+
[
|
55
|
+
Date.new(2015,1,1),
|
56
|
+
Time.now,
|
57
|
+
DateTime.now,
|
58
|
+
].each do |x|
|
59
|
+
puts x.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
puts
|
63
|
+
|
64
|
+
EXAMPLES.each do |example|
|
65
|
+
a = ALLOWED_IN_KEYS.any? { |thing| example.is_a?(thing) }
|
66
|
+
b = ALLOWED_IN_KEYS.include? example.class
|
67
|
+
unless a == b
|
68
|
+
raise "#{example.inspect}: #{a.inspect} vs #{b.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Benchmark.ips do |x|
|
73
|
+
x.report("any") do
|
74
|
+
example = EXAMPLES.sample
|
75
|
+
y = ALLOWED_IN_KEYS.any? { |thing| example.is_a?(thing) }
|
76
|
+
a = 1
|
77
|
+
y
|
78
|
+
end
|
79
|
+
|
80
|
+
x.report("include") do
|
81
|
+
example = EXAMPLES.sample
|
82
|
+
y = ALLOWED_IN_KEYS.include? example.class
|
83
|
+
a = 1
|
84
|
+
y
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'timeout'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
5
|
+
require 'redis'
|
6
|
+
require 'redlock'
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_support/core_ext'
|
9
|
+
require 'msgpack'
|
10
|
+
require 'messagepack_ext'
|
11
|
+
|
12
|
+
require_relative 'lock_and_cache_msgpack/version'
|
13
|
+
require_relative 'lock_and_cache_msgpack/action'
|
14
|
+
require_relative 'lock_and_cache_msgpack/key'
|
15
|
+
|
16
|
+
# Lock and cache using redis!
|
17
|
+
#
|
18
|
+
# Most caching libraries don't do locking, meaning that >1 process can be calculating a cached value at the same time. Since you presumably cache things because they cost CPU, database reads, or money, doesn't it make sense to lock while caching?
|
19
|
+
module LockAndCacheMsgpack
|
20
|
+
DEFAULT_MAX_LOCK_WAIT = 60 * 60 * 24 # 1 day in seconds
|
21
|
+
|
22
|
+
DEFAULT_HEARTBEAT_EXPIRES = 32 # 32 seconds
|
23
|
+
|
24
|
+
class TimeoutWaitingForLock < StandardError; end
|
25
|
+
|
26
|
+
# @param redis_connection [Redis] A redis connection to be used for lock and cached value storage
|
27
|
+
def LockAndCacheMsgpack.storage=(redis_connection)
|
28
|
+
raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
|
29
|
+
@storage = redis_connection
|
30
|
+
@lock_manager = Redlock::Client.new [redis_connection], retry_count: 1
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Redis] The redis connection used for lock and cached value storage
|
34
|
+
def LockAndCacheMsgpack.storage
|
35
|
+
@storage
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param logger [Logger] A logger.
|
39
|
+
def LockAndCacheMsgpack.logger=(logger)
|
40
|
+
@logger = logger
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Logger] The logger.
|
44
|
+
def LockAndCacheMsgpack.logger
|
45
|
+
@logger
|
46
|
+
end
|
47
|
+
|
48
|
+
# Flush LockAndCacheMsgpack's storage.
|
49
|
+
#
|
50
|
+
# @note If you are sharing a redis database, it will clear it...
|
51
|
+
#
|
52
|
+
# @note If you want to clear a single key, try `LockAndCacheMsgpack.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.
|
53
|
+
def LockAndCacheMsgpack.flush
|
54
|
+
storage.flushdb
|
55
|
+
end
|
56
|
+
|
57
|
+
# Lock and cache based on a key.
|
58
|
+
#
|
59
|
+
# @param key_parts [*] Parts that should be used to construct a key.
|
60
|
+
#
|
61
|
+
# @note Standalone mode. See also "context mode," where you mix LockAndCacheMsgpack into a class and call it from within its methods.
|
62
|
+
#
|
63
|
+
# @note A single hash arg is treated as a cache key, e.g. `LockAndCacheMsgpack.lock_and_cache(foo: :bar, expires: 100)` will be treated as a cache key of `foo: :bar, expires: 100` (which is probably wrong!!!). Try `LockAndCacheMsgpack.lock_and_cache({ foo: :bar }, expires: 100)` instead. This is the opposite of context mode.
|
64
|
+
def LockAndCacheMsgpack.lock_and_cache(*key_parts_and_options, &blk)
|
65
|
+
options = (key_parts_and_options.last.is_a?(Hash) && key_parts_and_options.length > 1) ? key_parts_and_options.pop : {}
|
66
|
+
raise "need a cache key" unless key_parts_and_options.length > 0
|
67
|
+
key = LockAndCacheMsgpack::Key.new key_parts_and_options
|
68
|
+
action = LockAndCacheMsgpack::Action.new key, options, blk
|
69
|
+
action.perform
|
70
|
+
end
|
71
|
+
|
72
|
+
# Clear a single key
|
73
|
+
#
|
74
|
+
# @note Standalone mode. See also "context mode," where you mix LockAndCacheMsgpack into a class and call it from within its methods.
|
75
|
+
def LockAndCacheMsgpack.clear(*key_parts)
|
76
|
+
key = LockAndCacheMsgpack::Key.new key_parts
|
77
|
+
key.clear
|
78
|
+
end
|
79
|
+
|
80
|
+
# Check if a key is locked
|
81
|
+
#
|
82
|
+
# @note Standalone mode. See also "context mode," where you mix LockAndCacheMsgpack into a class and call it from within its methods.
|
83
|
+
def LockAndCacheMsgpack.locked?(*key_parts)
|
84
|
+
key = LockAndCacheMsgpack::Key.new key_parts
|
85
|
+
key.locked?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Check if a key is cached already
|
89
|
+
#
|
90
|
+
# @note Standalone mode. See also "context mode," where you mix LockAndCacheMsgpack into a class and call it from within its methods.
|
91
|
+
def LockAndCacheMsgpack.cached?(*key_parts)
|
92
|
+
key = LockAndCacheMsgpack::Key.new key_parts
|
93
|
+
key.cached?
|
94
|
+
end
|
95
|
+
|
96
|
+
# @param seconds [Numeric] Maximum wait time to get a lock
|
97
|
+
#
|
98
|
+
# @note Can be overridden by putting `max_lock_wait:` in your call to `#lock_and_cache`
|
99
|
+
def LockAndCacheMsgpack.max_lock_wait=(seconds)
|
100
|
+
@max_lock_wait = seconds.to_f
|
101
|
+
end
|
102
|
+
|
103
|
+
# @private
|
104
|
+
def LockAndCacheMsgpack.max_lock_wait
|
105
|
+
@max_lock_wait || DEFAULT_MAX_LOCK_WAIT
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param seconds [Numeric] How often a process has to heartbeat in order to keep a lock
|
109
|
+
#
|
110
|
+
# @note Can be overridden by putting `heartbeat_expires:` in your call to `#lock_and_cache`
|
111
|
+
def LockAndCacheMsgpack.heartbeat_expires=(seconds)
|
112
|
+
memo = seconds.to_f
|
113
|
+
raise "heartbeat_expires must be greater than 2 seconds" unless memo >= 2
|
114
|
+
@heartbeat_expires = memo
|
115
|
+
end
|
116
|
+
|
117
|
+
# @private
|
118
|
+
def LockAndCacheMsgpack.heartbeat_expires
|
119
|
+
@heartbeat_expires || DEFAULT_HEARTBEAT_EXPIRES
|
120
|
+
end
|
121
|
+
|
122
|
+
# @private
|
123
|
+
def LockAndCacheMsgpack.lock_manager
|
124
|
+
@lock_manager
|
125
|
+
end
|
126
|
+
|
127
|
+
# Check if a method is locked on an object.
|
128
|
+
#
|
129
|
+
# @note Subject mode - this is expected to be called on an object whose class has LockAndCacheMsgpack mixed in. See also standalone mode.
|
130
|
+
def lock_and_cache_locked?(method_id, *key_parts)
|
131
|
+
key = LockAndCacheMsgpack::Key.new key_parts, context: self, method_id: method_id
|
132
|
+
key.locked?
|
133
|
+
end
|
134
|
+
|
135
|
+
# Clear a lock and cache given exactly the method and exactly the same arguments
|
136
|
+
#
|
137
|
+
# @note Subject mode - this is expected to be called on an object whose class has LockAndCacheMsgpack mixed in. See also standalone mode.
|
138
|
+
def lock_and_cache_clear(method_id, *key_parts)
|
139
|
+
key = LockAndCacheMsgpack::Key.new key_parts, context: self, method_id: method_id
|
140
|
+
key.clear
|
141
|
+
end
|
142
|
+
|
143
|
+
# Lock and cache a method given key parts.
|
144
|
+
#
|
145
|
+
# The cache key will automatically include the class name of the object calling it (the context!) and the name of the method it is called from.
|
146
|
+
#
|
147
|
+
# @param key_parts_and_options [*] Parts that you want to include in the lock and cache key. If the last element is a Hash, it will be treated as options.
|
148
|
+
#
|
149
|
+
# @return The cached value (possibly newly calculated).
|
150
|
+
#
|
151
|
+
# @note Subject mode - this is expected to be called on an object whose class has LockAndCacheMsgpack mixed in. See also standalone mode.
|
152
|
+
#
|
153
|
+
# @note A single hash arg is treated as an options hash, e.g. `lock_and_cache(expires: 100)` will be treated as options `expires: 100`. This is the opposite of standalone mode.
|
154
|
+
def lock_and_cache(*key_parts_and_options, &blk)
|
155
|
+
options = key_parts_and_options.last.is_a?(Hash) ? key_parts_and_options.pop : {}
|
156
|
+
key = LockAndCacheMsgpack::Key.new key_parts_and_options, context: self, caller: caller
|
157
|
+
action = LockAndCacheMsgpack::Action.new key, options, blk
|
158
|
+
action.perform
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
logger = Logger.new $stderr
|
163
|
+
logger.level = (ENV['LOCK_AND_CACHE_DEBUG'] == 'true') ? Logger::DEBUG : Logger::INFO
|
164
|
+
LockAndCacheMsgpack.logger = logger
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module LockAndCacheMsgpack
|
2
|
+
# @private
|
3
|
+
class Action
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :options
|
6
|
+
attr_reader :blk
|
7
|
+
|
8
|
+
def initialize(key, options, blk)
|
9
|
+
raise "need a block" unless blk
|
10
|
+
@key = key
|
11
|
+
@options = options.stringify_keys
|
12
|
+
@blk = blk
|
13
|
+
end
|
14
|
+
|
15
|
+
def expires
|
16
|
+
return @expires if defined?(@expires)
|
17
|
+
@expires = options.has_key?('expires') ? options['expires'].to_f.round : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def nil_expires
|
21
|
+
return @nil_expires if defined?(@nil_expires)
|
22
|
+
@nil_expires = options.has_key?('nil_expires') ? options['nil_expires'].to_f.round : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def digest
|
26
|
+
@digest ||= key.digest
|
27
|
+
end
|
28
|
+
|
29
|
+
def lock_digest
|
30
|
+
@lock_digest ||= key.lock_digest
|
31
|
+
end
|
32
|
+
|
33
|
+
def storage
|
34
|
+
@storage ||= LockAndCacheMsgpack.storage or raise("must set LockAndCacheMsgpack.storage=[Redis]")
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform
|
38
|
+
max_lock_wait = options.fetch 'max_lock_wait', LockAndCacheMsgpack.max_lock_wait
|
39
|
+
heartbeat_expires = options.fetch('heartbeat_expires', LockAndCacheMsgpack.heartbeat_expires).to_f.ceil
|
40
|
+
raise "heartbeat_expires must be >= 2 seconds" unless heartbeat_expires >= 2
|
41
|
+
heartbeat_frequency = (heartbeat_expires / 2).ceil
|
42
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
43
|
+
if storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
|
44
|
+
return MessagePack.unpack(existing)
|
45
|
+
end
|
46
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
47
|
+
retval = nil
|
48
|
+
lock_manager = LockAndCacheMsgpack.lock_manager
|
49
|
+
lock_info = nil
|
50
|
+
begin
|
51
|
+
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
|
52
|
+
until lock_info = lock_manager.lock(lock_digest, heartbeat_expires*1000)
|
53
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
54
|
+
sleep rand
|
55
|
+
end
|
56
|
+
end
|
57
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
58
|
+
if storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
|
59
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
60
|
+
retval = MessagePack.unpack existing
|
61
|
+
end
|
62
|
+
unless retval
|
63
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] F1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
64
|
+
done = false
|
65
|
+
begin
|
66
|
+
lock_extender = Thread.new do
|
67
|
+
loop do
|
68
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] heartbeat1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
69
|
+
break if done
|
70
|
+
sleep heartbeat_frequency
|
71
|
+
break if done
|
72
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] heartbeat2 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
73
|
+
lock_manager.lock lock_digest, heartbeat_expires*1000, extend: lock_info
|
74
|
+
end
|
75
|
+
end
|
76
|
+
retval = blk.call
|
77
|
+
retval.nil? ? set_nil : set_non_nil(retval)
|
78
|
+
ensure
|
79
|
+
done = true
|
80
|
+
lock_extender.join if lock_extender.status.nil?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
ensure
|
84
|
+
lock_manager.unlock lock_info if lock_info
|
85
|
+
end
|
86
|
+
retval
|
87
|
+
end
|
88
|
+
|
89
|
+
NIL = MessagePack.pack nil
|
90
|
+
def set_nil
|
91
|
+
if nil_expires
|
92
|
+
storage.setex digest, nil_expires, NIL
|
93
|
+
elsif expires
|
94
|
+
storage.setex digest, expires, NIL
|
95
|
+
else
|
96
|
+
storage.set digest, NIL
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_non_nil(retval)
|
101
|
+
raise "expected not null #{retval.inspect}" if retval.nil?
|
102
|
+
if expires
|
103
|
+
storage.setex digest, expires, MessagePack.pack(retval)
|
104
|
+
else
|
105
|
+
storage.set digest, MessagePack.pack(retval)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module LockAndCacheMsgpack
|
4
|
+
# @private
|
5
|
+
class Key
|
6
|
+
class << self
|
7
|
+
# @private
|
8
|
+
#
|
9
|
+
# Extract the method id from a method's caller array.
|
10
|
+
def extract_method_id_from_caller(kaller)
|
11
|
+
kaller[0] =~ METHOD_NAME_IN_CALLER
|
12
|
+
raise "couldn't get method_id from #{kaller[0]}" unless $1
|
13
|
+
$1.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
#
|
18
|
+
# Get a context object's class name, which is its own name if it's an object.
|
19
|
+
def extract_class_name(context)
|
20
|
+
(context.class == ::Class) ? context.name : context.class.name
|
21
|
+
end
|
22
|
+
|
23
|
+
# @private
|
24
|
+
#
|
25
|
+
# Recursively extract id from obj. Calls #lock_and_cache_key if available, otherwise #id
|
26
|
+
def extract_obj_id(obj)
|
27
|
+
klass = obj.class
|
28
|
+
if ALLOWED_IN_KEYS.include?(klass)
|
29
|
+
obj
|
30
|
+
elsif DATE.include?(klass)
|
31
|
+
obj.to_s
|
32
|
+
elsif obj.respond_to?(:lock_and_cache_key)
|
33
|
+
extract_obj_id obj.lock_and_cache_key
|
34
|
+
elsif obj.respond_to?(:id)
|
35
|
+
extract_obj_id obj.id
|
36
|
+
elsif obj.respond_to?(:map)
|
37
|
+
obj.map { |objj| extract_obj_id objj }
|
38
|
+
else
|
39
|
+
raise "#{obj.inspect} must respond to #lock_and_cache_key or #id"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ALLOWED_IN_KEYS = [
|
45
|
+
::String,
|
46
|
+
::Symbol,
|
47
|
+
::Numeric,
|
48
|
+
::TrueClass,
|
49
|
+
::FalseClass,
|
50
|
+
::NilClass,
|
51
|
+
::Integer,
|
52
|
+
::Float,
|
53
|
+
].to_set
|
54
|
+
parts = ::RUBY_VERSION.split('.').map(&:to_i)
|
55
|
+
unless parts[0] >= 2 and parts[1] >= 4
|
56
|
+
ALLOWED_IN_KEYS << ::Fixnum
|
57
|
+
ALLOWED_IN_KEYS << ::Bignum
|
58
|
+
end
|
59
|
+
DATE = [
|
60
|
+
::Date,
|
61
|
+
::DateTime,
|
62
|
+
::Time,
|
63
|
+
].to_set
|
64
|
+
METHOD_NAME_IN_CALLER = /in `([^']+)'/
|
65
|
+
|
66
|
+
attr_reader :context
|
67
|
+
attr_reader :method_id
|
68
|
+
|
69
|
+
def initialize(parts, options = {})
|
70
|
+
@_parts = parts
|
71
|
+
@context = options[:context]
|
72
|
+
@method_id = if options.has_key?(:method_id)
|
73
|
+
options[:method_id]
|
74
|
+
elsif options.has_key?(:caller)
|
75
|
+
Key.extract_method_id_from_caller options[:caller]
|
76
|
+
elsif context
|
77
|
+
raise "supposed to call context with method_id or caller"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A (non-cryptographic) digest of the key parts for use as the cache key
|
82
|
+
def digest
|
83
|
+
@digest ||= ::Digest::SHA1.hexdigest MessagePack.pack(key)
|
84
|
+
end
|
85
|
+
|
86
|
+
# A (non-cryptographic) digest of the key parts for use as the lock key
|
87
|
+
def lock_digest
|
88
|
+
@lock_digest ||= 'lock/' + digest
|
89
|
+
end
|
90
|
+
|
91
|
+
# A human-readable representation of the key parts
|
92
|
+
def key
|
93
|
+
@key ||= if context
|
94
|
+
[class_name, context_id, method_id, parts].compact
|
95
|
+
else
|
96
|
+
parts
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def locked?
|
101
|
+
LockAndCacheMsgpack.storage.exists lock_digest
|
102
|
+
end
|
103
|
+
|
104
|
+
def cached?
|
105
|
+
LockAndCacheMsgpack.storage.exists digest
|
106
|
+
end
|
107
|
+
|
108
|
+
def clear
|
109
|
+
LockAndCacheMsgpack.logger.debug { "[lock_and_cache] clear #{debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
110
|
+
storage = LockAndCacheMsgpack.storage
|
111
|
+
storage.del digest
|
112
|
+
storage.del lock_digest
|
113
|
+
end
|
114
|
+
|
115
|
+
alias debug key
|
116
|
+
|
117
|
+
def context_id
|
118
|
+
return @context_id if defined?(@context_id)
|
119
|
+
@context_id = if context.class == ::Class
|
120
|
+
nil
|
121
|
+
else
|
122
|
+
Key.extract_obj_id context
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def class_name
|
127
|
+
@class_name ||= Key.extract_class_name context
|
128
|
+
end
|
129
|
+
|
130
|
+
# An array of the parts we use for the key
|
131
|
+
def parts
|
132
|
+
@parts ||= Key.extract_obj_id @_parts
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|