class Chef::RunLock

Chef::RunLock

Provides an interface for acquiring and releasing a system-wide exclusive lock.

Used by Chef::Client to ensure only one instance of chef-client (or solo) is modifying the system at a time.

Attributes

mutex[R]
runlock[R]
runlock_file[R]

Public Class Methods

new(lockfile) click to toggle source

Create a new instance of RunLock

Arguments

  • :lockfile:

    the full path to the lockfile.

# File lib/chef/run_lock.rb, line 46
def initialize(lockfile)
  @runlock_file = lockfile
  @runlock = nil
  @mutex = nil
  @runpid = nil
end

Public Instance Methods

acquire() click to toggle source

Acquire the system-wide lock. Will block indefinitely if another process already has the lock and Chef::Config is not set. Otherwise will block for Chef::Config seconds and exit if the lock is not acquired.

Each call to acquire should have a corresponding call to release.

The implementation is based on File#flock (see also: flock(2)).

Either acquire() or test() methods should be called in order to get the ownership of run_lock.

# File lib/chef/run_lock.rb, line 64
def acquire
  if timeout_given?
    begin
      Timeout.timeout(time_to_wait) do
        unless test
          if time_to_wait > 0.0
            wait
          else
            exit_from_timeout
          end
        end
      end
    rescue Timeout::Error
      exit_from_timeout
    end
  else
    wait unless test
  end
end
acquire_lock() click to toggle source

@api private solely for race condition tests

# File lib/chef/run_lock.rb, line 140
def acquire_lock
  if ChefUtils.windows?
    acquire_win32_mutex
  else
    # If we support FD_CLOEXEC, then use it.
    # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not
    # ruby-1.8.7/1.9.3
    if Fcntl.const_defined?(:F_SETFD) && Fcntl.const_defined?(:FD_CLOEXEC)
      runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC)
    end
    # Flock will return 0 if it can acquire the lock otherwise it
    # will return false
    if runlock.flock(File::LOCK_NB | File::LOCK_EX) == 0
      true
    # Target mode does not have run locks, because concurrency is intended
    elsif Chef::Config.target_mode?
      true
    else
      false
    end
  end
end
create_lock() click to toggle source

@api private solely for race condition tests

# File lib/chef/run_lock.rb, line 133
def create_lock
  # ensure the runlock_file path exists
  create_path(File.dirname(runlock_file))
  @runlock = File.open(runlock_file, "a+")
end
release() click to toggle source

Release the system-wide lock.

# File lib/chef/run_lock.rb, line 117
def release
  if runlock
    if ChefUtils.windows?
      mutex.release
    else
      runlock.flock(File::LOCK_UN)
    end
    runlock.close
    # Don't unlink the pid file, if another chef-client was waiting, it
    # won't be recreated. Better to leave a "dead" pid file than not have
    # it available if you need to break the lock.
    reset
  end
end
save_pid() click to toggle source
# File lib/chef/run_lock.rb, line 107
def save_pid
  runlock.truncate(0)
  runlock.rewind # truncate doesn't reset position to 0.
  runlock.write(Process.pid.to_s)
  # flush the file fsync flushes the system buffers
  # in addition to ruby buffers
  runlock.fsync
end
test() click to toggle source

Tests and if successful acquires the system-wide lock. Returns true if the lock is acquired, false otherwise.

Either acquire() or test() methods should be called in order to get the ownership of run_lock.

# File lib/chef/run_lock.rb, line 90
def test
  create_lock
  acquire_lock
end
wait() click to toggle source

Waits until acquiring the system-wide lock.

# File lib/chef/run_lock.rb, line 98
def wait
  Chef::Log.warn("#{ChefUtils::Dist::Infra::PRODUCT} #{runpid} is running, will wait for it to finish and then run.")
  if ChefUtils.windows?
    mutex.wait
  else
    runlock.flock(File::LOCK_EX)
  end
end

Private Instance Methods

acquire_win32_mutex() click to toggle source

Since flock mechanism doesn’t exist on windows we are using platform Mutex. We are creating a “Global” mutex here so that non-admin users can not DoS chef-client by creating the same named mutex we are using locally. Mutex name is case-sensitive contrary to other things in windows. “" is the only invalid character.

# File lib/chef/run_lock.rb, line 178
def acquire_win32_mutex
  @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.tr("\\", "/").downcase}")
  mutex.test
end
exit_from_timeout() click to toggle source
# File lib/chef/run_lock.rb, line 195
def exit_from_timeout
  rp = runpid
  release # Just to be on the safe side...
  raise Chef::Exceptions::RunLockTimeout.new(time_to_wait, rp)
end
reset() click to toggle source
# File lib/chef/run_lock.rb, line 165
def reset
  @runlock = nil
  @mutex = nil
  @runpid = nil
end
runpid() click to toggle source
# File lib/chef/run_lock.rb, line 183
def runpid
  @runpid ||= runlock.read.strip
end
time_to_wait() click to toggle source
# File lib/chef/run_lock.rb, line 191
def time_to_wait
  Chef::Config[:run_lock_timeout]
end
timeout_given?() click to toggle source
# File lib/chef/run_lock.rb, line 187
def timeout_given?
  !time_to_wait.nil?
end