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
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
|