class Bosh::Director::Lock

Distributed lock backed by DB.

Public Class Methods

new(name, opts = {}) click to toggle source

Creates new lock with the given name.

@param name lock name @option opts [Number] timeout how long to wait before giving up @option opts [Number] expiration how long to wait before expiring an old

lock
# File lib/bosh/director/lock.rb, line 15
def initialize(name, opts = {})
  @name = name
  @uid = SecureRandom.uuid
  @timeout = opts[:timeout] || 1.0
  @expiration = opts[:expiration] || 10.0
  @logger = Config.logger
  @refresh_thread = nil
end

Public Instance Methods

lock() { || ... } click to toggle source

Acquire a lock.

@yield [void] optional block to do work before automatically releasing

the lock.

@return [void]

# File lib/bosh/director/lock.rb, line 29
def lock
  acquire

  @refresh_thread = Thread.new do
    sleep_interval = [1.0, @expiration/2].max
    begin
      stopped = false
      until stopped
        @logger.debug("Renewing lock: #@name")
        lock_expiration = Time.now.to_f + @expiration + 1

        if Models::Lock.where(name: @name, uid: @uid).update(expired_at: Time.at(lock_expiration)) == 0
          stopped = true
        end

        sleep(sleep_interval) unless stopped
      end
    ensure
      @logger.debug("Lock renewal thread exiting")
    end
  end

  if block_given?
    begin
      yield
    ensure
      release
    end
  end
end
release() click to toggle source

Release a lock that was not auto released by the lock method.

@return [void]

# File lib/bosh/director/lock.rb, line 63
def release
  @refresh_thread.exit if @refresh_thread
  delete
end

Private Instance Methods

acquire() click to toggle source
# File lib/bosh/director/lock.rb, line 70
def acquire
  @logger.debug("Acquiring lock: #@name")
  started = Time.now

  lock_expiration = Time.now.to_f + @expiration + 1
  acquired = false
  until acquired
    begin
      Models::Lock.create(name: @name, uid: @uid, expired_at: Time.at(lock_expiration))
      acquired = true
    rescue Sequel::DatabaseError
      affected_locks = Models::Lock.where(name: @name).where{ expired_at < Time.now }.update(uid: @uid, expired_at: Time.at(lock_expiration))
      if affected_locks == 1
        acquired = true
      end
    end

    unless acquired
      raise TimeoutError, "Failed to acquire lock for #{@name} uid: #{@uid}" if Time.now - started > @timeout
      sleep(0.5)
      lock_expiration = Time.now.to_f + @expiration + 1
    end
  end

  @lock_expiration = lock_expiration
  @logger.debug("Acquired lock: #@name")
end
delete() click to toggle source
# File lib/bosh/director/lock.rb, line 99
def delete
  if Models::Lock.where(name: @name, uid: @uid).delete > 0
    @logger.debug("Deleted lock: #{@name} uid: #{@uid}")
  else
    @logger.debug("Can not find lock: #{@name} - uid: #{@uid}")
  end
end
lock_expired?(lock_record) click to toggle source
# File lib/bosh/director/lock.rb, line 107
def lock_expired?(lock_record)
  lock_record.expired_at < Time.now
end