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.
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
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
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