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 =
"7.0.0"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cache_storageRedis

Returns The redis connection used for cached value storage.

Returns:

  • (Redis)

    The redis connection used for cached value storage



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

def LockAndCache.cache_storage
  @cache_storage
end

.cache_storage=(redis_connection) ⇒ Object

Parameters:

  • redis_connection (Redis)

    A redis connection to be used for cached value storage



35
36
37
38
# File 'lib/lock_and_cache.rb', line 35

def LockAndCache.cache_storage=(redis_connection)
  raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
  @cache_storage = redis_connection
end

.cached?(*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 cached already

Returns:

  • (Boolean)


107
108
109
110
# File 'lib/lock_and_cache.rb', line 107

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

.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



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

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

.flush_cacheObject

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 cached value storage.



60
61
62
# File 'lib/lock_and_cache.rb', line 60

def LockAndCache.flush_cache
  cache_storage.flushdb
end

.flush_locksObject

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 lock storage.



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

def LockAndCache.flush_locks
  lock_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



127
128
129
130
131
# File 'lib/lock_and_cache.rb', line 127

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.



80
81
82
83
84
85
86
# File 'lib/lock_and_cache.rb', line 80

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

.lock_storageRedis

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

Returns:

  • (Redis)

    The redis connection used for lock and cached value storage



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

def LockAndCache.lock_storage
  @lock_storage
end

.lock_storage=(redis_connection) ⇒ Object

Parameters:

  • redis_connection (Redis)

    A redis connection to be used for lock storage



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

def LockAndCache.lock_storage=(redis_connection)
  raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
  @lock_storage = redis_connection
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)


99
100
101
102
# File 'lib/lock_and_cache.rb', line 99

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

.loggerLogger

Returns The logger.

Returns:

  • (Logger)

    The logger.



51
52
53
# File 'lib/lock_and_cache.rb', line 51

def LockAndCache.logger
  @logger
end

.logger=(logger) ⇒ Object

Parameters:

  • logger (Logger)

    A logger.



46
47
48
# File 'lib/lock_and_cache.rb', line 46

def LockAndCache.logger=(logger)
  @logger = logger
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



115
116
117
# File 'lib/lock_and_cache.rb', line 115

def LockAndCache.max_lock_wait=(seconds)
  @max_lock_wait = seconds.to_f
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).



165
166
167
168
169
170
# File 'lib/lock_and_cache.rb', line 165

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



149
150
151
152
# File 'lib/lock_and_cache.rb', line 149

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)


141
142
143
144
# File 'lib/lock_and_cache.rb', line 141

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