/*
* This file or a portion of this file is licensed under the terms of
* the Globus Toolkit Public License, found in file GTPL, or at
* http://www.globus.org/toolkit/download/license.html. This notice must
* appear in redistributions of this file, with or without modification.
*
* Redistributions of this Software, with or without modification, must
* reproduce the GTPL in: (1) the Software, or (2) the Documentation or
* some other similar material which is provided with the Software (if
* any).
*
* Copyright 1999-2004 University of Chicago and The University of
* Southern California. All rights reserved.
*/
package org.griphyn.vdl.util;
import org.griphyn.vdl.util.*;
import java.util.*;
import java.io.*;
/**
* This class implements file locking using system-provided lock
* functions. On unix, these may most likely include POSIX fcntl locking
* calls. While such lock may on some systems be NFS-safe, networked
* file locking on Linux is notoriously broken, and cannot be assumed to
* work as expected.<p>
*
* All access to the files must go through the respective open and close
* methods provided by this class in order to guarantee proper locking!
*
* @author Jens-S. Vöckler
* @author Yong Zhao
* @version $Revision$
*
* @see FcntlLock
* @see java.io.File
*/
public class FcntlFileLock extends FileHelper
{
/**
* File channel lock of database.
*/
private FcntlLock m_lock_db;
/**
* File channel lock of nr file.
*/
private FcntlLock m_lock_nr;
/**
* Number memory for updates on close.
*/
private int m_current;
/**
* Remembers, if this instance is already in use.
*/
private boolean m_active;
/**
* Primary ctor: obtain access to a database cycle via database.
* @param database is the base name of the database without the
* digit suffix.
*/
public FcntlFileLock( String database )
{
super(database);
this.m_lock_db = this.m_lock_nr = null;
this.m_current = -1;
this.m_active = false;
}
/**
* Opens a reader for the database, adjusting the database cycles.
* The access can be shared with other simultaneous readers. The
* locks on number file and database are held shared. They are
* released in the close operation.
*
* @return a reader opened to the database, or null for failure.
* @see #closeReader( File )
*/
public synchronized File openReader()
{
File result = null;
// sanity check -- don't reopen without close
if ( m_active ) return result;
// lock number file shared
// read contents of number file, if available
int number = -1;
if ( m_number.exists() ) {
try {
m_lock_nr = new FcntlLock( m_number, false, true );
} catch ( IOException e ) {
Logging.instance().log( "lock", 0, "while creating lock on " +
m_number.getPath() + ": " + e.getMessage() );
return result;
}
// read number from number file
number = readCount();
} else {
// if the number file does not exit, DO NOT create it (yet)
Logging.instance().log( "lock", 2, "number file " + m_number.getPath() +
" does not exist, ignoring lock" );
}
// remember number
m_current = number;
// database file
File database = new File( number == -1 ?
m_database :
m_database + '.' + Integer.toString(number) );
// lock and open database
if ( database.exists() ) {
try {
m_lock_db = new FcntlLock( database, false, true );
} catch ( IOException e ) {
Logging.instance().log( "lock", 0, "while creating lock on " +
database.getPath() + ": " + e.getMessage() );
return result;
}
} else {
Logging.instance().log( "lock", 1, "database file " +
database.getPath() + " does not exist, ignoring" );
}
result = database;
// exit condition: Both, number file and database are locked using
// a shared (read) lock.
m_active = ( result != null );
return result;
}
/**
* Opens a writer for the database, adjusting the database cycles
* The access is exclusive, and cannot be shared with readers nor
* writers. Both locks are exclusive, and held through the close
* operation.
*
* @return a writer opened for the database, or null for failure.
* @see #closeWriter( File )
*/
public synchronized File openWriter()
{
File result = null;
// sanity check -- don't reopen without close
if ( m_active ) return result;
// lock number file exclusively
// read contents of number file, if available
int number = -1;
if ( m_number.exists() ) {
try {
m_lock_nr = new FcntlLock( m_number, true, false );
} catch ( IOException e ) {
Logging.instance().log( "lock", 0, "while creating lock on " +
m_number.getPath() + ": " + e.getMessage() );
return result;
}
// read number from number file
number = readCount();
} else {
// if the number file does not exit, DO NOT create it (yet)
Logging.instance().log( "lock", 2, "number lock " + m_number.getPath() +
" does not exist, ignoring" );
}
// generate new file number to write to
m_current = number = (number + 1) % 10;
// generate database filename
File database = new File( m_database + '.' + Integer.toString(number) );
// lock and open database for exclusive access
try {
m_lock_db = new FcntlLock( database, true, false );
} catch ( IOException e ) {
Logging.instance().log( "lock", 0, "while creating lock on " +
database.getPath() + ": " + e.getMessage() );
return result;
}
// valid result
result = database;
m_active = true;
// exit condition: Both, database and number file are locked, using
// an exclusive lock.
return result;
}
/**
* Closes a previously obtained reader, and releases internal locking
* resources.
*
* @param r is the reader that was created by its open operation.
* @return true, if unlocking went smoothly, or false in the presence
* of an error.
* @see #openReader()
*/
public synchronized boolean closeReader( File r )
{
boolean result = false;
// sanity check
if ( ! m_active ) return false;
result = true;
// done with database
if ( m_lock_db != null ) m_lock_db.done();
// done with nr file; however, it may not have existed
if ( m_lock_nr != null ) m_lock_nr.done();
m_active = false;
return result;
}
/**
* Closes a previously obtained writer, and releases internal
* locking resources.
*
* @param w is the instance that was returned by its open operation.
* @return true, if the closing went smoothly, false in the presence
* of an error.
* @see #openWriter()
*/
public synchronized boolean closeWriter( File w )
{
boolean result = false;
// sanity check
if ( ! m_active ) return result;
// done with database
m_lock_db.done();
// update number file
result = writeCount(m_current);
// release shared lock on number file
if ( m_lock_nr != null ) m_lock_nr.done();
m_active = false;
return result;
}
}