Implementing a simple lock file system in Ruby
2009/09/08 // Leeds // // Feed
Lock Files are great for monitoring when a resource is in use, Wikipedia explains it simply:
File locking is often used by shell scripts and other programs: creation of lock files, which are files whose contents are irrelevant (although often one will find the Process identifier of the holder of the lock in the file) and whose only purpose is to signal by their presence that some resource is locked. A lock file is often the best approach if the resource to be controlled is not a regular file at all, so using methods for locking files does not apply. - Wikipedia
Recently whilst working on a project i needed to implement some way of monitoring when a particular task was being executed, ensuring that a duplicate task wasn’t invoked (consuming more resources and possibly hindering the execution of the current task). Ruby (and pretty much any other programming language) provides a whole host of file manipulation classes to easily implement a simple lock file system.
With Ruby we can use the File class to manipulate files, ensuring a platform independent way of reading/writing to/from the filesystem. The actual class methods that we’ll be using within the File class come from the mixin of the ftools module (which adds a group of methods for copying, moving, deleting, installing, and comparing files).
Heres the code:
# lock_file.rb
# git://github.com/joshnesbitt/lockfile.git
class LockFile
attr_accessor :path, :filename, :qualified_path
def initialize(path="/tmp", filename="lockfile.lock")
@path, @filename = path, filename
end
def path
@path
end
def filename
@filename
end
def qualified_path
"#{@path}/#{@filename}"
end
def process_id
locked? ? read_lockfile(self.qualified_path) : nil
end
def lock!
locked? ? false : create_lockfile(self.qualified_path)
end
def unlock!
unlocked? ? false : destroy_lockfile(self.qualified_path)
end
def locked?
lockfile_exists?(self.qualified_path)
end
def unlocked?
!lockfile_exists?(self.qualified_path)
end
protected
def lockfile_exists?(file)
File.exists?(file)
end
def create_lockfile(lockfile)
begin
File.open(lockfile, "w") { |f| f.write(Process.pid) }
rescue
raise LockFileExists
end
end
def read_lockfile(lockfile)
begin
File.open(lockfile, "r").gets { |l| puts l }
rescue
raise LockFileMissing
end
end
def destroy_lockfile(lockfile)
begin
File.delete(lockfile)
rescue
raise LockFileMissing
end
end
class FileError < StandardError #:nodoc:
end
class LockFileExists < FileError #:nodoc:
end
class LockFileMissing < FileError #:nodoc:
end
end
This is a very basic implementation as i didn’t need anything uber fancy. Use it like this:
l = LockFile.new("/path/to/lockfile", "test.lock")
# => "/path/to/lockfile/test.lock" containing the PID
l.locked?
# => false
l.lock!
# => Integer value
l.locked?
# => true
l.process_id
# => PID
l.unlock!
# => Integer value
l.locked?
# => false
To put it into context a bit more, imagine you’re doing something like this within your application:
def do_something
call_rake_task
end
When hit this method will call a Rake task from within your application, lets not worry about why you might be doing this for now but focus on how we ensure this task isn’t invoked again if it’s already being executed:
def do_something
call_rake_task unless @lockfile.locked?
end
Where you initialize the lock file instance is up to you, i ended up creating it in the initializer of the class i was using it in to make sure it was available as soon as the class was instantiated.
There are lots of uses for lock files, for me this was just one quick solution to a problem. Also its important to note that there are other solutions to performing expensive tasks outside of a typical request (most of which are better than using plain old lock files), you might want to look at these too:
Bonza!