Module: LockAndCache

Defined in:
lib/lock_and_cache.rb,
lib/lock_and_cache/key.rb,
lib/lock_and_cache/action.rb,
lib/lock_and_cache/version.rb

Overview

Lock and cache using redis!

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?

Defined Under Namespace

Classes: TimeoutWaitingForLock

Constant Summary collapse

DEFAULT_MAX_LOCK_WAIT =

1 day in seconds

60 * 60 * 24
DEFAULT_HEARTBEAT_EXPIRES =

32 seconds

32
VERSION =
'3.0.0'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.clear(*key_parts) ⇒ Object

Note:

Standalone mode. See also “context mode,” where you mix LockAndCache into a class and call it from within its methods.

Clear a single key



62
63
64
65
# File 'lib/lock_and_cache.rb', line 62

def LockAndCache.clear(*key_parts)
  key = LockAndCache::Key.new key_parts
  key.clear
end

.flushObject

Note:

If you are sharing a redis database, it will clear it…

Note:

If you want to clear a single key, try ‘LockAndCache.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.

Flush LockAndCache’s storage.



40
41
42
# File 'lib/lock_and_cache.rb', line 40

def LockAndCache.flush
  storage.flushdb
end

.heartbeat_expires=(seconds) ⇒ Object

Note:

Can be overridden by putting ‘heartbeat_expires:` in your call to `#lock_and_cache`

Parameters:

  • seconds (Numeric)

    How often a process has to heartbeat in order to keep a lock



90
91
92
93
94
# File 'lib/lock_and_cache.rb', line 90

def LockAndCache.heartbeat_expires=(seconds)
  memo = seconds.to_f
  raise "heartbeat_expires must be greater than 2 seconds" unless memo >= 2
  @heartbeat_expires = memo
end

.lock_and_cache(*key_parts_and_options, &blk) ⇒ Object

Note:

Standalone mode. See also “context mode,” where you mix LockAndCache into a class and call it from within its methods.

Note:

A single hash arg is treated as a cache key, e.g. ‘LockAndCache.lock_and_cache(foo: :bar, expires: 100)` will be treated as a cache key of `foo: :bar, expires: 100` (which is probably wrong!!!). Try `LockAndCache.lock_and_cache({ foo: :bar }, expires: 100)` instead. This is the opposite of context mode.

Lock and cache based on a key.

Parameters:

  • key_parts (*)

    Parts that should be used to construct a key.



51
52
53
54
55
56
57
# File 'lib/lock_and_cache.rb', line 51

def LockAndCache.lock_and_cache(*key_parts_and_options, &blk)
  options = (key_parts_and_options.last.is_a?(Hash) && key_parts_and_options.length > 1) ? key_parts_and_options.pop : {}
  raise "need a cache key" unless key_parts_and_options.length > 0
  key = LockAndCache::Key.new key_parts_and_options
  action = LockAndCache::Action.new key, options, blk
  action.perform
end

.locked?(*key_parts) ⇒ Boolean

Note:

Standalone mode. See also “context mode,” where you mix LockAndCache into a class and call it from within its methods.

Check if a key is locked

Returns:

  • (Boolean)


70
71
72
73
# File 'lib/lock_and_cache.rb', line 70

def LockAndCache.locked?(*key_parts)
  key = LockAndCache::Key.new key_parts
  key.locked?
end

.max_lock_wait=(seconds) ⇒ Object

Note:

Can be overridden by putting ‘max_lock_wait:` in your call to `#lock_and_cache`

Parameters:

  • seconds (Numeric)

    Maximum wait time to get a lock



78
79
80
# File 'lib/lock_and_cache.rb', line 78

def LockAndCache.max_lock_wait=(seconds)
  @max_lock_wait = seconds.to_f
end

.storageRedis

Returns The redis connection used for lock and cached value storage.

Returns:

  • (Redis)

    The redis connection used for lock and cached value storage



31
32
33
# File 'lib/lock_and_cache.rb', line 31

def LockAndCache.storage
  @storage
end

.storage=(redis_connection) ⇒ Object

Parameters:

  • redis_connection (Redis)

    A redis connection to be used for lock and cached value storage



24
25
26
27
28
# File 'lib/lock_and_cache.rb', line 24

def LockAndCache.storage=(redis_connection)
  raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
  @storage = redis_connection
  @lock_manager = Redlock::Client.new [redis_connection], retry_count: 1
end

Instance Method Details

#lock_and_cache(*key_parts_and_options, &blk) ⇒ Object

Note:

Subject mode - this is expected to be called on an object whose class has LockAndCache mixed in. See also standalone mode.

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.

Lock and cache a method given key parts.

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.

Parameters:

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

Returns:

  • The cached value (possibly newly calculated).



133
134
135
136
137
138
# File 'lib/lock_and_cache.rb', line 133

def lock_and_cache(*key_parts_and_options, &blk)
  options = key_parts_and_options.last.is_a?(Hash) ? key_parts_and_options.pop : {}
  key = LockAndCache::Key.new key_parts_and_options, context: self, caller: caller
  action = LockAndCache::Action.new key, options, blk
  action.perform
end

#lock_and_cache_clear(method_id, *key_parts) ⇒ Object

Note:

Subject mode - this is expected to be called on an object whose class has LockAndCache mixed in. See also standalone mode.

Clear a lock and cache given exactly the method and exactly the same arguments



117
118
119
120
# File 'lib/lock_and_cache.rb', line 117

def lock_and_cache_clear(method_id, *key_parts)
  key = LockAndCache::Key.new key_parts, context: self, method_id: method_id
  key.clear
end

#lock_and_cache_locked?(method_id, *key_parts) ⇒ Boolean

Note:

Subject mode - this is expected to be called on an object whose class has LockAndCache mixed in. See also standalone mode.

Check if a method is locked on an object.

Returns:

  • (Boolean)


109
110
111
112
# File 'lib/lock_and_cache.rb', line 109

def lock_and_cache_locked?(method_id, *key_parts)
  key = LockAndCache::Key.new key_parts, context: self, method_id: method_id
  key.locked?
end