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
31import java.io.*;
32import java.nio.channels.FileChannel;
33import java.nio.channels.FileLock;
34import java.util.concurrent.CountDownLatch;
35
36public class Sharing {
37
38    static final int numFiles = 10;
39    static volatile 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}
409