Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
752 views
in Technique[技术] by (71.8m points)

java - Windows: moving a file that was previously mapped in memory fails

-:= EDITED TO SIMPLIFY =:-


I am facing an issue in the process of porting code from a Linux (Ubuntu LTS 12.4) environment to Windows Server 2008.

I need to use memory mapped file but I am not able to prevent the error below on Windows.

This issue is reproduced in the unit test below. The 2 tests are succeeding on Linux but on Windows the test testWithRandowmAccessFile fails with the stack trace on the bottom.

What is the root cause of the testWithRandowmAccessFile test failing?
How am I supposed to implement that on Windows?

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import org.apache.commons.io.IOUtils;
import org.junit.Test;

public class TestIOOnWindows {   

    @Test
    public void testWithRandowmAccessFile() throws IOException {
        final File sourceFile = new File("source.txt");
        final File manipulatedFile = new File("manipulated.txt");
        final File targetFile = new File("target.txt");

        try
        (
            FileInputStream sourceInputStream = new FileInputStream(sourceFile);
            RandomAccessFile manipulated = new RandomAccessFile(manipulatedFile, "rw");
            FileChannel fcOut = manipulated.getChannel()
        ) 
        {
            byte[] sourceBytes = new byte[Long.valueOf(sourceFile.length()).intValue()];
            IOUtils.read(sourceInputStream, sourceBytes);

            final int length = sourceBytes.length;            

            // ========= with this single line on Windows, the move fails ======
            MappedByteBuffer byteBuffer = fcOut.map(FileChannel.MapMode.READ_WRITE, 0, length);
            // commenting this line would not prevent the error on Windows
            byteBuffer.put(sourceBytes, 0, length);            
        }

        Files.move(
                Paths.get(manipulatedFile.getAbsolutePath()),
                Paths.get(targetFile.getAbsolutePath()),
                StandardCopyOption.REPLACE_EXISTING);
    }

    @Test
    public void testWithFileOutputStream() throws IOException {
        final File sourceFile = new File("source.txt");
        final File manipulatedFile = new File("manipulated.txt");
        final File targetFile = new File("target.txt");

        try
        (
            FileInputStream sourceInputStream = new FileInputStream(sourceFile);
            FileOutputStream manipulatedOutputStream = new FileOutputStream(manipulatedFile);
            FileChannel fcIn = sourceInputStream.getChannel();
            FileChannel fcOut = manipulatedOutputStream.getChannel()
        ) 
        {
            final long length = sourceFile.length();

            // ========= with this single line on Windows, the move succeed ====
            fcIn.transferTo(0, length, fcOut);
        }

        Files.move(
                Paths.get(manipulatedFile.getAbsolutePath()),
                Paths.get(targetFile.getAbsolutePath()),
                StandardCopyOption.REPLACE_EXISTING);
    }
}


adding the tack trace I am getting when running the unit test from command prompt on windows.

There was 1 failure:
1) testWithRandowmAccessFile(TestIOOnWindows) java.nio.file.FileSystemException: C:UsersAdministratorAppDataLocalTempmanipulated.txt -> C:UsersAdministratorAppDataLocalTemparget.txt: The process cannot access the file because it is being used by another process.

    at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    at sun.nio.fs.WindowsFileCopy.move(Unknown Source)
    at sun.nio.fs.WindowsFileSystemProvider.move(Unknown Source)
    at java.nio.file.Files.move(Unknown Source)
    ===> at TestIOOnWindows.testWithRandowmAccessFile(TestIOOnWindows.java:40) <===
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:24)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
    at org.junit.runner.JUnitCore.runMain(JUnitCore.java:98)
    at org.junit.runner.JUnitCore.runMainAndExit(JUnitCore.java:53)
    at org.junit.runner.JUnitCore.main(JUnitCore.java:45)

FAILURES!!!
Tests run: 2,  Failures: 1
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

In Java, file mappings are garbage collected, and there is no supported way to forcibly destroy a mapping.

From the FileChannel.map() documentation:

The buffer and the mapping that it represents will remain valid until the buffer itself is garbage-collected.

A mapping, once established, is not dependent upon the file channel that was used to create it. Closing the channel, in particular, has no effect upon the validity of the mapping.

In Sun's JDK, you can test that this is indeed the culprit by forcibly destroying the mapping just before doing the file move:

import sun.nio.ch.DirectBuffer;
import sun.misc.Cleaner;
[...]
if (byteBuffer.isDirect()) {
    Cleaner cleaner = ((DirectBuffer) byteBuffer).cleaner();
    cleaner.clean();
}
// move file

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...