1 /* 2 * Copyright (c) 2014, 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 8048020 27 * @author Daniel Fuchs 28 * @summary Regression on java.util.logging.FileHandler. 29 * The fix is to avoid filling up the file system with zombie lock files. 30 * 31 * @run main/othervm CheckZombieLockTest WRITABLE CLOSE CLEANUP 32 * @run main/othervm CheckZombieLockTest CLEANUP 33 * @run main/othervm CheckZombieLockTest WRITABLE 34 * @run main/othervm CheckZombieLockTest CREATE_FIRST 35 * @run main/othervm CheckZombieLockTest CREATE_NEXT 36 * @run main/othervm CheckZombieLockTest CREATE_NEXT 37 * @run main/othervm CheckZombieLockTest CLEANUP 38 * @run main/othervm CheckZombieLockTest REUSE 39 * @run main/othervm CheckZombieLockTest CLEANUP 40 */ 41 import java.io.File; 42 import java.io.IOException; 43 import java.nio.channels.FileChannel; 44 import java.nio.file.Paths; 45 import java.nio.file.StandardOpenOption; 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.UUID; 49 import java.util.logging.FileHandler; 50 import java.util.logging.Level; 51 import java.util.logging.LogRecord; 52 public class CheckZombieLockTest { 53 54 private static final String WRITABLE_DIR = "writable-lockfile-dir"; 55 private static volatile boolean supportsLocking = true; 56 57 static enum TestCase { 58 WRITABLE, // just verifies that we can create a file in our 'writable-lockfile-dir' 59 CLOSE, // checks that closing a FileHandler removes its lock file 60 CREATE_FIRST, // verifies that 'writable-lockfile-dir' contains no lock, then creates a first FileHandler. 61 CREATE_NEXT, // verifies that 'writable-lockfile-dir' contains a single lock, then creates the next FileHandler 62 REUSE, // verifies that zombie lock files can be reused 63 CLEANUP // removes "writable-lockfile-dir" 64 }; 65 66 public static void main(String... args) throws IOException { 67 // we'll base all file creation attempts on the system temp directory, 68 // %t 69 File writableDir = setup(); 70 System.out.println("Writable dir is: " + writableDir.getAbsolutePath()); 71 // we now have one writable directory to work with: 72 // writableDir 73 if (args == null || args.length == 0) { 74 args = new String[] { "WRITABLE", "CLOSE", "CLEANUP" }; 75 } 76 try { 77 runTests(writableDir, args); 78 } catch (RuntimeException | IOException | Error x) { 79 // some error occured: cleanup 80 delete(writableDir); 81 throw x; 82 } 83 } 84 85 /** 86 * @param writableDir in which log and lock file are created 87 * @throws SecurityException 88 * @throws RuntimeException 89 * @throws IOException 90 */ 91 private static void runTests(File writableDir, String... args) throws SecurityException, 92 RuntimeException, IOException { 93 for (String arg : args) { 94 switch(TestCase.valueOf(arg)) { 95 // Test 1: makes sure we can create FileHandler in writable directory 96 case WRITABLE: checkWritable(writableDir); break; 97 // Test 2: verifies that FileHandler.close() cleans up its lock file 98 case CLOSE: testFileHandlerClose(writableDir); break; 99 // Test 3: creates the first file handler 100 case CREATE_FIRST: testFileHandlerCreate(writableDir, true); break; 101 // Test 4, 5, ... creates the next file handler 102 case CREATE_NEXT: testFileHandlerCreate(writableDir, false); break; 103 // Checks that zombie lock files are reused appropriatly 104 case REUSE: testFileHandlerReuse(writableDir); break; 105 // Removes the writableDir 106 case CLEANUP: delete(writableDir); break; 107 default: throw new RuntimeException("No such test case: " + arg); 108 } 109 } 110 } 111 112 /** 113 * @param writableDir in which log and lock file are created 114 * @throws SecurityException 115 * @throws RuntimeException 116 * @throws IOException 117 */ 118 private static void checkWritable(File writableDir) throws SecurityException, 119 RuntimeException, IOException { 120 // Test 1: make sure we can create/delete files in the writable dir. 121 final File file = new File(writableDir, "test.txt"); 122 if (!createFile(file, false)) { 123 throw new IOException("Can't create " + file + "\n\tUnable to run test"); 124 } else { 125 delete(file); 126 } 127 } 128 129 130 private static FileHandler createFileHandler(File writableDir) throws SecurityException, 131 RuntimeException, IOException { 132 // Test 1: make sure we can create FileHandler in writable directory 133 try { 134 FileHandler handler = new FileHandler("%t/" + WRITABLE_DIR + "/log.log"); 135 handler.publish(new LogRecord(Level.INFO, handler.toString())); 136 handler.flush(); 137 return handler; 138 } catch (IOException ex) { 139 throw new RuntimeException("Test failed: should have been able" 140 + " to create FileHandler for " + "%t/" + WRITABLE_DIR 141 + "/log.log in writable directory.", ex); 142 } 143 } 144 145 private static List<File> listLocks(File writableDir, boolean print) 146 throws IOException { 147 List<File> locks = new ArrayList<>(); 148 for (File f : writableDir.listFiles()) { 149 if (print) { 150 System.out.println("Found file: " + f.getName()); 151 } 152 if (f.getName().endsWith(".lck")) { 153 locks.add(f); 154 } 155 } 156 return locks; 157 } 158 159 private static void testFileHandlerClose(File writableDir) throws IOException { 160 File fakeLock = new File(writableDir, "log.log.lck"); 161 if (!createFile(fakeLock, false)) { 162 throw new IOException("Can't create fake lock file: " + fakeLock); 163 } 164 try { 165 List<File> before = listLocks(writableDir, true); 166 System.out.println("before: " + before.size() + " locks found"); 167 FileHandler handler = createFileHandler(writableDir); 168 System.out.println("handler created: " + handler); 169 List<File> after = listLocks(writableDir, true); 170 System.out.println("after creating handler: " + after.size() + " locks found"); 171 handler.close(); 172 System.out.println("handler closed: " + handler); 173 List<File> afterClose = listLocks(writableDir, true); 174 System.out.println("after closing handler: " + afterClose.size() + " locks found"); 175 afterClose.removeAll(before); 176 if (!afterClose.isEmpty()) { 177 throw new RuntimeException("Zombie lock file detected: " + afterClose); 178 } 179 } finally { 180 if (fakeLock.canRead()) delete(fakeLock); 181 } 182 List<File> finalLocks = listLocks(writableDir, false); 183 System.out.println("After cleanup: " + finalLocks.size() + " locks found"); 184 } 185 186 187 private static void testFileHandlerReuse(File writableDir) throws IOException { 188 List<File> before = listLocks(writableDir, true); 189 System.out.println("before: " + before.size() + " locks found"); 190 try { 191 if (!before.isEmpty()) { 192 throw new RuntimeException("Expected no lock file! Found: " + before); 193 } 194 } finally { 195 before.stream().forEach(CheckZombieLockTest::delete); 196 } 197 198 FileHandler handler1 = createFileHandler(writableDir); 199 System.out.println("handler created: " + handler1); 200 List<File> after = listLocks(writableDir, true); 201 System.out.println("after creating handler: " + after.size() + " locks found"); 202 if (after.size() != 1) { 203 throw new RuntimeException("Unexpected number of lock files found for " 204 + handler1 + ": " + after); 205 } 206 final File lock = after.get(0); 207 after.clear(); 208 handler1.close(); 209 after = listLocks(writableDir, true); 210 System.out.println("after closing handler: " + after.size() + " locks found"); 211 if (!after.isEmpty()) { 212 throw new RuntimeException("Unexpected number of lock files found for " 213 + handler1 + ": " + after); 214 } 215 if (!createFile(lock, false)) { 216 throw new IOException("Can't create fake lock file: " + lock); 217 } 218 try { 219 before = listLocks(writableDir, true); 220 System.out.println("before: " + before.size() + " locks found"); 221 if (before.size() != 1) { 222 throw new RuntimeException("Unexpected number of lock files found: " 223 + before + " expected [" + lock + "]."); 224 } 225 FileHandler handler2 = createFileHandler(writableDir); 226 System.out.println("handler created: " + handler2); 227 after = listLocks(writableDir, true); 228 System.out.println("after creating handler: " + after.size() + " locks found"); 229 after.removeAll(before); 230 if (!after.isEmpty()) { 231 throw new RuntimeException("Unexpected lock file found: " + after 232 + "\n\t" + lock + " should have been reused"); 233 } 234 handler2.close(); 235 System.out.println("handler closed: " + handler2); 236 List<File> afterClose = listLocks(writableDir, true); 237 System.out.println("after closing handler: " + afterClose.size() + " locks found"); 238 if (!afterClose.isEmpty()) { 239 throw new RuntimeException("Zombie lock file detected: " + afterClose); 240 } 241 242 if (supportsLocking) { 243 FileChannel fc = FileChannel.open(Paths.get(lock.getAbsolutePath()), 244 StandardOpenOption.CREATE_NEW, StandardOpenOption.APPEND, 245 StandardOpenOption.WRITE); 246 try { 247 if (fc.tryLock() != null) { 248 System.out.println("locked: " + lock); 249 handler2 = createFileHandler(writableDir); 250 System.out.println("handler created: " + handler2); 251 after = listLocks(writableDir, true); 252 System.out.println("after creating handler: " + after.size() 253 + " locks found"); 254 after.removeAll(before); 255 if (after.size() != 1) { 256 throw new RuntimeException("Unexpected lock files found: " + after 257 + "\n\t" + lock + " should not have been reused"); 258 } 259 } else { 260 throw new RuntimeException("Failed to lock: " + lock); 261 } 262 } finally { 263 delete(lock); 264 } 265 } 266 } finally { 267 List<File> finalLocks = listLocks(writableDir, false); 268 System.out.println("end: " + finalLocks.size() + " locks found"); 269 delete(writableDir); 270 } 271 } 272 273 274 private static void testFileHandlerCreate(File writableDir, boolean first) 275 throws IOException { 276 List<File> before = listLocks(writableDir, true); 277 System.out.println("before: " + before.size() + " locks found"); 278 try { 279 if (first && !before.isEmpty()) { 280 throw new RuntimeException("Expected no lock file! Found: " + before); 281 } else if (!first && before.size() != 1) { 282 throw new RuntimeException("Expected a single lock file! Found: " + before); 283 } 284 } finally { 285 before.stream().forEach(CheckZombieLockTest::delete); 286 } 287 FileHandler handler = createFileHandler(writableDir); 288 System.out.println("handler created: " + handler); 289 List<File> after = listLocks(writableDir, true); 290 System.out.println("after creating handler: " + after.size() + " locks found"); 291 if (after.size() != 1) { 292 throw new RuntimeException("Unexpected number of lock files found for " 293 + handler + ": " + after); 294 } 295 } 296 297 298 /** 299 * Setup all the files and directories needed for the tests 300 * 301 * @return writable directory created that needs to be deleted when done 302 * @throws RuntimeException 303 */ 304 private static File setup() throws RuntimeException { 305 // First do some setup in the temporary directory (using same logic as 306 // FileHandler for %t pattern) 307 String tmpDir = System.getProperty("java.io.tmpdir"); // i.e. %t 308 if (tmpDir == null) { 309 tmpDir = System.getProperty("user.home"); 310 } 311 File tmpOrHomeDir = new File(tmpDir); 312 // Create a writable directory here (%t/writable-lockfile-dir) 313 File writableDir = new File(tmpOrHomeDir, WRITABLE_DIR); 314 if (!createFile(writableDir, true)) { 315 throw new RuntimeException("Test setup failed: unable to create" 316 + " writable working directory " 317 + writableDir.getAbsolutePath() ); 318 } 319 320 // try to determine whether file locking is supported 321 final String uniqueFileName = UUID.randomUUID().toString()+".lck"; 322 try { 323 FileChannel fc = FileChannel.open(Paths.get(writableDir.getAbsolutePath(), 324 uniqueFileName), 325 StandardOpenOption.CREATE_NEW, StandardOpenOption.APPEND, 326 StandardOpenOption.DELETE_ON_CLOSE); 327 try { 328 fc.tryLock(); 329 } catch(IOException x) { 330 supportsLocking = false; 331 } finally { 332 fc.close(); 333 } 334 } catch (IOException t) { 335 // should not happen 336 System.err.println("Failed to create new file " + uniqueFileName + 337 " in " + writableDir.getAbsolutePath()); 338 throw new RuntimeException("Test setup failed: unable to run test", t); 339 } 340 return writableDir; 341 } 342 343 /** 344 * @param newFile 345 * @return true if file already exists or creation succeeded 346 */ 347 private static boolean createFile(File newFile, boolean makeDirectory) { 348 if (newFile.exists()) { 349 return true; 350 } 351 if (makeDirectory) { 352 return newFile.mkdir(); 353 } else { 354 try { 355 return newFile.createNewFile(); 356 } catch (IOException ioex) { 357 ioex.printStackTrace(); 358 return false; 359 } 360 } 361 } 362 363 /* 364 * Recursively delete all files starting at specified file 365 */ 366 private static void delete(File f) { 367 if (f != null && f.isDirectory()) { 368 for (File c : f.listFiles()) 369 delete(c); 370 } 371 if (!f.delete()) 372 System.err.println( 373 "WARNING: unable to delete/cleanup writable test directory: " 374 + f ); 375 } 376 }