1 /*
2  * Copyright (c) 2007, 2018, 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  *
26  * @test
27  * @modules java.base/java.io:open
28  * @library /test/lib
29  * @build jdk.test.lib.util.FileUtils UnreferencedFOSClosesFd
30  * @bug 6524062
31  * @summary Test to ensure that the fd is closed if left unreferenced
32  * @run main/othervm UnreferencedFOSClosesFd
33  */
34 import java.io.File;
35 import java.io.FileDescriptor;
36 import java.io.FileNotFoundException;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.lang.management.ManagementFactory;
40 import java.lang.management.OperatingSystemMXBean;
41 import java.lang.ref.Reference;
42 import java.lang.ref.ReferenceQueue;
43 import java.lang.ref.WeakReference;
44 import java.lang.reflect.Field;
45 import java.nio.file.Path;
46 import java.util.ArrayDeque;
47 import java.util.HashSet;
48 import java.util.concurrent.atomic.AtomicInteger;
49 
50 import com.sun.management.UnixOperatingSystemMXBean;
51 
52 import jdk.test.lib.util.FileUtils;
53 
54 public class UnreferencedFOSClosesFd {
55 
56     static final String FILE_NAME = "empty.txt";
57 
58     /**
59      * Subclass w/ no overrides; not close.
60      * Cleanup should be via the Cleaner.
61      */
62     public static class StreamOverrides extends FileOutputStream {
63 
64         protected final AtomicInteger closeCounter;
65 
StreamOverrides(String name)66         public StreamOverrides(String name) throws FileNotFoundException {
67             super(name);
68             closeCounter = new AtomicInteger(0);
69         }
70 
closeCounter()71         final AtomicInteger closeCounter() {
72             return closeCounter;
73         }
74     }
75 
76     /**
77      * Subclass overrides close.
78      * Cleanup should be via AltFinalizer calling close().
79      */
80     public static class StreamOverridesClose extends StreamOverrides {
81 
StreamOverridesClose(String name)82         public StreamOverridesClose(String name) throws FileNotFoundException {
83             super(name);
84         }
85 
close()86         public void close() throws IOException {
87             closeCounter.incrementAndGet();
88             super.close();
89         }
90     }
91 
92     /**
93      * Subclass overrides finalize and close.
94      * Cleanup should be via the Cleaner.
95      */
96     public static class StreamOverridesFinalize extends StreamOverrides {
97 
StreamOverridesFinalize(String name)98         public StreamOverridesFinalize(String name) throws FileNotFoundException {
99             super(name);
100         }
101 
102         @SuppressWarnings({"deprecation","removal"})
finalize()103         protected void finalize() throws IOException, Throwable {
104             super.finalize();
105         }
106     }
107 
108     /**
109      * Subclass overrides finalize and close.
110      * Cleanup should be via the Cleaner.
111      */
112     public static class StreamOverridesFinalizeClose extends StreamOverridesClose {
113 
StreamOverridesFinalizeClose(String name)114         public StreamOverridesFinalizeClose(String name) throws FileNotFoundException {
115             super(name);
116         }
117 
118         @SuppressWarnings({"deprecation","removal"})
finalize()119         protected void finalize() throws IOException, Throwable {
120             super.finalize();
121         }
122     }
123 
124     /**
125      * Main runs each test case and reports number of failures.
126      */
main(String argv[])127     public static void main(String argv[]) throws Exception {
128 
129         File inFile = new File(System.getProperty("test.dir", "."), FILE_NAME);
130         inFile.createNewFile();
131         inFile.deleteOnExit();
132 
133         String name = inFile.getPath();
134 
135         FileUtils.listFileDescriptors(System.out);
136         long fdCount0 = getFdCount();
137 
138         int failCount = 0;
139         failCount += test(new FileOutputStream(name));
140 
141         failCount += test(new StreamOverrides(name));
142 
143         failCount += test(new StreamOverridesClose(name));
144 
145         failCount += test(new StreamOverridesFinalize(name));
146 
147         failCount += test(new StreamOverridesFinalizeClose(name));
148 
149         if (failCount > 0) {
150             throw new AssertionError("Failed test count: " + failCount);
151         }
152 
153         // Check the final count of open file descriptors
154         long fdCount = getFdCount();
155         if (fdCount != fdCount0) {
156             System.out.printf("initial count of open file descriptors: %d%n", fdCount0);
157             System.out.printf("final count of open file descriptors: %d%n", fdCount);
158             FileUtils.listFileDescriptors(System.out);
159         }
160     }
161 
162     // Get the count of open file descriptors, or -1 if not available
getFdCount()163     private static long getFdCount() {
164         OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean();
165         return  (mxBean instanceof UnixOperatingSystemMXBean)
166                 ? ((UnixOperatingSystemMXBean) mxBean).getOpenFileDescriptorCount()
167                 : -1L;
168     }
169 
test(FileOutputStream fos)170     private static int test(FileOutputStream fos) throws Exception {
171 
172         try {
173             System.out.printf("%nTesting %s%n", fos.getClass().getName());
174 
175             // Prepare to wait for FOS to be reclaimed
176             ReferenceQueue<Object> queue = new ReferenceQueue<>();
177             HashSet<Reference<?>> pending = new HashSet<>();
178             WeakReference<FileOutputStream> msWeak = new WeakReference<>(fos, queue);
179             pending.add(msWeak);
180 
181             FileDescriptor fd = fos.getFD();
182             WeakReference<FileDescriptor> fdWeak = new WeakReference<>(fd, queue);
183             pending.add(fdWeak);
184 
185             Field fdField = FileDescriptor.class.getDeclaredField("fd");
186             fdField.setAccessible(true);
187             int ffd = fdField.getInt(fd);
188 
189             Field cleanupField = FileDescriptor.class.getDeclaredField("cleanup");
190             cleanupField.setAccessible(true);
191             Object cleanup = cleanupField.get(fd);
192             System.out.printf("  cleanup: %s, ffd: %d, cf: %s%n", cleanup, ffd, cleanupField);
193             if (cleanup == null) {
194                 throw new RuntimeException("cleanup should not be null");
195             }
196 
197             WeakReference<Object> cleanupWeak = new WeakReference<>(cleanup, queue);
198             pending.add(cleanupWeak);
199             System.out.printf("    fdWeak: %s%n    msWeak: %s%n    cleanupWeak: %s%n",
200                     fdWeak, msWeak, cleanupWeak);
201 
202             AtomicInteger closeCounter = fos instanceof StreamOverrides
203                     ? ((StreamOverrides) fos).closeCounter() : null;
204 
205             Reference<?> r;
206             while (((r = queue.remove(1000L)) != null)
207                     || !pending.isEmpty()) {
208                 System.out.printf("    r: %s, pending: %d%n",
209                         r, pending.size());
210                 if (r != null) {
211                     pending.remove(r);
212                 } else {
213                     fos = null;
214                     fd = null;
215                     cleanup = null;
216                     System.gc();  // attempt to reclaim them
217                 }
218             }
219             Reference.reachabilityFence(fd);
220             Reference.reachabilityFence(fos);
221             Reference.reachabilityFence(cleanup);
222 
223             // Confirm the correct number of calls to close depending on the cleanup type
224             if (closeCounter != null && closeCounter.get() > 0) {
225                 throw new RuntimeException("Close should not have been called: count: " + closeCounter);
226             }
227         } catch (Exception ex) {
228             ex.printStackTrace(System.out);
229             return 1;
230         }
231         return 0;
232     }
233 }
234