views:

3085

answers:

6

I have a cluster of machines, each running a Java app.

These Java apps need to access a unique resource.txt file concurently.

I need to atomically rename a temp.txt file to resource.txt in Java, even if resource.txt already exist.

Deleting resource.txt and renaming temp.txt doesn't work, as it's not atomic (it creates a small timeframe where resource.txt doesn't exist).

And it should be cross-platform...

Thanks !

+6  A: 

On Linux (and I believe Solaris and other UNIX operating systems), Java's File.renameTo() method will overwrite the destination file if it exists, but this is not the case under Windows.

To be cross platform, I think you'd have to use file locking on resource.txt and then overwrite the data.

Something like here.

The behavior of the file lock is platform-dependent. On some platforms, the file lock is advisory, which means that unless an application checks for a file lock, it will not be prevented from accessing the file. On other platforms, the file lock is mandatory, which means that a file lock prevents any application from accessing the file.

try {
    // Get a file channel for the file
    File file = new File("filename");
    FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

    // Use the file channel to create a lock on the file.
    // This method blocks until it can retrieve the lock.
    FileLock lock = channel.lock();

    // Try acquiring the lock without blocking. This method returns
    // null or throws an exception if the file is already locked.
    try {
        lock = channel.tryLock();
    } catch (OverlappingFileLockException e) {
        // File is already locked in this thread or virtual machine
    }

    // Release the lock
    lock.release();

    // Close the file
    channel.close();
} catch (Exception e) {
}

Linux, by defualt, uses voluntary locking, while Windows enforces it. Maybe you could detect the OS, and use renameTo() under UNIX with some locking code for Windows?

There's also a way to turn on mandatory locking under Linux for specific files, but it's kind of obscure. You have to set the mode bits just right.

Linux, following System V (see System V Interface Definition (SVID) Version 3), lets the sgid bit for files without group execute permission mark the file for mandatory locking

Stephen Pape
+2  A: 

Here is a discussion that relates: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4017593

TofuBeer
+1  A: 

If this should be cross-platform I suggest 2 options:

  1. Implement an intermediate service that is responsible for all the file accesses. Here you can use several mechanisms for synchronizing the requests. Each client java app accesses the file only through this service.
  2. Create a control file each time you need to perform synchronized operations. Each java app that accesses the file is responsible checking for the control file and waiting while this control file exists. (almost like a semaphore). The process doing the delete/rename operation is responsible for creating/deleting the control file.
bruno conde
What about the user deleting the file by hand outside of Java? You cannot control that all file access goes through the Java service.
TofuBeer
+1  A: 

As stated here, it looks like the Windows OS doesn't even support atomic file rename for older versions. It's very likely you have to use some manual locking mechanisms or some kind of transactions. For that, you might want to take a look into the apache commons transaction package.

MicSim
+1  A: 

If the purpose of the rename is to replace resource.txt on the fly and you have control over all the programs involved, and the frequency of replacement is not high, you could do the following.

To open/read the file:

  1. Open "resource.txt", if that fails
  2. Open "resource.old.txt", if that fails
  3. Open "resource.txt" again, if that fails
  4. You have an error condition.

To replace the file:

  1. Rename "resource.txt" to "resource.old.txt", then
  2. Rename "resource.new.txt" to "resource.txt", then
  3. Delete "resource.old.txt".

Which will ensure all your readers always find a valid file.

But, easier, would be to simply try your opening in a loop, like:

InputStream inp=null;
StopWatch   tmr=new StopWatch();                     // made up class, not std Java
IOException err=null;

while(inp==null && tmr.elapsed()<5000) {             // or some approp. length of time
    try { inp=new FileInputStream("resource.txt"); }
    catch(IOException thr) { err=thr; sleep(100); }  // or some approp. length of time
    }

if(inp==null) {
     // handle error here - file did not turn up after required elapsed time
     throw new IOException("Could not obtain data from resource.txt file");
     }

... carry on
Software Monkey
A: 

You might get some traction by establishing a filechannel lock on the file before renaming it (and deleting the file you're going to overwrite once you have the lock). -r

rogerdpack