Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Random access file FileLock: java.io vs. java.nio

I've noticed that java.io and java.nio implementations of random access files differ slightly with respect to how FileLocks are handled.

It appears as though (on Windows) java.io gives you a mandatory file lock and java.nio gives you an advisory file lock upon requesting it respectively. A mandatory file lock means that the lock holds for all processes and an advisory holds for well behaving processes that follow the same locking protocol.

If I run the following example, I'm able to delete the *.nio file manually, while *.io file refuses to be deleted.

import java.io.*;
import java.lang.management.ManagementFactory;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class NioIoLock {

    public static void main(String[] args) throws IOException, InterruptedException {
        String workDir = System.getProperty("user.dir");

        FileChannel channelIo, channelNio;
        FileLock lockIo, lockNio;

        // use io
        {
            String fileName = workDir
                    + File.separator
                    + ManagementFactory.getRuntimeMXBean().getName()
                    + ".io";
            File lockFile = new File(fileName);
            lockFile.deleteOnExit();
            RandomAccessFile file = new RandomAccessFile(lockFile, "rw");            

            channelIo = file.getChannel();
            lockIo = channelIo.tryLock();
            if (lockIo != null) {                   
                channelIo.write(ByteBuffer.wrap("foobar".getBytes("UTF-8")));
            }

        }

        // use nio
        {
            Path workDirPath = Paths.get(workDir);
            Path file = workDirPath.resolve(
                    Paths.get(ManagementFactory.getRuntimeMXBean().getName() + ".nio"));

            // open/create test file
            channelNio = FileChannel.open(
                    file, StandardOpenOption.READ, StandardOpenOption.WRITE,
                    StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE);

            // lock file
            lockNio = channelNio.tryLock();
            if (lockNio != null) {
                channelNio.write(ByteBuffer.wrap("foobar".getBytes("UTF-8")));
            }

        }

        // do not release locks for some time
        Thread.sleep(10000);

        // release io lock and channel
        if (lockIo != null && lockIo.isValid()) {
            lockIo.release();
        }
        channelIo.close();

        // release nio lock and channel
        if (lockNio != null && lockNio.isValid()) {
            lockNio.release();
        }
        channelNio.close();
    }

}

Is there a reason for this? Are these two even considered as alternatives or are they meant to coexist indefinitely?

like image 357
predi Avatar asked Sep 02 '16 13:09

predi


People also ask

Does Java support random access files?

Java RandomAccessFile provides the facility to read and write data to a file. RandomAccessFile works with file as large array of bytes stored in the file system and a cursor using which we can move the file pointer position.

What is the difference between the file and RandomAccessFile classes?

The contents of the files cannot be read or write. The RandomAccessFile class has methods that perform the direct access to data of any part of the file. RandomAccessFile class also provides the facilities to read and write data from a single file without closing the streams for each read or write.

What is random access file in Java?

A random access file behaves like a large array of bytes stored in the file system. There is a kind of cursor, or index into the implied array, called the file pointer; input operations read bytes starting at the file pointer and advance the file pointer past the bytes read.

How does NIO work in Java?

Java NIO works as the second I/O system after standard Java IO with some added advanced features. It provides a different way of working with I/O than the standard IO. Like Java.io package which contains all the classes required for Java input and output operations, the java.


1 Answers

TL;DR: It's not about locks, it's about the way files are opened. What you see in io is the best Windows could do prior to Windows 2000, and it did that even when files were opened for reading only -- it wasn't possible to delete that file. What you see in nio is an improvement that uses a new capability introduced since Windows 2000, but you still can have your old behavior in nio if you choose to. It was decided not to port that capability into what io does.

Full story: I removed all the code related to locking (see below) as well as writing to the files and it works absolutely the same as your code does; however, I also found that if you specify the ExtendedOpenOption.NOSHARE_DELETE when opening the "nio" channel, then behavior when you try to delete any of the two files is the same (uncomment it in the code and try). Also found this question and it has some explanation, not sure if it will help.

import java.io.*;
import java.lang.management.ManagementFactory;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class NioIoLock {

    public static void main(String[] args)
            throws IOException, InterruptedException {
        String workDir = System.getProperty("user.dir");

        FileChannel channelIo, channelNio;
        FileLock lockIo, lockNio;

        // use io
        {
            String fileName = workDir + File.separator
                + ManagementFactory.getRuntimeMXBean().getName() + ".io";
            File lockFile = new File(fileName);
            lockFile.deleteOnExit();
            RandomAccessFile file = new RandomAccessFile(lockFile, "rw");
            channelIo = file.getChannel();
        }

        // use nio
        {
            Path workDirPath = Paths.get(workDir);
            Path file = workDirPath.resolve(Paths
                .get(ManagementFactory.getRuntimeMXBean().getName() + ".nio"));

            // open/create test file
            channelNio = FileChannel.open(file, StandardOpenOption.READ,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE,
                StandardOpenOption.DELETE_ON_CLOSE
                /*, com.sun.nio.file.ExtendedOpenOption.NOSHARE_DELETE*/);
        }

        // do not release locks for some time
        Thread.sleep(10000);
    }
}

Update 1: Actually, I still don't know the rationale behind this, but the mechanics are clear: RandomAccessFile constructor eventually calls the following native code from method winFileHandleOpen from io_util_md.c:

FD
winFileHandleOpen(JNIEnv *env, jstring path, int flags)
{
    ...
    const DWORD sharing =
        FILE_SHARE_READ | FILE_SHARE_WRITE;
    ... // "sharing" not updated anymore
    h = CreateFileW(
        pathbuf,            /* Wide char path name */
        access,             /* Read and/or write permission */
        sharing,            /* File sharing flags */
        NULL,               /* Security attributes */
        disposition,        /* creation disposition */
        flagsAndAttributes, /* flags and attributes */
        NULL);
    ...
}

So, FILE_SHARE_DELETE flag is not set and there's nothing you can do to set it. On the other hand, calling java.nio.channels.FileChannel.open(Path, OpenOption...) eventually takes this flag into account:

    private static FileDescriptor open(String pathForWindows,
                                       String pathToCheck,
                                       Flags flags,
                                       long pSecurityDescriptor)
        throws WindowsException
    {
        ...
        int dwShareMode = 0;
        if (flags.shareRead)
            dwShareMode |= FILE_SHARE_READ;
        if (flags.shareWrite)
            dwShareMode |= FILE_SHARE_WRITE;
        if (flags.shareDelete)
            dwShareMode |= FILE_SHARE_DELETE;
        ...
        // open file
        long handle = CreateFile(pathForWindows,
                                 dwDesiredAccess,
                                 dwShareMode,
                                 pSecurityDescriptor,
                                 dwCreationDisposition,
                                 dwFlagsAndAttributes);
        ...
    }

Update 2: From JDK-6607535:

The suggestion to change the sharing mode is RFE 6357433. It would be great to do that but it may break existing applications (since the current behavior has existed since jdk1.0). Adding a special mode to RandomAccessFile to work around a Windows issues is probably not the right approach either. Also, there is some suggestion that this can help with the issue of deleting files that have a file mapping. This is not so, as a file mapping still prevents a file from being deleted. Anyway, for jdk7 we are working on a new file system API that will address some of the requirements here. The Windows implementation does the right thing so that opened files can be deleted.

Also see JDK-6357433 referenced from there:

A customer has pointed out on the java.net forums that opening a file on Windows using a FileInputStream causes other processes to be unable to delete the file. This is intended behavior as the code is currently written, as the sharing flags specified during the opening of the file are FILE_SHARE_READ and FILE_SHARE_WRITE. See io_util_md.c, fileOpen. However, Windows 2000 supplies a new flag, FILE_SHARE_DELETE, which allows another process to delete the file while it is still open.

and finally

WORK AROUND

For jdk7, the workaround is to use new file system API. So instead of new FileInputStream(f) and new FileOutputStream(f), use f.toPath().newInputStream() and f.toPath().newOutputStream().

like image 94
starikoff Avatar answered Oct 17 '22 15:10

starikoff