module Buryspam::Lockfile

The Lockfile module can be used to prevent multiple processes from reading/writing the same file simultaneously. Its usage is straightforward:

Lockfile.open(filename) { |f|
  # Do some stuff with f
}

The lock will automatically be released after the block completes or if there is an exception in the block. By default, shared (read) locks are granted, but exclusive (write) locks can also be requested.

The Lockfile module can also be used to prevent the same program from being started more than once.

Public Class Methods

only_one() { |file| ... } click to toggle source

Prevent two or more similar buryspam operations from being done at the same time. For example, only one --init can be done at a time. Also, --transfer, --poll and --bulk are all transfer operations so only one of them can be done at a time.

# File buryspam.rb, line 689
def only_one
  @lockfile = invoker + "-pid.lock"
  begin
    @lockfile = File.expand_path(@lockfile, File.dirname(Config.word_file))
    File.open(@lockfile, File::CREAT | File::EXCL | File::WRONLY) { |file|
      write_pid_host(file)
      yield(file)
    }
  rescue Errno::EEXIST
    # We need to set @lockfile to nil to prevent the 'ensure' clause
    # below from removing the lockfile.
    lock = @lockfile
    @lockfile = nil
    pid, host = IO.binread(lock).split("/")
    raise AlreadyLockedError.new(pid.to_i),
      "According to lockfile:\n " +
      lock.to_s +
      "\na conflicting process may already be running on " +
      "'#{host}' (PID ##{pid}).\n\n" +
      "If '#{host}' recently crashed or if you are not running any\n" +
      "buryspam processes on '#{host}' then remove the lockfile and retry."
  ensure
    if ! @lockfile.nil?
      Logger.debug("Removing #{@lockfile} lock file.")
      FileUtils.rm_f(@lockfile)
    end
  end
end
open(filename, typ = File::LOCK_SH, timeout = Config.lock_timeout) { |file| ... } click to toggle source

Lock the given filename with the specified type of lock for the specified amount of time. The file will be created if it doesn't already exist. The lock is held until the given block supplied to this method finishes or the lock times out, raising an ExpireError exception. If a nonblocking lock is requested, AlreadyLockedError will be raised if the file is already locked. If timeout is nil, then a nonblocking lock is implicitly requested.

# File buryspam.rb, line 643
def open(filename, typ = File::LOCK_SH, timeout = Config.lock_timeout)
  mode, ntyp = (typ & File::LOCK_SH > 0) ?
                   %w(r shared) :
                   %w(a exclusive)
  func = invoker
  locked = false
  if typ & File::LOCK_NB > 0 || timeout.nil?
    ntyp << " (nonblocking)"
    typ |= typ & File::LOCK_NB
    timeout = nil
  end

  file = File.open(filename, mode)
  # Disable encoding conversion and treat input as ASCII-8BIT
  # (for ruby version > 1.9)
  file.binmode
  begin
    # Don't wait forever for a lock and don't let anyone hold
    # a lock for too long (unless timeout is nil).
    Timeout.timeout(timeout) {
      Logger.debug(func) { "Waiting for #{ntyp} lock on '#{filename}'..." }
      # flock returns false immediately if lock is nonblocking
      # and it can't get lock.
      raise AlreadyLockedError unless file.flock(typ)
      locked = true
      Logger.debug(func) { "Have #{ntyp} lock on '#{filename}'" }
      yield file
    }
  rescue Timeout::Error
    err_str = "Lock timeout on '#{filename}'"
    Logger.debug(func) { err_str }
    raise ExpireError, err_str
  ensure
    if locked
      Logger.debug(func) { "Release lock on '#{filename}'..." }
      file.flock(File::LOCK_UN)
      Logger.debug(func) { "Unlocking complete for '#{filename}'" }
    end
    file.close
  end
end
write_pid_host(f) click to toggle source

Store the process id and hostname in the lockfile. When in 'polling' mode, this method will be called again after daemonization in order to store the new pid of the child process, hence the rewind/truncate.

# File buryspam.rb, line 721
def write_pid_host(f)
  f.rewind
  f.write("%s/%s" % [$$, Startup::HOSTNAME])
  f.flush
  f.truncate(f.pos)
end