Class: Redlock::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/redlock/client.rb,
lib/redlock/testing.rb

Defined Under Namespace

Classes: RedisInstance

Constant Summary collapse

DEFAULT_REDIS_HOST =
ENV["DEFAULT_REDIS_HOST"] || "localhost"
DEFAULT_REDIS_PORT =
ENV["DEFAULT_REDIS_PORT"] || "6379"
DEFAULT_REDIS_URLS =
["redis://#{DEFAULT_REDIS_HOST}:#{DEFAULT_REDIS_PORT}"]
DEFAULT_REDIS_TIMEOUT =
0.1
DEFAULT_RETRY_COUNT =
3
DEFAULT_RETRY_DELAY =
200
DEFAULT_RETRY_JITTER =
50
CLOCK_DRIFT_FACTOR =
0.01

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(servers = DEFAULT_REDIS_URLS, options = {}) ⇒ Client

Create a distributed lock manager implementing redlock algorithm. Params:

servers

The array of redis connection URLs or Redis connection instances. Or a mix of both.

options
  • ‘retry_count` being how many times it’ll try to lock a resource (default: 3)

  • ‘retry_delay` being how many ms to sleep before try to lock again (default: 200)

  • ‘retry_jitter` being how many ms to jitter retry delay (default: 50)

  • ‘redis_timeout` being how the Redis timeout will be set in seconds (default: 0.1)

  • ‘time_source` being a callable object returning a monotonic time in milliseconds

    (default: see #default_time_source)
    


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/redlock/client.rb', line 38

def initialize(servers = DEFAULT_REDIS_URLS, options = {})
  redis_timeout = options[:redis_timeout] || DEFAULT_REDIS_TIMEOUT
  @servers = servers.map do |server|
    if server.is_a?(String)
      RedisInstance.new(url: server, timeout: redis_timeout)
    else
      RedisInstance.new(server)
    end
  end
  @quorum = (servers.length / 2).to_i + 1
  @retry_count = options[:retry_count] || DEFAULT_RETRY_COUNT
  @retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
  @retry_jitter = options[:retry_jitter] || DEFAULT_RETRY_JITTER
  @time_source = options[:time_source] || self.class.default_time_source
end

Class Attribute Details

.testing_modeObject

Returns the value of attribute testing_mode.



6
7
8
# File 'lib/redlock/testing.rb', line 6

def testing_mode
  @testing_mode
end

Class Method Details

.default_time_sourceObject

Returns default time source function depending on CLOCK_MONOTONIC availability.



20
21
22
23
24
25
26
# File 'lib/redlock/client.rb', line 20

def self.default_time_source
  if defined?(Process::CLOCK_MONOTONIC)
    proc { (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i }
  else
    proc { (Time.now.to_f * 1000).to_i }
  end
end

Instance Method Details

#get_remaining_ttl_for_lock(lock_info) ⇒ Object

Gets remaining ttl of a resource. The ttl is returned if the holder currently holds the lock and it has not expired, otherwise the method returns nil. Params:

lock_info

the lock that has been acquired when you locked the resource



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

def get_remaining_ttl_for_lock(lock_info)
  ttl_info = try_get_remaining_ttl(lock_info[:resource])
  return nil if ttl_info.nil? || ttl_info[:value] != lock_info[:value]
  ttl_info[:ttl]
end

#get_remaining_ttl_for_resource(resource) ⇒ Object

Gets remaining ttl of a resource. If there is no valid lock, the method returns nil. Params:

resource

the name of the resource (string) for which to check the ttl



125
126
127
128
129
# File 'lib/redlock/client.rb', line 125

def get_remaining_ttl_for_resource(resource)
  ttl_info = try_get_remaining_ttl(resource)
  return nil if ttl_info.nil?
  ttl_info[:ttl]
end

#lock(resource, ttl, options = {}, &block) ⇒ Object

Locks a resource for a given time. Params:

resource

the resource (or key) string to be locked.

ttl

The time-to-live in ms for the lock.

options

Hash of optional parameters

* +retry_count+: see +initialize+
* +retry_delay+: see +initialize+
* +retry_jitter+: see +initialize+
* +extend+: A lock ("lock_info") to extend.
* +extend_only_if_locked+: Boolean, if +extend+ is given, only acquire lock if currently held
* +extend_only_if_life+: Deprecated, same as +extend_only_if_locked+
* +extend_life+: Deprecated, same as +extend_only_if_locked+
block

an optional block to be executed; after its execution, the lock (if successfully

acquired) is automatically unlocked.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/redlock/client.rb', line 68

def lock(resource, ttl, options = {}, &block)
  lock_info = try_lock_instances(resource, ttl, options)
  if options[:extend_only_if_life] && !Gem::Deprecate.skip
    warn 'DEPRECATION WARNING: The `extend_only_if_life` option has been renamed `extend_only_if_locked`.'
    options[:extend_only_if_locked] = options[:extend_only_if_life]
  end
  if options[:extend_life] && !Gem::Deprecate.skip
    warn 'DEPRECATION WARNING: The `extend_life` option has been renamed `extend_only_if_locked`.'
    options[:extend_only_if_locked] = options[:extend_life]
  end

  if block_given?
    begin
      yield lock_info
      !!lock_info
    ensure
      unlock(lock_info) if lock_info
    end
  else
    lock_info
  end
end

#lock!(resource, *args) ⇒ Object

Locks a resource, executing the received block only after successfully acquiring the lock, and returning its return value as a result. See Redlock::Client#lock for parameters.



101
102
103
104
105
106
107
108
# File 'lib/redlock/client.rb', line 101

def lock!(resource, *args)
  fail 'No block passed' unless block_given?

  lock(resource, *args) do |lock_info|
    raise LockError, resource unless lock_info
    return yield
  end
end

#locked?(resource) ⇒ Boolean

Checks if a resource is locked Params:

lock_info

the lock that has been acquired when you locked the resource

Returns:

  • (Boolean)


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

def locked?(resource)
  ttl = get_remaining_ttl_for_resource(resource)
  !(ttl.nil? || ttl.zero?)
end

#testing_mode=(mode) ⇒ Object



9
10
11
12
13
14
15
# File 'lib/redlock/testing.rb', line 9

def testing_mode=(mode)
  warn 'DEPRECATION WARNING: Instance-level `testing_mode` has been removed, and this ' +
    'setter will be removed in the future. Please set the testing mode on the `Redlock::Client` ' +
    'instead, e.g. `Redlock::Client.testing_mode = :bypass`.'

  self.class.testing_mode = mode
end

#try_lock_instances(resource, ttl, options) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/redlock/client.rb', line 224

def try_lock_instances(resource, ttl, options)
  retry_count = options[:retry_count] || @retry_count
  tries = options[:extend] ? 1 : (retry_count + 1)

  tries.times do |attempt_number|
    # Wait a random delay before retrying.
    sleep(attempt_retry_delay(attempt_number, options)) if attempt_number > 0

    lock_info = lock_instances(resource, ttl, options)
    return lock_info if lock_info
  end

  false
end

#try_lock_instances_without_testingObject



17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/redlock/testing.rb', line 17

def try_lock_instances(resource, ttl, options)
  retry_count = options[:retry_count] || @retry_count
  tries = options[:extend] ? 1 : (retry_count + 1)

  tries.times do |attempt_number|
    # Wait a random delay before retrying.
    sleep(attempt_retry_delay(attempt_number, options)) if attempt_number > 0

    lock_info = lock_instances(resource, ttl, options)
    return lock_info if lock_info
  end

  false
end

#unlock(lock_info) ⇒ Object

Unlocks a resource. Params:

lock_info

the lock that has been acquired when you locked the resource.



94
95
96
# File 'lib/redlock/client.rb', line 94

def unlock(lock_info)
  @servers.each { |s| s.unlock(lock_info[:resource], lock_info[:value]) }
end

#unlock_without_testingObject

Unlocks a resource. Params:

lock_info

the lock that has been acquired when you locked the resource.



33
34
35
# File 'lib/redlock/testing.rb', line 33

def unlock(lock_info)
  @servers.each { |s| s.unlock(lock_info[:resource], lock_info[:value]) }
end

#valid_lock?(lock_info) ⇒ Boolean

Checks if a lock is still valid Params:

lock_info

the lock that has been acquired when you locked the resource

Returns:

  • (Boolean)


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

def valid_lock?(lock_info)
  ttl = get_remaining_ttl_for_lock(lock_info)
  !(ttl.nil? || ttl.zero?)
end