Class: WithAdvisoryLock::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/with_advisory_lock/base.rb

Direct Known Subclasses

Flock, MySQL, PostgreSQL

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, lock_name, options) ⇒ Base

Returns a new instance of Base.



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/with_advisory_lock/base.rb', line 26

def initialize(connection, lock_name, options)
  options = { timeout_seconds: options } unless options.respond_to?(:fetch)
  options.assert_valid_keys :timeout_seconds, :shared, :transaction, :disable_query_cache

  @connection = connection
  @lock_name = lock_name
  @timeout_seconds = options.fetch(:timeout_seconds, nil)
  @shared = options.fetch(:shared, false)
  @transaction = options.fetch(:transaction, false)
  @disable_query_cache = options.fetch(:disable_query_cache, false)
end

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection.



24
25
26
# File 'lib/with_advisory_lock/base.rb', line 24

def connection
  @connection
end

#disable_query_cacheObject (readonly)

Returns the value of attribute disable_query_cache.



24
25
26
# File 'lib/with_advisory_lock/base.rb', line 24

def disable_query_cache
  @disable_query_cache
end

#lock_nameObject (readonly)

Returns the value of attribute lock_name.



24
25
26
# File 'lib/with_advisory_lock/base.rb', line 24

def lock_name
  @lock_name
end

#sharedObject (readonly)

Returns the value of attribute shared.



24
25
26
# File 'lib/with_advisory_lock/base.rb', line 24

def shared
  @shared
end

#timeout_secondsObject (readonly)

Returns the value of attribute timeout_seconds.



24
25
26
# File 'lib/with_advisory_lock/base.rb', line 24

def timeout_seconds
  @timeout_seconds
end

#transactionObject (readonly)

Returns the value of attribute transaction.



24
25
26
# File 'lib/with_advisory_lock/base.rb', line 24

def transaction
  @transaction
end

Class Method Details

.lock_stackObject



46
47
48
49
# File 'lib/with_advisory_lock/base.rb', line 46

def self.lock_stack
  # access doesn't need to be synchronized as it is only accessed by the current thread.
  Thread.current[:with_advisory_lock_stack] ||= []
end

Instance Method Details

#already_locked?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/with_advisory_lock/base.rb', line 52

def already_locked?
  lock_stack.include? lock_stack_item
end

#lock_and_yield(&block) ⇒ Object



66
67
68
69
70
71
72
73
74
# File 'lib/with_advisory_lock/base.rb', line 66

def lock_and_yield(&block)
  if already_locked?
    Result.new(true, yield)
  elsif timeout_seconds == 0
    yield_with_lock(&block)
  else
    yield_with_lock_and_timeout(&block)
  end
end

#lock_stack_itemObject



42
43
44
# File 'lib/with_advisory_lock/base.rb', line 42

def lock_stack_item
  @lock_stack_item ||= LockStackItem.new(lock_str, shared)
end

#lock_strObject



38
39
40
# File 'lib/with_advisory_lock/base.rb', line 38

def lock_str
  @lock_str ||= "#{ENV['WITH_ADVISORY_LOCK_PREFIX']}#{lock_name}"
end

#stable_hashcode(input) ⇒ Object



76
77
78
79
80
81
82
83
84
# File 'lib/with_advisory_lock/base.rb', line 76

def stable_hashcode(input)
  if input.is_a? Numeric
    input.to_i
  else
    # Ruby MRI's String#hash is randomly seeded as of Ruby 1.9 so
    # make sure we use a deterministic hash.
    Zlib.crc32(input.to_s, 0)
  end
end

#unique_column_nameObject

Prevent AR from caching results improperly



114
115
116
# File 'lib/with_advisory_lock/base.rb', line 114

def unique_column_name
  "t#{SecureRandom.hex}"
end

#with_advisory_lock_if_needed(&block) ⇒ Object



56
57
58
59
60
61
62
63
64
# File 'lib/with_advisory_lock/base.rb', line 56

def with_advisory_lock_if_needed(&block)
  if disable_query_cache
    return lock_and_yield do
      ActiveRecord::Base.uncached(&block)
    end
  end

  lock_and_yield(&block)
end

#yield_with_lockObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/with_advisory_lock/base.rb', line 98

def yield_with_lock
  if try_lock
    begin
      lock_stack.push(lock_stack_item)
      result = block_given? ? yield : nil
      Result.new(true, result)
    ensure
      lock_stack.pop
      release_lock
    end
  else
    FAILED_TO_LOCK
  end
end

#yield_with_lock_and_timeout(&block) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/with_advisory_lock/base.rb', line 86

def yield_with_lock_and_timeout(&block)
  give_up_at = Time.now + @timeout_seconds if @timeout_seconds
  while @timeout_seconds.nil? || Time.now < give_up_at
    r = yield_with_lock(&block)
    return r if r.lock_was_acquired?

    # Randomizing sleep time may help reduce contention.
    sleep(rand(0.05..0.15))
  end
  FAILED_TO_LOCK
end