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)
    


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

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.



4
5
6
# File 'lib/redlock/testing.rb', line 4

def testing_mode
  @testing_mode
end

Class Method Details

.default_time_sourceObject

Returns default time source function depending on CLOCK_MONOTONIC availability.



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

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

#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

* +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.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/redlock/client.rb', line 63

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.



96
97
98
99
100
101
102
103
# File 'lib/redlock/client.rb', line 96

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

#testing_mode=(mode) ⇒ Object



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

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



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/redlock/client.rb', line 188

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

  tries.times do |attempt_number|
    # Wait a random delay before retrying.
    sleep(attempt_retry_delay(attempt_number)) 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



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

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

  tries.times do |attempt_number|
    # Wait a random delay before retrying.
    sleep(attempt_retry_delay(attempt_number)) 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.



89
90
91
# File 'lib/redlock/client.rb', line 89

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.



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

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