1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 7105952 6322678 7082769
  27  * @summary Improve finalisation for FileInputStream/FileOutputStream/RandomAccessFile
  28  * @run main/othervm Sharing
  29  */
  30 
  31 import java.io.*;
  32 import java.nio.channels.FileChannel;
  33 import java.nio.channels.FileLock;
  34 import java.util.concurrent.CountDownLatch;
  35 
  36 public class Sharing {
  37 
  38     final static int numFiles = 10;
  39     volatile static boolean fail; 
  40 
  41     public static void main(String[] args) throws Exception {
  42         TestFinalizer();
  43         TestMultipleFD();
  44         TestIsValid();
  45         MultiThreadedFD();
  46         TestCloseAll();
  47     }
  48 
  49     /**
  50      * Finalizer shouldn't discard a file descriptor until all streams have
  51      * finished with it.
  52      */
  53     private static void TestFinalizer() throws Exception {
  54         FileDescriptor fd = null;
  55         File tempFile = new File("TestFinalizer1.txt");
  56         tempFile.deleteOnExit();
  57         try (Writer writer = new FileWriter(tempFile)) {
  58             for (int i=0; i<5; i++) {
  59                 writer.write("test file content test file content");
  60             }
  61         }
  62 
  63         FileInputStream fis1 = new FileInputStream(tempFile);
  64         fd = fis1.getFD();
  65         // Create a new FIS based on the existing FD (so the two FIS's share the same native fd)
  66         try (FileInputStream fis2 = new FileInputStream(fd)) {
  67             // allow fis1 to be gc'ed
  68             fis1 = null;
  69             int ret = 0;
  70             while(ret >= 0) {
  71                 // encourage gc
  72                 System.gc();
  73                 // read from fis2 - when fis1 is gc'ed and finalizer is run, read will fail
  74                 System.out.print(".");
  75                 ret = fis2.read();
  76             }
  77         }
  78 
  79         // variation of above. Use RandomAccessFile to obtain a filedescriptor
  80         File testFinalizerFile = new File("TestFinalizer");
  81         RandomAccessFile raf = new RandomAccessFile(testFinalizerFile, "rw");
  82         raf.writeBytes("test file content test file content");
  83         raf.seek(0L);
  84         fd = raf.getFD();
  85         try (FileInputStream fis3 = new FileInputStream(fd)) { 
  86             // allow raf to be gc'ed
  87             raf = null;
  88             int ret = 0;
  89             while (ret >= 0) {
  90                 // encourage gc
  91                 System.gc();
  92                 /*
  93                  * read from fis3 - when raf is gc'ed and finalizer is run,
  94                  * fd should still be valid.
  95                  */
  96                 System.out.print(".");
  97                 ret = fis3.read();
  98             }
  99         } finally {
 100             testFinalizerFile.delete();
 101         } 
 102     }
 103 
 104     /**
 105      * Exercise FileDispatcher close()/preClose()
 106      */
 107     private static void TestMultipleFD() throws Exception {
 108         RandomAccessFile raf = null;
 109         FileOutputStream fos = null;
 110         FileInputStream fis = null;
 111         FileChannel fc = null;
 112         FileLock fileLock = null;
 113 
 114         File test1 = new File("test1");
 115         try { 
 116             raf = new RandomAccessFile(test1, "rw");
 117             fos = new FileOutputStream(raf.getFD());
 118             fis = new FileInputStream(raf.getFD());
 119             fc = raf.getChannel();
 120             fileLock = fc.lock();
 121             raf.setLength(0L);
 122             fos.flush();
 123             fos.write("TEST".getBytes());
 124         } finally {
 125             if (fileLock != null) fileLock.release();
 126             if (fis != null) fis.close();
 127             if (fos != null) fos.close();
 128             if (raf != null) raf.close();
 129             test1.delete();
 130         }
 131 
 132         /* 
 133          * Close out in different order to ensure FD is not 
 134          * closed out too early
 135          */
 136         File test2 = new File("test2");
 137         try {
 138             raf = new RandomAccessFile(test2, "rw");
 139             fos = new FileOutputStream(raf.getFD());
 140             fis = new FileInputStream(raf.getFD());
 141             fc = raf.getChannel();
 142             fileLock = fc.lock();
 143             raf.setLength(0L);
 144             fos.flush();
 145             fos.write("TEST".getBytes());
 146         } finally {
 147             if (fileLock != null) fileLock.release();
 148             if (raf != null) raf.close();
 149             if (fos != null) fos.close();
 150             if (fis != null) fis.close();
 151             test2.delete();
 152         } 
 153 
 154         // one more time, fos first this time
 155         File test3 = new File("test3");
 156         try {
 157             raf = new RandomAccessFile(test3, "rw");
 158             fos = new FileOutputStream(raf.getFD());
 159             fis = new FileInputStream(raf.getFD());
 160             fc = raf.getChannel();
 161             fileLock = fc.lock();
 162             raf.setLength(0L);
 163             fos.flush();
 164             fos.write("TEST".getBytes());
 165         } finally {
 166             if (fileLock != null) fileLock.release();
 167             if (fos != null) fos.close();
 168             if (raf != null) raf.close();
 169             if (fis != null) fis.close();
 170             test3.delete();
 171         }
 172     }
 173 
 174     /**
 175      * Similar to TestMultipleFD() but this time we
 176      * just get and use FileDescriptor.valid() for testing.
 177      */
 178     private static void TestIsValid() throws Exception {
 179         FileDescriptor fd = null;
 180         RandomAccessFile raf = null;
 181         FileOutputStream fos = null;
 182         FileInputStream fis = null;
 183         FileChannel fc = null;
 184  
 185         File test1 = new File("test1");
 186         try { 
 187             raf = new RandomAccessFile(test1, "rw");
 188             fd = raf.getFD();
 189             fos = new FileOutputStream(fd);
 190             fis = new FileInputStream(fd);
 191         } finally {
 192             try { 
 193                 if (fis != null) fis.close();
 194                 if (fd.valid()) {
 195                     throw new RuntimeException("[FIS close()] FileDescriptor shouldn't be valid");
 196                 }
 197                 if (fos != null) fos.close();
 198                 if (raf != null) raf.close();
 199             } finally {
 200                 test1.delete();
 201             }
 202         }
 203 
 204         /* 
 205          * Close out in different order to ensure FD is
 206          * closed correctly.
 207          */
 208         File test2 = new File("test2");
 209         try {
 210             raf = new RandomAccessFile(test2, "rw");
 211             fd = raf.getFD();
 212             fos = new FileOutputStream(fd);
 213             fis = new FileInputStream(fd);
 214         } finally {
 215             try {
 216                 if (raf != null) raf.close();
 217                 if (fd.valid()) {
 218                     throw new RuntimeException("[RAF close()] FileDescriptor shouldn't be valid");
 219                 }
 220                 if (fos != null) fos.close();
 221                 if (fis != null) fis.close();
 222             } finally {
 223                 test2.delete();
 224             }
 225         } 
 226 
 227         // one more time, fos first this time
 228         File test3 = new File("test3");
 229         try {
 230             raf = new RandomAccessFile(test3, "rw");
 231             fd = raf.getFD();
 232             fos = new FileOutputStream(fd);
 233             fis = new FileInputStream(fd);
 234         } finally {
 235             try {
 236                 if (fos != null) fos.close();
 237                 if (fd.valid()) {
 238                     throw new RuntimeException("[FOS close()] FileDescriptor shouldn't be valid");
 239                 }
 240                 if (raf != null) raf.close();
 241                 if (fis != null) fis.close();
 242             } finally {
 243                 test3.delete();
 244             }
 245         }
 246     }
 247    
 248     /**
 249      * Test concurrent access to the same FileDescriptor
 250      */
 251     private static void MultiThreadedFD() throws Exception {
 252         RandomAccessFile raf = null;
 253         FileDescriptor fd = null;
 254         int numThreads = 2;
 255         CountDownLatch done = new CountDownLatch(numThreads);
 256         OpenClose[] fileOpenClose = new OpenClose[numThreads];
 257         File MultipleThreadedFD = new File("MultipleThreadedFD");
 258         try {
 259             raf = new RandomAccessFile(MultipleThreadedFD, "rw");
 260             fd = raf.getFD();
 261             for(int count=0;count<numThreads;count++) {
 262                 fileOpenClose[count] = new OpenClose(fd, done);
 263                 fileOpenClose[count].start();
 264             }
 265             done.await();
 266         } finally { 
 267             try {
 268                 if(raf != null) raf.close();
 269                 // fd should now no longer be valid
 270                 if(fd.valid()) {
 271                     throw new RuntimeException("FileDescriptor should not be valid");
 272                 }
 273                 // OpenClose thread tests failed
 274                 if(fail) {
 275                     throw new RuntimeException("OpenClose thread tests failed.");
 276                 }
 277             } finally {
 278                 MultipleThreadedFD.delete();
 279             }
 280         }
 281     }
 282 
 283     /**
 284      * Test closeAll handling in FileDescriptor
 285      */
 286     private static void TestCloseAll() throws Exception {
 287         File testFile = new File("test");
 288         testFile.deleteOnExit();
 289         RandomAccessFile raf = new RandomAccessFile(testFile, "rw");
 290         FileInputStream fis = new FileInputStream(raf.getFD());
 291         fis.close();
 292         if (raf.getFD().valid()) {
 293              throw new RuntimeException("FD should not be valid.");
 294         }
 295 
 296         // Test the suppressed exception handling - FileInputStream
 297 
 298         raf = new RandomAccessFile(testFile, "rw");
 299         fis = new FileInputStream(raf.getFD());
 300         BadFileInputStream bfis1 = new BadFileInputStream(raf.getFD());
 301         BadFileInputStream bfis2 = new BadFileInputStream(raf.getFD());
 302         BadFileInputStream bfis3 = new BadFileInputStream(raf.getFD());
 303         // extra test - set bfis3 to null
 304         bfis3 = null;
 305         try {
 306             fis.close();
 307         } catch (IOException ioe) {
 308             ioe.printStackTrace();
 309             if (ioe.getSuppressed().length != 2) {
 310                 throw new RuntimeException("[FIS]Incorrect number of suppressed " + 
 311                           "exceptions received : " + ioe.getSuppressed().length);
 312             }
 313         }
 314         if (raf.getFD().valid()) { 
 315             // we should still have closed the FD 
 316             // even with the exception.
 317             throw new RuntimeException("[FIS]TestCloseAll : FD still valid.");
 318         }
 319 
 320         // Now test with FileOutputStream
 321 
 322         raf = new RandomAccessFile(testFile, "rw");
 323         FileOutputStream fos = new FileOutputStream(raf.getFD());
 324         BadFileOutputStream bfos1 = new BadFileOutputStream(raf.getFD());
 325         BadFileOutputStream bfos2 = new BadFileOutputStream(raf.getFD());
 326         BadFileOutputStream bfos3 = new BadFileOutputStream(raf.getFD());
 327         // extra test - set bfos3 to null
 328         bfos3 = null;
 329         try {
 330             fos.close();
 331         } catch (IOException ioe) {
 332             ioe.printStackTrace();
 333             if (ioe.getSuppressed().length != 2) {
 334                 throw new RuntimeException("[FOS]Incorrect number of suppressed " +
 335                           "exceptions received : " + ioe.getSuppressed().length);
 336             }
 337         }
 338         if (raf.getFD().valid()) {
 339             // we should still have closed the FD
 340             // even with the exception.
 341             throw new RuntimeException("[FOS]TestCloseAll : FD still valid.");
 342         }
 343     }
 344    
 345     /**
 346      * A thread which will open and close a number of FileInputStreams and
 347      * FileOutputStreams referencing the same native file descriptor.
 348      */
 349     private static class OpenClose extends Thread {
 350         private FileDescriptor fd = null;
 351         private CountDownLatch done;
 352         FileInputStream[] fisArray = new FileInputStream[numFiles];
 353         FileOutputStream[] fosArray = new FileOutputStream[numFiles];
 354         
 355         OpenClose(FileDescriptor filedescriptor, CountDownLatch done) {
 356             this.fd = filedescriptor;
 357             this.done = done;
 358         }
 359          
 360         public void run() {
 361              try { 
 362                  for(int i=0;i<numFiles;i++) {
 363                      fisArray[i] = new FileInputStream(fd);
 364                      fosArray[i] = new FileOutputStream(fd);
 365                  }
 366              
 367                  // Now close out
 368                  for(int i=0;i<numFiles;i++) {
 369                      if(fisArray[i] != null) fisArray[i].close();
 370                      if(fosArray[i] != null) fosArray[i].close();
 371                  }
 372              
 373              } catch(IOException ioe) {
 374                  System.out.println("OpenClose encountered IO issue :" + ioe);
 375                  fail = true;
 376              } finally {
 377                  if (fd.valid()) { // fd should not be valid after first close() call
 378                      System.out.println("OpenClose: FileDescriptor shouldn't be valid");
 379                      fail = true;
 380                  }
 381                  done.countDown();
 382              }
 383          }
 384     }
 385     
 386     private static class BadFileInputStream extends FileInputStream {
 387 
 388         BadFileInputStream(FileDescriptor fd) {
 389             super(fd);
 390         }
 391 
 392         public void close() throws IOException {
 393             throw new IOException("Bad close operation");
 394         }
 395     }
 396 
 397     private static class BadFileOutputStream extends FileOutputStream {
 398 
 399         BadFileOutputStream(FileDescriptor fd) {
 400             super(fd);
 401         }
 402 
 403         public void close() throws IOException {
 404             throw new IOException("Bad close operation");
 405         }
 406     }
 407 
 408 }