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