Module: LockAndCacheMsgpack

Defined in:
lib/lock_and_cache_msgpack.rb,
lib/lock_and_cache_msgpack/key.rb,
lib/lock_and_cache_msgpack/action.rb,
lib/lock_and_cache_msgpack/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: Action, Key, TimeoutWaitingForLock

Constant Summary collapse

DEFAULT_MAX_LOCK_WAIT =

1 day in seconds

60 * 60 * 24
DEFAULT_HEARTBEAT_EXPIRES =

32 seconds

32
VERSION =
'4.1.0'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cached?(*key_parts) ⇒ Boolean

Note:

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

Check if a key is cached already

Returns:

  • (Boolean)


95
96
97
98
# File 'lib/lock_and_cache_msgpack.rb', line 95

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

.clear(*key_parts) ⇒ Object

Note:

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

Clear a single key



79
80
81
82
# File 'lib/lock_and_cache_msgpack.rb', line 79

def LockAndCacheMsgpack.clear(*key_parts)
  key = LockAndCacheMsgpack::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 ‘LockAndCacheMsgpack.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.

Flush LockAndCacheMsgpack’s storage.



57
58
59
# File 'lib/lock_and_cache_msgpack.rb', line 57

def LockAndCacheMsgpack.flush
  storage.flushdb
end

.heartbeat_expiresObject



122
123
124
# File 'lib/lock_and_cache_msgpack.rb', line 122

def LockAndCacheMsgpack.heartbeat_expires
  @heartbeat_expires || DEFAULT_HEARTBEAT_EXPIRES
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



115
116
117
118
119
# File 'lib/lock_and_cache_msgpack.rb', line 115

def LockAndCacheMsgpack.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 LockAndCacheMsgpack into a class and call it from within its methods.

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.

Lock and cache based on a key.

Parameters:

  • key_parts (*)

    Parts that should be used to construct a key.



68
69
70
71
72
73
74
# File 'lib/lock_and_cache_msgpack.rb', line 68

def LockAndCacheMsgpack.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 = LockAndCacheMsgpack::Key.new key_parts_and_options
  action = LockAndCacheMsgpack::Action.new key, options, blk
  action.perform
end

.lock_managerObject



127
128
129
# File 'lib/lock_and_cache_msgpack.rb', line 127

def LockAndCacheMsgpack.lock_manager
  @lock_manager
end

.locked?(*key_parts) ⇒ Boolean

Note:

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

Check if a key is locked

Returns:

  • (Boolean)


87
88
89
90
# File 'lib/lock_and_cache_msgpack.rb', line 87

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

.loggerLogger

Returns The logger.

Returns:

  • (Logger)

    The logger.



48
49
50
# File 'lib/lock_and_cache_msgpack.rb', line 48

def LockAndCacheMsgpack.logger
  @logger
end

.logger=(logger) ⇒ Object

Parameters:

  • logger (Logger)

    A logger.



43
44
45
# File 'lib/lock_and_cache_msgpack.rb', line 43

def LockAndCacheMsgpack.logger=(logger)
  @logger = logger
end

.max_lock_waitObject



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

def LockAndCacheMsgpack.max_lock_wait
  @max_lock_wait || DEFAULT_MAX_LOCK_WAIT
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



103
104
105
# File 'lib/lock_and_cache_msgpack.rb', line 103

def LockAndCacheMsgpack.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



32
33
34
35
36
37
38
39
40
# File 'lib/lock_and_cache_msgpack.rb', line 32

def LockAndCacheMsgpack.storage
  @storage ||=
    begin
      connection = @redis_connection.class == Proc ? @redis_connection.call : @redis_connection
      raise "only redis for now" unless connection.class.to_s == 'Redis'
      @lock_manager = Redlock::Client.new [connection], retry_count: 1
      connection
    end
end

.storage=(redis_connection) ⇒ Object

Parameters:

  • redis_connection (Redis || lambda)

    A redis connection to be used for lock and cached value storage. Lazy evaluated if wrapped in a lambda



27
28
29
# File 'lib/lock_and_cache_msgpack.rb', line 27

def LockAndCacheMsgpack.storage=(redis_connection)
  @redis_connection = redis_connection
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 LockAndCacheMsgpack 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).



158
159
160
161
162
163
# File 'lib/lock_and_cache_msgpack.rb', line 158

def lock_and_cache(*key_parts_and_options, &blk)
  options = key_parts_and_options.last.is_a?(Hash) ? key_parts_and_options.pop : {}
  key = LockAndCacheMsgpack::Key.new key_parts_and_options, context: self, caller: caller
  action = LockAndCacheMsgpack::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 LockAndCacheMsgpack mixed in. See also standalone mode.

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



142
143
144
145
# File 'lib/lock_and_cache_msgpack.rb', line 142

def lock_and_cache_clear(method_id, *key_parts)
  key = LockAndCacheMsgpack::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 LockAndCacheMsgpack mixed in. See also standalone mode.

Check if a method is locked on an object.

Returns:

  • (Boolean)


134
135
136
137
# File 'lib/lock_and_cache_msgpack.rb', line 134

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