Locking a File

Problem

You want to prevent other threads or processes from modifying a file that you're working on.

Solution

Open the file, then lock it with File#flock. There are two kinds of lock; pass in the File constant for the kind you want.

  • File::LOCK_EX gives you an exclusive lock, or write lock. If your thread has an exclusive lock on a file, no other thread or process can get a lock on that file. Use this when you want to write to a file without anyone else being able to write to it.
  • File::LOCK_SH will give you a shared lock, or read lock. Other threads and processes can get their own shared locks on the file, but no one can get an exclusive lock. Use this when you want to read a file and know that it won't change while you're reading it.

Once you're done using the file, you need to unlock it. Call File#flock again, and pass in File::LOCK_UN as the lock type. You can skip this step if you're running on Windows.

The best way to handle all this is to enclose the locking and unlocking in a method that takes a block, the way open does:

	 def flock(file, mode)
	 success = file.flock(mode)
	 if success
	 begin
	 yield file
	 ensure
	 file.flock(File::LOCK_UN)
	 end
	 end
	 return success
	end

This makes it possible to lock a file without having to worry about unlocking it later. Even if your block raises an exception, the file will be unlocked and another thread can use it.

	open('output', 'w') do |f|
	 flock(f, File::LOCK_EX) do |f|
	 f << "Kiss me, I've got a write lock on a file!"
	 end
	end

 

Discussion

Different operating systems support different ways of locking files. Ruby's flock implementation tries to hide the differences behind a common interface that looks like Unix's file locking interface. In general, you can use flock as though you were on Unix, and your scripts will work across platforms.

On Unix, both exclusive and shared locks work only if all threads and processes play by the rules. If one thread has an exclusive lock on a file, another thread can still open the file without locking it and wreak havoc by overwriting its contents. That's why it's important to get a lock on any file that might conceivably be used by another thread or another process on the system.

Ruby's block-oriented coding style makes it easy to do the right thing with locking. The following shortcut method works with the flock method previously defined. It takes care of opening, locking, unlocking, and closing a file, letting you focus on whatever you want to do with the file's contents.

	def open_lock(filename, openmode="r", lockmode=nil)
	 if openmode == 'r' || openmode == 'rb'
	 lockmode ||= File::LOCK_SH
	 else
	 lockmode ||= File::LOCK_EX
	 end
	 value = nil
	 open(filename, openmode) do |f|
	 flock(f, lockmode) do
	 begin
	 value = yield f
	 ensure
	 f.flock(File::LOCK_UN) # Comment this line out on Windows.
	 end
	 end
	 return value
	 end
	end

This code creates two threads, each of which want to access the same file. Thanks to locks, we can guarantee that only one thread is accessing the file at a time (see Chapter 20 if you're not comfortable with threads).

	t1 = Thread.new do
	 puts 'Thread 1 is requesting a lock.'
	 open_lock('output', 'w') do |f|
	 puts 'Thread 1 has acquired a lock.'
	 f << "At last we're alone!"
	 sleep(5)
	 end
	 
	 puts 'Thread 1 has released its lock.'
	end
	
	t2 = Thread.new do
	 puts 'Thread 2 is requesting a lock.'
	 open_lock('output', 'r') do |f|
	 puts 'Thread 2 has acquired a lock.'
	 puts "File contents: #{f.read}"
	 end
	 puts 'Thread 2 has released its lock.'
	 end
	 t1.join
	 t2.join
	 # Thread 1 is requesting a lock.
	 # Thread 1 has acquired a lock.
	 # Thread 2 is requesting a lock.
	 # Thread 1 has released its lock.
	 # Thread 2 has acquired a lock.
	 # File contents: At last we're alone!
	 # Thread 2 has released its lock.

 

Nonblocking locks

If you try to get an exclusive or shared lock on a file, your thread will block until Ruby can lock the file. But you might be left waiting a long time, perhaps forever. The code that has the file locked may be buggy and in an infinite loop; or it may itself be blocking, waiting to lock a file that you have locked.

You can avoid deadlock and similar problems by asking for a nonblocking lock. When you do, if Ruby can't lock the file for you, File#flock returns false, rather than waiting (possibly forever) for another thread or process to release its lock. If you don't get a lock, you can wait a while and try again, or you can raise an exception and let the user deal with it.

To make a lock into a nonblocking lock, use the OR operator (|) to combine File:: LOCK_NB with either File::LOCK_EX or File::LOCK_SH.

The following code will print "I've got a lock!" if it can get an exclusive lock on the file "output"; otherwise it will print "I couldn't get a lock." and continue:

	def try_lock
	 puts "I couldn't get a lock." unless
	 open_lock('contested', 'w', File::LOCK_EX | File::LOCK_NB) do
	 puts "I've got a lock!"
	 true
	 end
	end
	
	try_lock
	# I've got a lock!
	
	open('contested', 'w').flock(File::LOCK_EX) # Get a lock, hold it forever.
	try_lock
	# I couldn't get a lock.

 

See Also

  • Chapter 20, especially Recipe 20.11, "Avoiding Deadlock," which covers other types of deadlock problems in a multithreaded environment


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net