Java - locking a file for exclusive access - java

My problem is this: I'm using a WatchService to get notified about new files in a specific folder, now if a file gets moved/copied or created in said folder an event gets triggered and the name of the new file gets returned. The problem now is, if I try to access the file and it is not fully there yet (e.g. the copy is still in progress) an exception gets raised. What i tried was to do something like this:
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
FileChannel fc = raf.getChannel();
FileLock lck = fc.lock();
But even if a lock gets acquired, sometimes still an Exception gets raised if I try to write to the file because another process has still an open handle to it.
Now, how can a file in Java be locked for truly exclusive access?

For me, the statement
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
returns a FileNotFoundException if I cannot acquire a lock on the file. I catch the filenotfound exception and treat it...
public static boolean isFileLocked(String filename) {
boolean isLocked=false;
RandomAccessFile fos=null;
try {
File file = new File(filename);
if(file.exists()) {
fos=new RandomAccessFile(file,"rw");
}
} catch (FileNotFoundException e) {
isLocked=true;
}catch (Exception e) {
// handle exception
}finally {
try {
if(fos!=null) {
fos.close();
}
}catch(Exception e) {
//handle exception
}
}
return isLocked;
}
you could run this in a loop and wait until you get a lock on the file. Wouldn't the line
FileChannel fc = raf.getChannel();
never reach if the file is locked? You will get a FileNotFoundException thrown..

its better not to use classes in thejava.io package, instead use the java.nio package .
The latter has a FileLock class that you can use for a lock to a FileChannel.
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();
/*
use channel.lock OR channel.tryLock();
*/
// 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 - if it is not null!
if( lock != null ) {
lock.release();
}
// Close the file
channel.close();
} catch (Exception e) {
}

Related

FileLock in Java doesn't work in Docker mount volume

In my situation, some web configuration files are shared through mount folder in Docker. In same cases we want to modify these files concurrently. That's why I want to use lock to make sure file is being modified once at the same time. But I found flock is not working in Docker. Does it not supported?
public void modifyFile() {
try {
File file = new File("/tmp/fileToLock.dat");
// Creates a random access file stream to read from, and optionally to write to
FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
// Acquire an exclusive lock on this channel's file (blocks until lock can be retrieved)
FileLock lock = null;
// Attempts to acquire an exclusive lock on this channel's file (returns null or throws
// an exception if the file is already locked.
try {
lock = channel.tryLock();
if (null != lock) {
List<String> fileToString = FileUtils.readLines(file, StandardCharsets.UTF_8);
long l = 0l;
if (null != fileToString && fileToString.size() > 0) {
l = Long.valueOf(fileToString.get(fileToString.size() - 1));
}
l++;
FileUtils.writeStringToFile(file, String.valueOf(l) + "\r\n", StandardCharsets.UTF_8, true);
}
} catch (OverlappingFileLockException e) {
// thrown when an attempt is made to acquire a lock on a a file that overlaps
// a region already locked by the same JVM or when another thread is already
// waiting to lock an overlapping region of the same file
System.out.println("Overlapping File Lock Error: " + e.getMessage());
channel.close();
}
// release the lock
if (null != lock) {
lock.release();
}
// close the channel
channel.close();
} catch (IOException e) {
System.out.println("I/O Error: " + e.getMessage());
}
}
In the end, I just leave any lock implementation for file without any Redis lock thing.

read/write to the same file from multiple docker containers

I have 4 containers(java app) in 4 docker hosts. they need to read/write to the same file.
file lock cannot be used because of different OS.
So, I tried to create a .lock file, if one of the 4 containers created the .lock file, the other containers will have to wait.
But this still isn't working well. The other containers cannot see the .lock file created by other containers sometimes(not real-time).
Are there other solutions?
I suggest you rethink your assumptions:
What if you have not 4 but 400 containers?
What if they are on servers not sharing a file system?
The clean way to do this is to write a very basic server (if the load allows it, this can be nginx+PHP and be done in 10 minutes) that does the file writing, run this in another container and connect to it from the other containers. This will give you:
file locking easy and reliable, as the file is seen only by one server
scalability
clusterability
abstraction
Try File lock api to implement this. Demo in Java.
public void modifyFile() {
try {
File file = new File("/tmp/fileToLock.dat");
// Creates a random access file stream to read from, and optionally to write to
FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
// Acquire an exclusive lock on this channel's file (blocks until lock can be retrieved)
FileLock lock = null;
// Attempts to acquire an exclusive lock on this channel's file (returns null or throws
// an exception if the file is already locked.
try {
lock = channel.tryLock();
if (null != lock) {
List<String> fileToString = FileUtils.readLines(file, StandardCharsets.UTF_8);
long l = 0l;
if (null != fileToString && fileToString.size() > 0) {
l = Long.valueOf(fileToString.get(fileToString.size() - 1));
}
l++;
FileUtils.writeStringToFile(file, String.valueOf(l) + "\r\n", StandardCharsets.UTF_8, true);
}
} catch (OverlappingFileLockException e) {
// thrown when an attempt is made to acquire a lock on a a file that overlaps
// a region already locked by the same JVM or when another thread is already
// waiting to lock an overlapping region of the same file
System.out.println("Overlapping File Lock Error: " + e.getMessage());
channel.close();
}
// release the lock
if (null != lock) {
lock.release();
}
// close the channel
channel.close();
} catch (IOException e) {
System.out.println("I/O Error: " + e.getMessage());
}
}

Disappointment when trying to lock file for reading

I'm trying to make each thread read its own text file.
private void burden() {
File mainFolder = new File("C:\\FilesToRead");
File[] files = mainFolder.listFiles();
String freeFile;
for (File file : files) {
FileChannel channel;
FileLock lock = null;
try {
channel = new RandomAccessFile(file, "r").getChannel();;
lock = channel.lock();
// Ok. We get the lock
readFile(file.getName());
} catch (OverlappingFileLockException e) {
continue; // File is open by someone else
} catch (FileNotFoundException f) {
} catch (IOException ex) {
} catch (NonWritableChannelException n) {
System.out.println("NonWritableChannelException");
} finally {
try {
lock.release();
} catch (IOException ex) {
System.out.println("IOException!");
}
}
}
} // burden();
I get this picture in the debugger:
The next step will bring me to the NonWritableChannelException.
I can't understand why as I tried to lock file for reading.
The javadoc gives the answer. You use .lock(), which is equivalent to calling .lock(0L, Long.MAX_VALUE, false).
And the third parameter, a boolean, is described as (emphasis mine):
true to request a shared lock, in which case this channel must be open for reading (and possibly writing); false to request an exclusive lock, in which case this channel must be open for writing (and possibly reading)
You have to .lock(0L, Long.MAX_VALUE, true)
Also, if you are using Java 7, use FileChannel.open()
you also need to lock the writable channel here so make your code,
channel = new RandomAccessFile(file, "rw").getChannel();
This should work.

Writing data to file is erasing everything before it(Placing 00's) and changing the file size

I have a program which writes out some data- I am using this logic
FileOutputStream outpre = new FileOutputStream(getfile());
FileChannel ch = outpre.getChannel();
ch.position(startwr);
ch.write(ByteBuffer.wrap(dsave));
outpre.close();
It writes out the correct data to the correct location, the only problem is that everything before the starting location to write (startwr) gets replaced with 00's, and the file is also changed making the point at which the writing was done, the end of the file.
How can I write data to the file without corrupting the previous data and changing the file size?
You need to instruct the stream to either append or overwrite the contents of the file...
Take a look at FileOutpuStream(File, boolean) for more details
Updated
After some mucking around, I found that the only solution I could get close to working was...
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(new File("C:/Test.txt"), "rw");
raf.seek(3);
raf.writeBytes("BB");
} catch (IOException exp) {
exp.printStackTrace();
} finally {
try {
raf.close();
} catch (Exception e) {
}
}
You can fix it this way
FileChannel fc = FileChannel.open(getFile().toPath(), StandardOpenOption.APPEND);

Is there a way to catch exceptions without breaking loop in Java?

In Java, there's a difference between a loop surrounded with a try-catch block if an exception could be thrown inside the while loop, and a statement surrounded by a try-catch block inside a loop.
For instance, the following code snippets are different:
Snippet 1:
try {
for (File file : files) {
FileInputStream fis = new FileInputStream(file);
System.out.println("OK!");
}
}
catch (FileNotFoundException exc) {
System.out.println("Error!");
}
^This code snippet breaks the loop if a FileNotFoundException is thrown. So if a file cannot be read, then the loop breaks and Java will stop reading further files.
Snippet 2:
for (File file : files) {
try {
FileInputStream fis = new FileInputStream(file);
System.out.println("OK!");
}
catch (FileNotFoundException exc) {
System.out.println("Error!");
}
}
^This code snippet does not break the loop if an exception is thrown, if an exception occurs, the code catches the exception and continues to the next element in files. With other words, it won't stop reading the files.
Now I want to read a certain file in a directory (say bananas.xml), and, unregarded if that file is readable or not—the XML file is a metadata file, which might not be required for the program to run—, read the corresponding directory (which is bananas):
File main = new File("/home/MCEmperor/test");
File fruitMeta = new File(main, "bananas.xml");
FileInputStream fruitInputStream = new FileInputStream(fruitMeta); // This code COULD throw a FileNotFoundException
// Do something with the fruitInputStream...
File fruitDir = new File(main, "bananas");
if (fruitDir.exists() && fruitDir.canRead()) {
File[] listBananas = fruitDir.listFiles();
for (File file : listBananas) {
FileInputStream fis = new FileInputStream(file); // This code COULD throws a FileNotFoundException
// Do something with the fis...
}
}
Now two lines in the snippet above may throw a FileNotFoundException and I don't want to break the loop.
Now is there a way to make one try-catch block with catches both lines if an exception is thrown, but without breaking the for-loop?
How about something like this?
FileInputStream fruitInputStream = getFileInputStream(fruitMeta);
...
fis = getFileInputStream(file);
private static FileInputStream getFileInputStream(File file) {
try {
return new FileInputStream(file);
catch(FileNotFoundException e) {
return null;
}
}

Categories

Resources