Tuesday, April 1, 2008

Linux File Locks: Java and the others...

Since Java 1.4, it's possible to locks file using the underline o.s. facilities through the standard API; in this way we can safe share external files with non-Java applications or among multiple Java applications (on different JVM) without writing native and unportable code.

The FileLock class, which is part of the java.nio.channels package, represents a file lock. You can use a file lock to restrict access to a file from multiple processes. In addition, you have the option of restricting access to an entire file or just a small region of it. A file lock is either shared or exclusive as usually. If you use a file lock in multiple threads, none of the threads would be restricted from accessing the file, because they run in the same VM and share the same system process. Only external processes are restricted.

To use file locking, you need to get a file channel, that is an instance of the FileChannel class. You can get a file channel through the getChannel method of any of the following I/O classes:
  • FileInputstream
  • FileOutputStream
  • RandomAccessFile
After you get a file channel, you can use either of two pairs of methods that it offers for working with locks: lock and tryLock.

FileLock lock()
FileLock tryLock()
In this post I show an example how use this api to lock a file under Linux (Ubuntu 7.10) and to synchronize a Java program with C native program to access a shared file.

Here the Java code, it asks the user for a file to open and than it writes to it the successive user input lines. Before starting to write to the file the Java Program try to get an exclusive lock on the file.

import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.util.regex.Pattern;

/**
* @author panks
*
*/
public class Main {

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {

RandomAccessFile file = null;
FileLock lock = null;
try {
BufferedReader console =
new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserisci nome file:");

String name = console.readLine();
file = new RandomAccessFile(name, "rwd");

System.out.println("locking file " + name);

lock = file.getChannel().tryLock();

if (lock == null) {
System.out.println("NO LOCK :( bye bye...");
return;
}
System.out.println("!!LOCKED!!");

String line = "";
while ((line = console.readLine()) != null) {
file.writeBytes(line + "\n");
}
} finally {
if (lock != null) lock.release();
if (file != null) file.close();
}
}

}

Ok then let's try the program
java Main
Inserisci nome file:
file
locking file file
!!LOCKED!!
hello world!!
...
leave the program running and open a new console to the same working directory and try

cat file
hello world!!
...


oopss... we can access the locked file!! ... does it seam strange? Do worry your linux station is working perfectly and this the normal specificated behavior. In fact we have to remember normally file locks on unix operation system are advisory locks, then programs must collaborate to share files, they must use the same lock strategy to be blocked each others.

Ok, the try with and well advised startegy

flock file ls
file Main.class

Whats?!?! So... That seams really strange. Wait a moment try this

flock file cat
blabla blabla
blabla blabla
uhuh uuhh uhh
uhuh uuhh uhh

open a new console to the same directory and type

flock -n file cat

this return 1 because it can't lock the file.
Then the flock works fine, but it doesn't work with Java locks.
In this situation we have only one thing to do... "let's open the box and take a look inside"...
we need to directly explore our great resource... the LINUX KERNEL and his system calls .

Ok in the flock man 1 page we can read:
"This utility manages flock(2) locks from within shell scripts or the command line"
so the flock shell command use the flock syscall, in fact in man 2 flock we can read

"... Since kernel 2.0, flock() is implemented as a system call in its own right rather than being emulated in the GNU C library as a call to fcntl(2). This yields true BSD semantics: there is no interaction between the types of lock placed by flock() and fcntl(2), and flock() does not detect deadlock. ..."

Now the situation becomes to be clearer, probably the Java Api are implemented by fcntl and so they can't share locks with flock. By the way we can explore this possibility.

We start to write a simple C program that uses the flock syscall directly.


#include
#include
#include

#include
#include

#include
#include

int main(void) {
char filepath[100];

puts("Please insert the file path:");
gets(filepath); //don't use gets, only here for simplicty ;)

int fd = open(filepath, O_CREAT | O_RDWR | O_SYNC
, S_IRUSR | S_IWUSR | S_IROTH | S_IRGRP);
if (fd < 0) perror("open");

printf("opened file %s \n", filepath);

puts("tring to lock file...");

if (flock(fd, LOCK_EX) < 0)
perror("fcntl");
else
puts("file !!LOCKED!!");

char line[1000];

while (fgets(line, 1000, stdin) != NULL) {
int n = write(fd, line, strlen(line));
if (n < 0) perror("write");
}


puts("release lock ...");

if (flock(fd, LOCK_UN) < 0)
perror("flock");
else
puts("file !!RELEASED!!");

puts("closing file...");

if (close(fd) < 0)
perror("close");
else
puts("file !!CLOSED!!");

return 0;
}


Ok now we test this program compiled to the executable file named as exs.

In one terminal we execute:


./exs
Please insert the file path:
file
opened file file
tring to lock file...
file !!LOCKED!!
Hello Word!
:)


We have the file file opened and locked in exclusive way and we are writing into it.

On one other terminal in the same dir we can try this:


flock file cat file


the terminal is blocked because the flock can't acquire the lock the file.
If we go back to the first terminal and we give the Crt+D

release lock ...
file !!RELEASED!!
closing file...
file !!CLOSED!!

on the second terminal we have


Hello Word!
:)


so our c program shares file lock with the shell command flock in the right way.

let's try it with the Java program.


java Main
Inserisci nome file:
file
locking file file
!!LOCKED!!
Ciao
Mondo!
by panks


I lock the file file and I start writing to it by Java code.
On the other terminal I try the c program


./exs
Please insert the file path:
file
opened file file
tring to lock file...
file !!LOCKED!!
Bella
Ciao!
release lock ...
file !!RELEASED!!
closing file...
file !!CLOSED!!

..mmm yes the two program seams absolutely ignore themselves...


Inserisci nome file:
file
locking file file
!!LOCKED!!
Ciao
Mondo!
by panks
"CRTL+D"
cat file
Bella
Ciao!
by panks


The c program rewrote what Java one has written before...





Well now we can try using the fcntl syscall from our c code.
Here the exs program modified with fcntl.


...
int main(void) {
char filepath[100];

puts("Please insert the file path:");
gets(filepath); //don't use gets, only here for simplicty ;)

struct flock lock;
/* Initialize the flock structure. */
memset (&lock, 0, sizeof(lock));
lock.l_type = F_WRLCK;

int fd = open(filepath, O_CREAT | O_RDWR | O_SYNC
, S_IRUSR | S_IWUSR | S_IROTH | S_IRGRP);
if (fd < 0) perror("open");

printf("opened file %s \n", filepath);
puts("tring to lock file...");

if (fcntl(fd, F_SETLKW, &lock) < 0)
perror("fcntl");
else
puts("file !!LOCKED!!");

char line[1000];

while (fgets(line, 1000, stdin) != NULL) {
int n = write(fd, line, strlen(line));
if (n < 0) perror("write");
}


puts("release lock ...");
lock.l_type = F_UNLCK;

if (fcntl(fd, F_SETLKW, &lock) < 0)
perror("fcntl");
else
puts("file !!RELEASED!!");

puts("closing file...");

if (close(fd) < 0)
perror("close");
else
puts("file !!CLOSED!!");

return 0;
}


ok now let's to try it with the Java one.

In the first terminal


./exs
Please insert the file path:
file
opened file file
tring to lock file...
file !!LOCKED!!
Come e' dura
l'avventura...


we lock the file file by the c code using the fcntl directly.
On the second terminal we execute the Java program:


java Main
Inserisci nome file:
file
locking file file
NO LOCK :( bye bye...


YES!!!! IT WORKS... we synchronized the Java program with linux C native code on a file.





Same conclusions:

The FileLock class is absolutely useful but probably better to use it to synchronize Java Processes running on different JVM of the same version (in fact different implementations could give unexpected behaviour). When we really need to synchronize on a file a Java program with same native one probably better to use other strategy:


  • One of most appropriate enterprise scale mechanism, database lock, queues ecc...
  • Advisory file locks by same external and well documented conventional mechanism, for example symbolic links in /var/lock as anyway all unix daemons usually do.

No comments:

About Me

Milano, Italy
Software Architect and Software Solution Advisor