lock_and_cache 3.0.1 → 4.0.4

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