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