I have two JVM processes running, say Process1 and Process2. Each process is running the following:
File lockFile = new File("lock");
if (!lockFile.exists()) {
lockFile.createNewFile();
}
RandomAccessFile lockRAF = new RandomAccessFile(lockFile, "rw");
FileChannel channel = lockRAF.getChannel();
FileLock fileLock;
try {
fileLock = channel.tryLock();
} catch (OverlappingFileLockException e) {
...
} ...
// do something exclusive
fileLock.release();
lockRAF.close();
lockFile.delete();
(Not showing exception handling since it is not the main point of the question).
The problem comes from deleting the file at the end where:
Process1 acquires a FileLock, does some work then releases it
Process2 acquires a lock
Process1 deletes the file
Process1 then tries to call the same code snippet again. This works because it creates a new file from which a lock is acquired.
The result is that both processes have acquired a lock which was supposed to be exclusive.
Note: this happens on Linux (Windows does not allow deleting the file when locked).
How can I solve this in a way that allows the lock file to be deleted at the end? File.deleteOnExist does not solve the issue.
Related
I'm developing disk cache (multithread). Cache can have many users at once. That is why I can write some rules:
1) Cache can't edit (or delete) file when client reads it.
2) When cache is editing file, clients should wait for the end of editing (and after read the file).
I need to organize this lock strategy with the help of java.
I read about synchronizatioan (synchronized block and java.util.concurrent.Locks) and as I understood it can't help here.
I tried to understand the FileLock. But when client reads file, cache lock can abort reading. And if clients will lock files before reading, there will be long sequence of client to read.
I need for advice how to organize it (maybe another ways).
UPDATE
public void write(InputStream is) throws FileNotFoundException, IOException {
File file = new File("path");
try (FileOutputStream fos = new FileOutputStream(file);
FileChannel filechannel = fos.getChannel();
FileLock lock = filechannel.lock(0, Long.MAX_VALUE, false)) {
// writing....
}
}
public void read(OutputStream osToClient) throws IOException {
File file = new File("path");
try (FileInputStream fis = new FileInputStream(file);
FileChannel filechannel = fis.getChannel();
FileLock lock = filechannel.lock(0, Long.MAX_VALUE, true)) {
IOUtils.copy(fis, osToClient);
}
}
You should probably not build this yourself unless you do it for fun or for a school assignment. However, except that there are warnings about portability (so it may not work the way you expect on all platforms) FileLock should do the job. When you are reading, first get a shared lock on the file, read the file and release the lock when done, ideally in a try-with-resources block. When you are writing, get an exclusive lock (shared=false) instead. There can be multiple readers but only one writer, and when there is a writer there can be no readers.
I have two java process (JAR) one is writing to a text file on every 1 min and another is reading that file and call a web service to store data in database.
Is there any way to lock the file when it is on write mode? I have observed that when wvdial is dialing a modem its create a lock file in /var/lock/ttyUSB0..LOCK I think. I want a this kind of procedure if the file is on write mode the another process could wait till write done. After writing the process can read the file content.
Please guide me to solve my issue.
Thank you
Maybe this class can help you http://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html
Edit: This post might already be covering the subject How can I lock a file using java (if possible)
Exemple:
FileInputStream in = new FileInputStream(file);
try
{
java.nio.channels.FileLock lock = in.getChannel().lock();
try
{
//write
}
finally
{
lock.release();
}
}
finally
{
in.close();
}
Now in the reading process:
FileInputStream in = new FileInputStream(file);
try
{
FileLock lock = in.getChannel().tryLock();
if (lock == null)
{
//file is locked, wait or do something else
}
else
{
try
{
//read
}
finally
{
lock.release();
}
}
}
finally
{
in.close();
}
The problem you will have here is that Java cannot open() with O_EXCL, as a result you cannot create a file atomically.
Use a directory instead: creating a directory is an atomic operation. File's .mkdir() will return false if the directory cannot be created. rmdir() it when you're done.
Of course, make sure that both of your processes have write access to the base directory!
I wrote a java application that accesses a file while other Processes in other VMs try to do the same. Therefore I use the FileLock class:
FileOutputStream fos = new FileOutputStream(filePath,append);
FileChannel f = fos.getChannel();
FileLock lock;
while ((lock = f.tryLock()) == null){
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
}
}
OutputStreamWriter out = new OutputStreamWriter( new FileOutputStream(filePath,append));
out.write(textToWrite);
out.close();
lock.release();
All works fine on Mac OSX, but when I run the code on Windows 7 it throws an IOException at the line
out.close();
, when trying to flush.
java.io.IOException: The process cannot access the file because another process has locked a portion of the file
at java.io.FileOutputStream.writeBytes(Native Method)
As far as I understand from How does FileLock work?, the actual obtaining of the lock with
f.tryLock()
forbids me to access it since another process (this one apparently) has exclusive lock.
Now that strikes me as a paradoxon - how am I to obtain an exlusive lock to enable me to write to the file without danger of other processes messing with it at the same time when the actual act of obtaining the lock hinders me to do so?
And consequently why does it work on Mac OS and not on windows? I know from the JavaDocs that there are OS specific differences and difficulties with the FileLock class, but surely not with respect to its designed-for functionality.
Since this can't be the case, I am doing something wrong and this is where I ask for your help.
Thx,
M
There is no file locking on UNIX.: http://www.coderanch.com/t/551144/java/java/File-lock-doesn-prevent-threads. In fact, on UNIX, you can delete a file from under a process and it may not even notice...
So you need to use a lock file that you can check exists.
Paradoxically your code is working on Windows but not on UNIX (i.e. Mac OS), the exception should be the expected result of trying to write to a file that is locked by another process.
So I try to locked the file to read it, but I got IOException, any idea why?
public static void main(String[] args){
File file = new File("C:\\dev\\harry\\data.txt");
FileReader fileReader = null;
BufferedReader bufferedReader = null;
FileChannel channel = null;
FileLock lock = null;
try{
channel = new RandomAccessFile(file, "rw").getChannel();
lock = channel.lock();
fileReader = new FileReader(file);
bufferedReader = new BufferedReader(fileReader);
String data;
while((data = bufferedReader.readLine()) != null){
System.out.println(data);
}
}catch(IOException e){
e.printStackTrace();
}finally{
try {
lock.release();
channel.close();
if(bufferedReader != null) bufferedReader.close();
if(fileReader != null) fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
and I got this error IOException: The process cannot access the file because another process has locked a portion of the file
Might as well add this as an answer instead of a comment.
If you use the FileLock API you need to use the corresponding NIO file apis.
Reproducing my answer from here (in case it gets deleted), and adding Jeff Foster's feedback:
Considering that an instance of the OverlappingFileLockException exception is thrown, it appears that another thread in the same process is attempting to lock on the same file. This is not a conflict between A and B, but rather a conflict within B, if one goes by the API documentation on the lock() method and when the condition under which it throws OverlappingFileLockException:
If a lock that overlaps the requested
region is already held by this Java
virtual machine, or if another thread
is already blocked in this method and
is attempting to lock an overlapping
region of the same file
The only solution to prevent this, is to have any other thread in B prevented from acquiring a lock on the same file, or the same overlapping region in the file.
The IOException being thrown has a bit more interesting message. It probably confirms the above theory, but without looking at the entire source code, I cannot confirm anything. The lock method is expected to block until the exclusive lock is acquired. If it was acquired, then there ought to be no problem in reading from the file. Except for one condition. If the file has already been opened (and locked) by the same JVM in a different thread, using a File object (or in other words, a second/different file descriptor), then the attempted read on the first file descriptor will fail even if the lock was acquired (after all, the lock does not lock out other threads).
An improved design, would be to have a single thread in each process that acquires an exclusive lock on the file (while using a single File object, or a single file descriptor) for only a certain amount of time, perform the required activity in the file, and then release the lock.
As Jeff has pointed out, using the NIO APIs would probably result in resolution of the problem. This is entirely due to the possibility of the FileReader API opening a new file descriptor, which is different from the one that the lock is obtained on.
Maybe what you want is something more like:
FileInputStream fis = new FileInputStream(file);
channel = fis.getChannel();
channel.lock();
bufferedReader = new BufferedReader(new InputStreamReader(fis));
I have a cluster of machines, each running a Java app.
These Java apps need to access a unique resource.txt file concurrently.
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...
For Java 1.7+, use java.nio.file.Files.move(Path source, Path target, CopyOption... options) with CopyOptions "REPLACE_EXISTING" and "ATOMIC_MOVE".
See API documentation for more information.
For example:
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
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.
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 default, 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
Here is a discussion that relates: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4017593
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.
If this should be cross-platform I suggest 2 options:
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.
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.
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:
Open "resource.txt", if that fails
Open "resource.old.txt", if that fails
Open "resource.txt" again, if that fails
You have an error condition.
To replace the file:
Rename "resource.txt" to "resource.old.txt", then
Rename "resource.new.txt" to "resource.txt", then
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
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
I solve with a simple rename function.
Calling :
File newPath = new File("...");
newPath = checkName(newPath);
Files.copy(file.toPath(), newPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
The checkName function checks if exits.
If exits then concat a number between two bracket (1) to the end of the filename.
Functions:
private static File checkName(File newPath) {
if (Files.exists(newPath.toPath())) {
String extractRegExSubStr = extractRegExSubStr(newPath.getName(), "\\([0-9]+\\)");
if (extractRegExSubStr != null) {
extractRegExSubStr = extractRegExSubStr.replaceAll("\\(|\\)", "");
int parseInt = Integer.parseInt(extractRegExSubStr);
int parseIntPLus = parseInt + 1;
newPath = new File(newPath.getAbsolutePath().replace("(" + parseInt + ")", "(" + parseIntPLus + ")"));
return checkName(newPath);
} else {
newPath = new File(newPath.getAbsolutePath().replace(".pdf", " (" + 1 + ").pdf"));
return checkName(newPath);
}
}
return newPath;
}
private static String extractRegExSubStr(String row, String patternStr) {
Pattern pattern = Pattern.compile(patternStr);
Matcher matcher = pattern.matcher(row);
if (matcher.find()) {
return matcher.group(0);
}
return null;
}
EDIT: Its only works for pdf. If you want other please replace the .pdf or create an extension paramter for it.
NOTE: If the file contains additional numbers between brackets '(' then it may mess up your file names.