class PEROBS::LockFile

This class implements a file based lock. It can only be taken by one process at a time. It support configurable lock lifetime, maximum retries and pause between retries.

Public Class Methods

new(file_name, options = {}) click to toggle source

Create a new lock for the given file. @param file_name [String] file name of the lock file @param options [Hash] See case statement

# File lib/perobs/LockFile.rb, line 40
def initialize(file_name, options = {})
  @file_name = file_name
  # The handle of the lock file
  @file = nil
  # The maximum duration after which a lock file is considered a left-over
  # from a dead or malefunctioning process.
  @timeout_secs = 60 * 60
  # The maximum number of times we try to get the lock.
  @max_retries = 5
  # The time we wait between retries
  @pause_secs = 1

  options.each do |name, value|
    case name
    when :timeout_secs
      @timeout_secs = value
    when :max_retries
      @max_retries = value
    when :pause_secs
      @pause_secs = value
    else
      PEROBS.log.fatal "Unknown option #{name}"
    end
  end
end

Public Instance Methods

forced_unlock() click to toggle source

Erase the lock file. It’s essentially a forced unlock method.

# File lib/perobs/LockFile.rb, line 149
def forced_unlock
  @file = nil
  if File.exist?(@file_name)
    begin
      File.delete(@file_name)
      PEROBS.log.debug "Lock file #{@file_name} has been deleted."
    rescue IOError => e
      PEROBS.log.error "Cannot delete lock file #{@file_name}: " +
        e.message
    end
  end
end
is_locked?() click to toggle source

Check if the lock has been taken. @return [Boolean] true if taken, false otherweise.

# File lib/perobs/LockFile.rb, line 121
def is_locked?
  File.exist?(@file_name)
end
lock() click to toggle source

Attempt to take the lock. @return [Boolean] true if lock was taken, false otherwise

# File lib/perobs/LockFile.rb, line 68
def lock
  retries = @max_retries
  while retries > 0
    begin
      @file = File.open(@file_name, File::RDWR | File::CREAT, 0644)
      @file.sync = true

      if @file.flock(File::LOCK_EX | File::LOCK_NB)
        # We have taken the lock. Write the PID into the file and leave it
        # open.
        @file.write($$)
        @file.flush
        @file.fsync
        @file.truncate(@file.pos)
        PEROBS.log.debug "Lock file #{@file_name} has been taken for " +
          "process #{$$}"

        return true
      else
        # We did not manage to take the lock file.
        if @file.mtime <= Time.now - @timeout_secs
          pid = @file.read.to_i
          PEROBS.log.info "Old lock file found for PID #{pid}. " +
            "Removing lock."
          if is_running?(pid)
            send_signal('TERM', pid)
            # Give the process 3 seconds to terminate gracefully.
            sleep 3
            # Then send a SIGKILL to ensure it's gone.
            send_signal('KILL', pid) if is_running?(pid)
          end
          @file.close
          File.delete(@file_name) if File.exist?(@file_name)
        else
          PEROBS.log.debug "Lock file #{@file_name} is taken. Trying " +
            "to get it #{retries} more times."
        end
      end
    rescue => e
      PEROBS.log.error "Cannot take lock file #{@file_name}: #{e.message}"
      return false
    end

    retries -= 1
    sleep(@pause_secs)
  end

  PEROBS.log.info "Failed to get lock file #{@file_name} due to timeout"
  false
end
unlock() click to toggle source

Release the lock again.

# File lib/perobs/LockFile.rb, line 126
def unlock
  unless @file
    PEROBS.log.error "There is no current lock to release"
    return false
  end

  begin
    @file.flock(File::LOCK_UN)
    @file.fsync
    @file.close
    forced_unlock
    PEROBS.log.debug "Lock file #{@file_name} for PID #{$$} has been " +
      "released"
  rescue => e
    PEROBS.log.error "Releasing of lock file #{@file_name} failed: " +
      e.message
    return false
  end

  true
end

Private Instance Methods

is_running?(pid) click to toggle source
# File lib/perobs/LockFile.rb, line 172
def is_running?(pid)
  begin
    Process.getpgid(pid)
    true
  rescue Errno::ESRCH
    false
  end
end
send_signal(name, pid) click to toggle source
# File lib/perobs/LockFile.rb, line 164
def send_signal(name, pid)
  begin
    Process.kill(name, pid)
  rescue => e
    PEROBS.log.info "Process kill error: #{e.message}"
  end
end