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 6322678 7082769 27 * @summary FileInputStream/FileOutputStream/RandomAccessFile allow file descriptor 28 * to be closed while still in use. 29 * @run main/othervm FileDescriptorSharing 30 */ 31 32 import java.io.*; 33 import java.nio.channels.FileChannel; 34 import java.nio.channels.FileLock; 35 import java.util.concurrent.CountDownLatch; 36 37 public class FileDescriptorSharing { 38 39 final static int numFiles = 10; 40 volatile static boolean fail; 41 42 public static void main(String[] args) throws Exception { 43 TestFinalizer(); 44 TestMultipleFD(); 45 TestIsValid(); 46 MultiThreadedFD(); 47 } 48 49 /** 50 * We 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 if(!fd.valid()) { 100 throw new RuntimeException("TestFinalizer() : FileDescriptor should be valid"); 101 } 102 } finally { 103 testFinalizerFile.delete(); 104 } 105 } 106 107 /** 108 * Exercise FileDispatcher close()/preClose() 109 */ 110 private static void TestMultipleFD() throws Exception { 111 RandomAccessFile raf = null; 112 FileOutputStream fos = null; 113 FileInputStream fis = null; 114 FileChannel fc = null; 115 FileLock fileLock = null; 116 117 File test1 = new File("test1"); 118 try { 119 raf = new RandomAccessFile(test1, "rw"); 120 fos = new FileOutputStream(raf.getFD()); 121 fis = new FileInputStream(raf.getFD()); 122 fc = raf.getChannel(); 123 fileLock = fc.lock(); 124 raf.setLength(0L); 125 fos.flush(); 126 fos.write("TEST".getBytes()); 127 } finally { 128 if (fileLock != null) fileLock.release(); 129 if (fis != null) fis.close(); 130 if (fos != null) fos.close(); 131 if (raf != null) raf.close(); 132 test1.delete(); 133 } 134 135 /* 136 * Close out in different order to ensure FD is not 137 * closed out too early 138 */ 139 File test2 = new File("test2"); 140 try { 141 raf = new RandomAccessFile(test2, "rw"); 142 fos = new FileOutputStream(raf.getFD()); 143 fis = new FileInputStream(raf.getFD()); 144 fc = raf.getChannel(); 145 fileLock = fc.lock(); 146 raf.setLength(0L); 147 fos.flush(); 148 fos.write("TEST".getBytes()); 149 } finally { 150 if (fileLock != null) fileLock.release(); 151 if (raf != null) raf.close(); 152 if (fos != null) fos.close(); 153 if (fis != null) fis.close(); 154 test2.delete(); 155 } 156 157 // one more time, fos first this time 158 File test3 = new File("test3"); 159 try { 160 raf = new RandomAccessFile(test3, "rw"); 161 fos = new FileOutputStream(raf.getFD()); 162 fis = new FileInputStream(raf.getFD()); 163 fc = raf.getChannel(); 164 fileLock = fc.lock(); 165 raf.setLength(0L); 166 fos.flush(); 167 fos.write("TEST".getBytes()); 168 } finally { 169 if (fileLock != null) fileLock.release(); 170 if (fos != null) fos.close(); 171 if (raf != null) raf.close(); 172 if (fis != null) fis.close(); 173 test3.delete(); 174 } 175 } 176 177 /** 178 * Similar to TestMultipleFD() but this time we 179 * just get and use FileDescriptor.valid() for testing. 180 */ 181 private static void TestIsValid() throws Exception { 182 FileDescriptor fd = null; 183 RandomAccessFile raf = null; 184 FileOutputStream fos = null; 185 FileInputStream fis = null; 186 FileChannel fc = null; 187 188 File test1 = new File("test1"); 189 try { 190 raf = new RandomAccessFile(test1, "rw"); 191 fd = raf.getFD(); 192 fos = new FileOutputStream(fd); 193 fis = new FileInputStream(fd); 194 } finally { 195 try { 196 if (fis != null) fis.close(); 197 if (fos != null) fos.close(); 198 if (!fd.valid()) { 199 throw new RuntimeException("FileDescriptor should be valid"); 200 } 201 if (raf != null) raf.close(); 202 if (fd.valid()) { 203 throw new RuntimeException("close() called and FileDescriptor still valid"); 204 } 205 } finally { 206 if (raf != null) raf.close(); 207 test1.delete(); 208 } 209 } 210 211 /* 212 * Close out in different order to ensure FD is not 213 * closed out too early 214 */ 215 File test2 = new File("test2"); 216 try { 217 raf = new RandomAccessFile(test2, "rw"); 218 fd = raf.getFD(); 219 fos = new FileOutputStream(fd); 220 fis = new FileInputStream(fd); 221 } finally { 222 try { 223 if (raf != null) raf.close(); 224 if (fos != null) fos.close(); 225 if (!fd.valid()) { 226 throw new RuntimeException("FileDescriptor should be valid"); 227 } 228 if (fis != null) fis.close(); 229 if (fd.valid()) { 230 throw new RuntimeException("close() called and FileDescriptor still valid"); 231 } 232 } finally { 233 test2.delete(); 234 } 235 } 236 237 // one more time, fos first this time 238 File test3 = new File("test3"); 239 try { 240 raf = new RandomAccessFile(test3, "rw"); 241 fd = raf.getFD(); 242 fos = new FileOutputStream(fd); 243 fis = new FileInputStream(fd); 244 } finally { 245 try { 246 if (fos != null) fos.close(); 247 if (raf != null) raf.close(); 248 if (!fd.valid()) { 249 throw new RuntimeException("FileDescriptor should be valid"); 250 } 251 if (fis != null) fis.close(); 252 if (fd.valid()) { 253 throw new RuntimeException("close() called and FileDescriptor still valid"); 254 } 255 } finally { 256 test3.delete(); 257 } 258 } 259 } 260 261 /** 262 * Test concurrent access to the same fd.useCount field 263 */ 264 private static void MultiThreadedFD() throws Exception { 265 RandomAccessFile raf = null; 266 FileDescriptor fd = null; 267 int numThreads = 2; 268 CountDownLatch done = new CountDownLatch(numThreads); 269 OpenClose[] fileOpenClose = new OpenClose[numThreads]; 270 File MultipleThreadedFD = new File("MultipleThreadedFD"); 271 try { 272 raf = new RandomAccessFile(MultipleThreadedFD, "rw"); 273 fd = raf.getFD(); 274 for(int count=0;count<numThreads;count++) { 275 fileOpenClose[count] = new OpenClose(fd, done); 276 fileOpenClose[count].start(); 277 } 278 done.await(); 279 } finally { 280 try { 281 if(raf != null) raf.close(); 282 // fd should now no longer be valid 283 if(fd.valid()) { 284 throw new RuntimeException("FileDescriptor should not be valid"); 285 } 286 // OpenClose thread tests failed 287 if(fail) { 288 throw new RuntimeException("OpenClose thread tests failed."); 289 } 290 } finally { 291 MultipleThreadedFD.delete(); 292 } 293 } 294 } 295 296 /** 297 * A thread which will open and close a number of FileInputStreams and 298 * FileOutputStreams referencing the same native file descriptor. 299 */ 300 private static class OpenClose extends Thread { 301 private FileDescriptor fd = null; 302 private CountDownLatch done; 303 FileInputStream[] fisArray = new FileInputStream[numFiles]; 304 FileOutputStream[] fosArray = new FileOutputStream[numFiles]; 305 306 OpenClose(FileDescriptor filedescriptor, CountDownLatch done) { 307 this.fd = filedescriptor; 308 this.done = done; 309 } 310 311 public void run() { 312 try { 313 for(int i=0;i<numFiles;i++) { 314 fisArray[i] = new FileInputStream(fd); 315 fosArray[i] = new FileOutputStream(fd); 316 } 317 318 // Now close out 319 for(int i=0;i<numFiles;i++) { 320 if(fisArray[i] != null) fisArray[i].close(); 321 if(fosArray[i] != null) fosArray[i].close(); 322 } 323 324 } catch(IOException ioe) { 325 System.out.println("OpenClose encountered IO issue :" + ioe); 326 fail = true; 327 } finally { 328 if (!fd.valid()) { // fd should still be valid given RAF reference 329 System.out.println("OpenClose: FileDescriptor should be valid"); 330 fail = true; 331 } 332 done.countDown(); 333 } 334 } 335 } 336 }