lock_and_cache 3.0.1 → 4.0.4

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: 49f75de7ca663703123c5a93b4c03960ce20449e
4
- data.tar.gz: e6f260e217549f32b4ce54b8e8fe03adbc27078e
3
+ metadata.gz: 4a3eaf745d9a9532d2062ea332fb63420fabbdd9
4
+ data.tar.gz: d1f4ec88da5e0f728da43d9b2f40d536c4224498
5
5
  SHA512:
6
- metadata.gz: 6a027a15e0ec3c8918f9825d57a97f059a93a91bf43d2aefd32d7cd0cedf6eb223fddfd0df006e652824e927573a6cba408bd4f22bbd2c2ce7fb1345377ca985
7
- data.tar.gz: 4ebe3e917a13f182e04a5e10fc19c58d109db200e2ac42bb1cb4078c1122eda2bd331836a3540ff794ba4987f326d33a8792b974fd26c0ac27ad9312a23ff9ca
6
+ metadata.gz: f34529d617b048841b0d33234619846f49a7367ff5627e653298098f0780401314f37df6aa9afe73d967ad358eeaffc3fd0d70ccbc043ecb6a8c1b360c2b61f7
7
+ data.tar.gz: d006e7f9e798425f6137d5611c3688b3e78fd906e15390ad0ff015f0834f3fd3e9a4d820c4ca546fa606f17f9ea481296d7f444dc0189cb48b4e368643251648
data/CHANGELOG CHANGED
@@ -1,3 +1,33 @@
1
+ 4.0.4 / 2016-04-11
2
+
3
+ * Bug fixes
4
+
5
+ * Don't default to debug logging
6
+
7
+ 4.0.3 / 2016-04-11
8
+
9
+ * Bug fixes
10
+
11
+ * Allow true or false in keys
12
+
13
+ 4.0.2 / 2016-04-11
14
+
15
+ * Bug fixes
16
+
17
+ * When generating key, recurse into #lock_and_cache_key
18
+
19
+ 4.0.1 / 2016-04-11
20
+
21
+ * Bug fixes
22
+
23
+ * Avoid deadlocks related to logging
24
+
25
+ 4.0.0 / 2016-04-11
26
+
27
+ * Breaking changes
28
+
29
+ * The cache key generation I've always wanted: recursively call #id or #lock_and_cache_key
30
+
1
31
  3.0.1 / 2016-04-04
2
32
 
3
33
  * Enhancements
data/README.md CHANGED
@@ -212,10 +212,13 @@ You can expire nil values with a different timeout (`nil_expires`) than other va
212
212
  * [redis](https://github.com/redis/redis-rb)
213
213
  * [redlock](https://github.com/leandromoreira/redlock-rb)
214
214
 
215
+ ## Known issues
216
+
217
+ * In cache keys, can't distinguish {a: 1} from [[:a, 1]]
218
+
215
219
  ## Wishlist
216
220
 
217
221
  * Convert most tests to use standalone mode, which is easier to understand
218
- * Make explicit Key unit tests
219
222
  * Check options
220
223
  * Lengthen heartbeat so it's not so sensitive
221
224
  * Clarify which options are seconds or milliseconds
@@ -1,3 +1,4 @@
1
+ require 'logger'
1
2
  require 'timeout'
2
3
  require 'digest/sha1'
3
4
  require 'base64'
@@ -18,8 +19,6 @@ module LockAndCache
18
19
 
19
20
  DEFAULT_HEARTBEAT_EXPIRES = 32 # 32 seconds
20
21
 
21
- LOG_MUTEX = Mutex.new
22
-
23
22
  class TimeoutWaitingForLock < StandardError; end
24
23
 
25
24
  # @param redis_connection [Redis] A redis connection to be used for lock and cached value storage
@@ -34,6 +33,16 @@ module LockAndCache
34
33
  @storage
35
34
  end
36
35
 
36
+ # @param logger [Logger] A logger.
37
+ def LockAndCache.logger=(logger)
38
+ @logger = logger
39
+ end
40
+
41
+ # @return [Logger] The logger.
42
+ def LockAndCache.logger
43
+ @logger
44
+ end
45
+
37
46
  # Flush LockAndCache's storage.
38
47
  #
39
48
  # @note If you are sharing a redis database, it will clear it...
@@ -139,3 +148,7 @@ module LockAndCache
139
148
  action.perform
140
149
  end
141
150
  end
151
+
152
+ logger = Logger.new $stderr
153
+ logger.level = (ENV['LOCK_AND_CACHE_DEBUG'] == 'true') ? Logger::DEBUG : Logger::INFO
154
+ LockAndCache.logger = logger
@@ -35,42 +35,41 @@ module LockAndCache
35
35
  end
36
36
 
37
37
  def perform
38
- debug = (ENV['LOCK_AND_CACHE_DEBUG'] == 'true')
39
38
  max_lock_wait = options.fetch 'max_lock_wait', LockAndCache.max_lock_wait
40
39
  heartbeat_expires = options.fetch('heartbeat_expires', LockAndCache.heartbeat_expires).to_f.ceil
41
40
  raise "heartbeat_expires must be >= 2 seconds" unless heartbeat_expires >= 2
42
41
  heartbeat_frequency = (heartbeat_expires / 2).ceil
43
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
42
+ LockAndCache.logger.debug { "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
44
43
  if storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
45
44
  return ::Marshal.load(existing)
46
45
  end
47
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
46
+ LockAndCache.logger.debug { "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
48
47
  retval = nil
49
48
  lock_manager = LockAndCache.lock_manager
50
49
  lock_info = nil
51
50
  begin
52
51
  Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
53
52
  until lock_info = lock_manager.lock(lock_digest, heartbeat_expires*1000)
54
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
53
+ LockAndCache.logger.debug { "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
55
54
  sleep rand
56
55
  end
57
56
  end
58
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
57
+ LockAndCache.logger.debug { "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
59
58
  if storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
60
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
59
+ LockAndCache.logger.debug { "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
61
60
  retval = ::Marshal.load existing
62
61
  end
63
62
  unless retval
64
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] F1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
63
+ LockAndCache.logger.debug { "[lock_and_cache] F1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
65
64
  done = false
66
65
  begin
67
66
  lock_extender = Thread.new do
68
67
  loop do
69
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] heartbeat1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
68
+ LockAndCache.logger.debug { "[lock_and_cache] heartbeat1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
70
69
  break if done
71
70
  sleep heartbeat_frequency
72
71
  break if done
73
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] heartbeat2 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
72
+ LockAndCache.logger.debug { "[lock_and_cache] heartbeat2 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
74
73
  lock_manager.lock lock_digest, heartbeat_expires*1000, extend: lock_info
75
74
  end
76
75
  end
@@ -20,20 +20,30 @@ module LockAndCache
20
20
 
21
21
  # @private
22
22
  #
23
- # Extract id from context. Calls #lock_and_cache_key if available, otherwise #id
24
- def extract_context_id(context)
25
- if context.class == ::Class
26
- nil
27
- elsif context.respond_to?(:lock_and_cache_key)
28
- context.lock_and_cache_key
29
- elsif context.respond_to?(:id)
30
- context.id
23
+ # Recursively extract id from obj. Calls #lock_and_cache_key if available, otherwise #id
24
+ def extract_obj_id(obj)
25
+ if ALLOWED_IN_KEYS.any? { |k| obj.is_a?(k) }
26
+ obj
27
+ elsif obj.respond_to?(:lock_and_cache_key)
28
+ extract_obj_id obj.lock_and_cache_key
29
+ elsif obj.respond_to?(:id)
30
+ extract_obj_id obj.id
31
+ elsif obj.respond_to?(:map)
32
+ obj.map { |objj| extract_obj_id objj }
31
33
  else
32
- raise "#{context} must respond to #lock_and_cache_key or #id"
34
+ raise "#{obj.inspect} must respond to #lock_and_cache_key or #id"
33
35
  end
34
36
  end
35
37
  end
36
38
 
39
+ ALLOWED_IN_KEYS = [
40
+ ::String,
41
+ ::Symbol,
42
+ ::Numeric,
43
+ ::TrueClass,
44
+ ::FalseClass,
45
+ ::NilClass,
46
+ ]
37
47
  METHOD_NAME_IN_CALLER = /in `([^']+)'/
38
48
 
39
49
  attr_reader :context
@@ -75,7 +85,7 @@ module LockAndCache
75
85
  end
76
86
 
77
87
  def clear
78
- LockAndCache::LOG_MUTEX.synchronize { $stderr.puts "[lock_and_cache] clear #{debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if ENV['LOCK_AND_CACHE_DEBUG'] == 'true'
88
+ LockAndCache.logger.debug { "[lock_and_cache] clear #{debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
79
89
  storage = LockAndCache.storage
80
90
  storage.del digest
81
91
  storage.del lock_digest
@@ -84,7 +94,12 @@ module LockAndCache
84
94
  alias debug key
85
95
 
86
96
  def context_id
87
- @context_id ||= Key.extract_context_id context
97
+ return @context_id if defined?(@context_id)
98
+ @context_id = if context.class == ::Class
99
+ nil
100
+ else
101
+ Key.extract_obj_id context
102
+ end
88
103
  end
89
104
 
90
105
  def class_name
@@ -93,14 +108,7 @@ module LockAndCache
93
108
 
94
109
  # An array of the parts we use for the key
95
110
  def parts
96
- @parts ||= @_parts.map do |v|
97
- case v
98
- when ::String, ::Symbol, ::Hash, ::Array
99
- v
100
- else
101
- v.respond_to?(:lock_and_cache_key) ? v.lock_and_cache_key : v.id
102
- end
103
- end
111
+ @parts ||= Key.extract_obj_id @_parts
104
112
  end
105
113
  end
106
114
  end
@@ -1,3 +1,3 @@
1
1
  module LockAndCache
2
- VERSION = '3.0.1'
2
+ VERSION = '4.0.4'
3
3
  end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ class KeyTestId
4
+ def id
5
+ 'id'
6
+ end
7
+ end
8
+ class KeyTestLockAndCacheKey
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
+ KeyTestLockAndCacheKey.new
16
+ end
17
+ end
18
+ describe LockAndCache::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
+ {
25
+ [1] => [1],
26
+ ["you"] => ['you'],
27
+ [["you"]] => [['you']],
28
+ [["you"], "person"] => [["you"], "person"],
29
+ [["you"], {:silly=>:person}] => [["you"], [[:silly, :person]] ],
30
+ { hi: 'you' } => [[:hi, "you"]],
31
+ [KeyTestId.new] => ['id'],
32
+ [[KeyTestId.new]] => [['id']],
33
+ { a: KeyTestId.new } => [[:a, "id"]],
34
+ [{ a: KeyTestId.new }] => [[[:a, "id"]]],
35
+ [[{ a: KeyTestId.new }]] => [[ [[:a, "id"]] ]],
36
+ [[{ a: [ KeyTestId.new ] }]] => [[[[:a, ["id"]]]]],
37
+ [[{ a: { b: KeyTestId.new } }]] => [[ [[ :a, [[:b, "id"]] ]] ]],
38
+ [[{ a: { b: [ KeyTestId.new ] } }]] => [[ [[ :a, [[:b, ["id"]]] ]] ]],
39
+ [KeyTestLockAndCacheKey.new] => ['lock_and_cache_key'],
40
+ [[KeyTestLockAndCacheKey.new]] => [['lock_and_cache_key']],
41
+ { a: KeyTestLockAndCacheKey.new } => [[:a, "lock_and_cache_key"]],
42
+ [{ a: KeyTestLockAndCacheKey.new }] => [[[:a, "lock_and_cache_key"]]],
43
+ [[{ a: KeyTestLockAndCacheKey.new }]] => [[ [[:a, "lock_and_cache_key"]] ]],
44
+ [[{ a: [ KeyTestLockAndCacheKey.new ] }]] => [[[[:a, ["lock_and_cache_key"]]]]],
45
+ [[{ a: { b: KeyTestLockAndCacheKey.new } }]] => [[ [[ :a, [[:b, "lock_and_cache_key"]] ]] ]],
46
+ [[{ a: { b: [ KeyTestLockAndCacheKey.new ] } }]] => [[ [[ :a, [[:b, ["lock_and_cache_key"]]] ]] ]],
47
+
48
+ [[{ a: { b: [ KeyTest1.new ] } }]] => [[ [[ :a, [[:b, ["lock_and_cache_key"]]] ]] ]],
49
+ }.each do |i, o|
50
+ it "turns #{i} into #{o}" do
51
+ expect(described_class.new(i).send(:parts)).to eq(o)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -88,10 +88,11 @@ class Bar
88
88
  def initialize(id)
89
89
  @id = id
90
90
  @count = 0
91
+ @mutex = Mutex.new
91
92
  end
92
93
 
93
94
  def unsafe_click
94
- LockAndCache::LOG_MUTEX.synchronize do
95
+ @mutex.synchronize do
95
96
  # puts "clicking bar #{@id} - #{$clicking.to_a} - #{$clicking.include?(@id)} - #{@id == $clicking.to_a[0]}"
96
97
  raise "somebody already clicking Bar #{@id}" if $clicking.include?(@id)
97
98
  $clicking << @id
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lock_and_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 4.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seamus Abshere
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-04 00:00:00.000000000 Z
11
+ date: 2016-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -172,6 +172,7 @@ files:
172
172
  - lib/lock_and_cache/key.rb
173
173
  - lib/lock_and_cache/version.rb
174
174
  - lock_and_cache.gemspec
175
+ - spec/lock_and_cache/key_spec.rb
175
176
  - spec/lock_and_cache_spec.rb
176
177
  - spec/spec_helper.rb
177
178
  homepage: https://github.com/seamusabshere/lock_and_cache
@@ -199,6 +200,7 @@ signing_key:
199
200
  specification_version: 4
200
201
  summary: Lock and cache methods.
201
202
  test_files:
203
+ - spec/lock_and_cache/key_spec.rb
202
204
  - spec/lock_and_cache_spec.rb
203
205
  - spec/spec_helper.rb
204
206
  has_rdoc: