filelock.rb 0.2 (c) C.Hintze 23mar2000 This module provides two classes to deal with lockfiles. Instances of LockFile represents real lockfiles. Mostly these file have the extension ".lock". If the instance is not any longer needed, it will delete the lockfile during finalization. Instances of LockedFile provides a possibility to open a certain file and handle the lockfile mechanism transparent for the user. If a LockedFile is not any longer needed it will be closed and the corresponding LockFile instance will be removed during finalization. What is it? =========== Sometimes it is not possible to use fctl mechanism to lock/unlock files. May it be that fctl doesn't properly work on a given UNIX, may it be that your file resides on a mounted filesystem. UNIX mail delivery system itself use a lock mechanism via lockfiles. For every file I want to lock a corresponding .lock file will be created. If such a lockfile already exists, than the process have to wait until the lock was released (lockfile deleted) or have to steal the lock to have access on that certain file. Why you may need it? ==================== If, for example, you want to process your system mailbox it may happen that while you are reading your mailbox, a new mail arrives. While you reading your mailbox there is probably no problem. But if you also want to write your mailbox this may create a big desaster. Here you can use the LockedFile wrapper object. If you obtain the lock and then process your mailbox you can be sure that in this very moment no write access from your mail delivery system will occur, until you close that file and therefore release the lock. See the function test2 at the bottom of the file 'test.rb' to see how you can do it. How to install? =============== Very simple. First you have to unpack the filelock-x.y.tar.gz file. Then you have to perform: ruby extconf.rb make make install If you want to test your module after installation you could perform ruby lib/filelock.rb then a small testsuite will be performed. Algorithm: ========== If there is no other lock, the lockfile will be created. Otherwise, it must be decided whether the lock should be stolen or not. If the lock should not be stolen, the process will sleep for 'sleep' seconds and then check, whether the existant lock is vanished or not. If not it sleeps again for 'sleep' seconds, ... etc. This all would be done for 'retry' times or forever if 'retry' is nil. If the lock should be stolen, then the process will first sleeps for 'steal' seconds. Then the lockfile of the other process will be removed. After that, the process again sleeps 'suspend' seconds, to give the process from which the lock was stolen from, the possibility to retain its lock. If the other process has not retained its lock, the lockfile will be created. That all would be done for 'retry' times or forever if 'retry' is nil. If the lock could not be obtained after 'retry' retries, an AcquireLockError will be thrown. Additonal Features: =================== The methods LockedFile::open and LockFile::create_for are able to deal with blocks like File::open does. LockedFile::open will hands over the reference to the LockedFile instance as parameter to the block and execute it. After executing, the LockedFile will be closed and the lock will be released. WARNING: You should not reopen the file within the block. Doing so will raise a FrozenLockError. LockFile::create_for will hands over the reference to a stolen-lock-checker lambda. You can invoke it with .call. It will return true, if the lock was stolen in the meantime; false otherwise. After the block is finished, the lock will be released if it was not stolen in the meantime. WARNING: You may unlock the lock explicity within the block, but you cannot obtain it again. Trying to do so will result in a FrozenLockError exception raised. Examples: ========= 1. Read in a whole UNIX mailbox. During the read-in, no new mail can be delivered by the UNIX mailsystem. contents = LockedFile::open(mailbox) { |mb| mb.read() } 2. Another way to safety deal with your mailbox: LockFile::create_for(mailbox) do |not_ok| fd = open(mailbox, "r+") ... # Do what you want with it. Filter mails, delete them ... # It's up to you! But don't forget to call the checker ... # regulary with not_ok.call fd.close end Here is a concrete but useless example: require "filelock" require "mailread" FileLock::LockFile::create_for(ARGV[0]) do |not_ok| fp = open(ARGV[0]) until fp.eof? or not_ok.call mail = Mail.new(fp) print "Subj=#{mail.header['Subject']}\n" sleep 1 end print "Lock was stolen!\n" if not_ok.call fp.close end But much more safety would be the following (NOT POSSIBLE USING Ruby 1.1c9!): require "filelock" require "mailread" fp = FileLock::LockedFile::open(ARGV[0]) begin until fp.eof? mail = Mail(fp) print "Subj=#{mail.header['Subject']}\n" sleep 1 end rescue FileLock::StolenLockError print "Lock was stolen!\n" ensure fp.close end Here the fact, that the lock was stolen, will be recognized on the soonest time possible, whereas in the previous example, it will be only recognized during execution of until! That perhaps could be too late!!! Copyright notice ================ This source is copyrighted, but you can freely use and copy it as long as you don't change or remove the copyright notice: Now coming to the warranty. There is no warranty! Never and in no case ;-) I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. To-do: - Improve the speed, although I don't know how :-} - Use directories alternatively for files (mkdir is atomic)? Bugs: - Slow compared with Ruby's file objects, as on every access it has to been checked, whether the lock is still valid yet. I have used the Python version of that module for some time without encoutering any problem. As Ruby is very new for me, and I have to adapt certain things to Ruby's way of doing, I don't know, if I have all done correct. So certainly there should be some errors. So please let me know. mailto:c.hintze@gmx.net Describe what you have tried to do, and the contents of the version strings contained in the constant 'Revision' on top of the file 'filelock.rb' and '_filelock.c'. Please do not forget to attach an example, where the error occurs to enable me to reproduce the error. Changes: 0.2 Distribution include an extension written in C. It realizes the createLock method as C function. This to minimize the time between existence check of file and file creation. Due to the new _filelock.c the distribution will now be delivered as .tar.gz file. It should be installed as every other distribution using 'mkmf.rb'. 0.1 Creation