Class: PEROBS::LockFile

Inherits:
Object
  • Object
show all
Defined in:
lib/perobs/LockFile.rb

Overview

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.

Instance Method Summary collapse

Constructor Details

#initialize(file_name, options = {}) ⇒ LockFile

Create a new lock for the given file.

Parameters:

  • file_name (String)

    file name of the lock file

  • options (Hash) (defaults to: {})

    See case statement



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 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

Instance Method Details

#forced_unlockObject

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



149
150
151
152
153
154
155
156
157
158
159
160
# 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?Boolean

Check if the lock has been taken.

Returns:

  • (Boolean)

    true if taken, false otherweise.



121
122
123
# File 'lib/perobs/LockFile.rb', line 121

def is_locked?
  File.exist?(@file_name)
end

#lockBoolean

Attempt to take the lock.

Returns:

  • (Boolean)

    true if lock was taken, false otherwise



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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

#unlockObject

Release the lock again.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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