/* * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 7105952 6322678 7082769 * @summary Improve finalisation for FileInputStream/FileOutputStream/RandomAccessFile * @run main/othervm Sharing */ import java.io.*; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.concurrent.CountDownLatch; public class Sharing { final static int numFiles = 10; volatile static boolean fail; public static void main(String[] args) throws Exception { TestFinalizer(); TestMultipleFD(); TestIsValid(); MultiThreadedFD(); TestCloseAll(); } /** * Finalizer shouldn't discard a file descriptor until all streams have * finished with it. */ private static void TestFinalizer() throws Exception { FileDescriptor fd = null; File tempFile = new File("TestFinalizer1.txt"); tempFile.deleteOnExit(); try (Writer writer = new FileWriter(tempFile)) { for (int i=0; i<5; i++) { writer.write("test file content test file content"); } } FileInputStream fis1 = new FileInputStream(tempFile); fd = fis1.getFD(); // Create a new FIS based on the existing FD (so the two FIS's share the same native fd) try (FileInputStream fis2 = new FileInputStream(fd)) { // allow fis1 to be gc'ed fis1 = null; int ret = 0; while(ret >= 0) { // encourage gc System.gc(); // read from fis2 - when fis1 is gc'ed and finalizer is run, read will fail System.out.print("."); ret = fis2.read(); } } // variation of above. Use RandomAccessFile to obtain a filedescriptor File testFinalizerFile = new File("TestFinalizer"); RandomAccessFile raf = new RandomAccessFile(testFinalizerFile, "rw"); raf.writeBytes("test file content test file content"); raf.seek(0L); fd = raf.getFD(); try (FileInputStream fis3 = new FileInputStream(fd)) { // allow raf to be gc'ed raf = null; int ret = 0; while (ret >= 0) { // encourage gc System.gc(); /* * read from fis3 - when raf is gc'ed and finalizer is run, * fd should still be valid. */ System.out.print("."); ret = fis3.read(); } } finally { testFinalizerFile.delete(); } } /** * Exercise FileDispatcher close()/preClose() */ private static void TestMultipleFD() throws Exception { RandomAccessFile raf = null; FileOutputStream fos = null; FileInputStream fis = null; FileChannel fc = null; FileLock fileLock = null; File test1 = new File("test1"); try { raf = new RandomAccessFile(test1, "rw"); fos = new FileOutputStream(raf.getFD()); fis = new FileInputStream(raf.getFD()); fc = raf.getChannel(); fileLock = fc.lock(); raf.setLength(0L); fos.flush(); fos.write("TEST".getBytes()); } finally { if (fileLock != null) fileLock.release(); if (fis != null) fis.close(); if (fos != null) fos.close(); if (raf != null) raf.close(); test1.delete(); } /* * Close out in different order to ensure FD is not * closed out too early */ File test2 = new File("test2"); try { raf = new RandomAccessFile(test2, "rw"); fos = new FileOutputStream(raf.getFD()); fis = new FileInputStream(raf.getFD()); fc = raf.getChannel(); fileLock = fc.lock(); raf.setLength(0L); fos.flush(); fos.write("TEST".getBytes()); } finally { if (fileLock != null) fileLock.release(); if (raf != null) raf.close(); if (fos != null) fos.close(); if (fis != null) fis.close(); test2.delete(); } // one more time, fos first this time File test3 = new File("test3"); try { raf = new RandomAccessFile(test3, "rw"); fos = new FileOutputStream(raf.getFD()); fis = new FileInputStream(raf.getFD()); fc = raf.getChannel(); fileLock = fc.lock(); raf.setLength(0L); fos.flush(); fos.write("TEST".getBytes()); } finally { if (fileLock != null) fileLock.release(); if (fos != null) fos.close(); if (raf != null) raf.close(); if (fis != null) fis.close(); test3.delete(); } } /** * Similar to TestMultipleFD() but this time we * just get and use FileDescriptor.valid() for testing. */ private static void TestIsValid() throws Exception { FileDescriptor fd = null; RandomAccessFile raf = null; FileOutputStream fos = null; FileInputStream fis = null; FileChannel fc = null; File test1 = new File("test1"); try { raf = new RandomAccessFile(test1, "rw"); fd = raf.getFD(); fos = new FileOutputStream(fd); fis = new FileInputStream(fd); } finally { try { if (fis != null) fis.close(); if (fd.valid()) { throw new RuntimeException("[FIS close()] FileDescriptor shouldn't be valid"); } if (fos != null) fos.close(); if (raf != null) raf.close(); } finally { test1.delete(); } } /* * Close out in different order to ensure FD is * closed correctly. */ File test2 = new File("test2"); try { raf = new RandomAccessFile(test2, "rw"); fd = raf.getFD(); fos = new FileOutputStream(fd); fis = new FileInputStream(fd); } finally { try { if (raf != null) raf.close(); if (fd.valid()) { throw new RuntimeException("[RAF close()] FileDescriptor shouldn't be valid"); } if (fos != null) fos.close(); if (fis != null) fis.close(); } finally { test2.delete(); } } // one more time, fos first this time File test3 = new File("test3"); try { raf = new RandomAccessFile(test3, "rw"); fd = raf.getFD(); fos = new FileOutputStream(fd); fis = new FileInputStream(fd); } finally { try { if (fos != null) fos.close(); if (fd.valid()) { throw new RuntimeException("[FOS close()] FileDescriptor shouldn't be valid"); } if (raf != null) raf.close(); if (fis != null) fis.close(); } finally { test3.delete(); } } } /** * Test concurrent access to the same FileDescriptor */ private static void MultiThreadedFD() throws Exception { RandomAccessFile raf = null; FileDescriptor fd = null; int numThreads = 2; CountDownLatch done = new CountDownLatch(numThreads); OpenClose[] fileOpenClose = new OpenClose[numThreads]; File MultipleThreadedFD = new File("MultipleThreadedFD"); try { raf = new RandomAccessFile(MultipleThreadedFD, "rw"); fd = raf.getFD(); for(int count=0;count<numThreads;count++) { fileOpenClose[count] = new OpenClose(fd, done); fileOpenClose[count].start(); } done.await(); } finally { try { if(raf != null) raf.close(); // fd should now no longer be valid if(fd.valid()) { throw new RuntimeException("FileDescriptor should not be valid"); } // OpenClose thread tests failed if(fail) { throw new RuntimeException("OpenClose thread tests failed."); } } finally { MultipleThreadedFD.delete(); } } } /** * Test closeAll handling in FileDescriptor */ private static void TestCloseAll() throws Exception { File testFile = new File("test"); testFile.deleteOnExit(); RandomAccessFile raf = new RandomAccessFile(testFile, "rw"); FileInputStream fis = new FileInputStream(raf.getFD()); fis.close(); if (raf.getFD().valid()) { throw new RuntimeException("FD should not be valid."); } // Test the suppressed exception handling - FileInputStream raf = new RandomAccessFile(testFile, "rw"); fis = new FileInputStream(raf.getFD()); BadFileInputStream bfis1 = new BadFileInputStream(raf.getFD()); BadFileInputStream bfis2 = new BadFileInputStream(raf.getFD()); BadFileInputStream bfis3 = new BadFileInputStream(raf.getFD()); // extra test - set bfis3 to null bfis3 = null; try { fis.close(); } catch (IOException ioe) { ioe.printStackTrace(); if (ioe.getSuppressed().length != 2) { throw new RuntimeException("[FIS]Incorrect number of suppressed " + "exceptions received : " + ioe.getSuppressed().length); } } if (raf.getFD().valid()) { // we should still have closed the FD // even with the exception. throw new RuntimeException("[FIS]TestCloseAll : FD still valid."); } // Now test with FileOutputStream raf = new RandomAccessFile(testFile, "rw"); FileOutputStream fos = new FileOutputStream(raf.getFD()); BadFileOutputStream bfos1 = new BadFileOutputStream(raf.getFD()); BadFileOutputStream bfos2 = new BadFileOutputStream(raf.getFD()); BadFileOutputStream bfos3 = new BadFileOutputStream(raf.getFD()); // extra test - set bfos3 to null bfos3 = null; try { fos.close(); } catch (IOException ioe) { ioe.printStackTrace(); if (ioe.getSuppressed().length != 2) { throw new RuntimeException("[FOS]Incorrect number of suppressed " + "exceptions received : " + ioe.getSuppressed().length); } } if (raf.getFD().valid()) { // we should still have closed the FD // even with the exception. throw new RuntimeException("[FOS]TestCloseAll : FD still valid."); } } /** * A thread which will open and close a number of FileInputStreams and * FileOutputStreams referencing the same native file descriptor. */ private static class OpenClose extends Thread { private FileDescriptor fd = null; private CountDownLatch done; FileInputStream[] fisArray = new FileInputStream[numFiles]; FileOutputStream[] fosArray = new FileOutputStream[numFiles]; OpenClose(FileDescriptor filedescriptor, CountDownLatch done) { this.fd = filedescriptor; this.done = done; } public void run() { try { for(int i=0;i<numFiles;i++) { fisArray[i] = new FileInputStream(fd); fosArray[i] = new FileOutputStream(fd); } // Now close out for(int i=0;i<numFiles;i++) { if(fisArray[i] != null) fisArray[i].close(); if(fosArray[i] != null) fosArray[i].close(); } } catch(IOException ioe) { System.out.println("OpenClose encountered IO issue :" + ioe); fail = true; } finally { if (fd.valid()) { // fd should not be valid after first close() call System.out.println("OpenClose: FileDescriptor shouldn't be valid"); fail = true; } done.countDown(); } } } private static class BadFileInputStream extends FileInputStream { BadFileInputStream(FileDescriptor fd) { super(fd); } public void close() throws IOException { throw new IOException("Bad close operation"); } } private static class BadFileOutputStream extends FileOutputStream { BadFileOutputStream(FileDescriptor fd) { super(fd); } public void close() throws IOException { throw new IOException("Bad close operation"); } } }