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-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-dir' 59 CLOSE, // checks that closing a FileHandler removes its lock file 60 CREATE_FIRST, // verifies that 'writable-dir' contains no lock, then creates a first FileHandler. 61 CREATE_NEXT, // verifies that 'writable-dir' contains a single lock, then creates the next FileHandler 62 REUSE, // verifies that zombie lock files can be reused 63 CLEANUP // removes "writable-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 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 "+handler1+": "+after); 204 } 205 final File lock = after.get(0); 206 after.clear(); 207 handler1.close(); 208 after = listLocks(writableDir, true); 209 System.out.println("after closing handler: " + after.size() + " locks found"); 210 if (!after.isEmpty()) { 211 throw new RuntimeException("Unexpected number of lock files found for "+handler1+": "+after); 212 } 213 if (!createFile(lock, false)) { 214 throw new IOException("Can't create fake lock file: "+lock); 215 } 216 try { 217 before = listLocks(writableDir, true); 218 System.out.println("before: " +before.size() + " locks found"); 219 if (before.size() != 1) { 220 throw new RuntimeException("Unexpected number of lock files found: "+before+" expected [" 221 +lock+"]."); 222 } 223 FileHandler handler2 = createFileHandler(writableDir); 224 System.out.println("handler created: "+handler2); 225 after = listLocks(writableDir, true); 226 System.out.println("after creating handler: " + after.size() + " locks found"); 227 after.removeAll(before); 228 if (!after.isEmpty()) { 229 throw new RuntimeException("Unexpected lock file found: "+after 230 + "\n\t" + lock + " should have been reused"); 231 } 232 handler2.close(); 233 System.out.println("handler closed: "+handler2); 234 List<File> afterClose = listLocks(writableDir, true); 235 System.out.println("after closing handler: " + afterClose.size() + " locks found"); 236 if (!afterClose.isEmpty()) { 237 throw new RuntimeException("Zombie lock file detected: "+ afterClose); 238 } 239 240 if (supportsLocking) { 241 FileChannel fc = FileChannel.open(Paths.get(lock.getAbsolutePath()), 242 StandardOpenOption.CREATE_NEW, StandardOpenOption.APPEND, 243 StandardOpenOption.WRITE); 244 try { 245 if (fc.tryLock() != null) { 246 System.out.println("locked: "+lock); 247 handler2 = createFileHandler(writableDir); 248 System.out.println("handler created: "+handler2); 249 after = listLocks(writableDir, true); 250 System.out.println("after creating handler: " + after.size() + " locks found"); 251 after.removeAll(before); 252 if (after.size() != 1) { 253 throw new RuntimeException("Unexpected lock files found: "+after 254 + "\n\t" + lock + " should not have been reused"); 255 } 256 } else { 257 throw new RuntimeException("Failed to lock: "+lock); 258 } 259 } finally { 260 delete(lock); 261 } 262 } 263 } finally { 264 List<File> finalLocks = listLocks(writableDir, false); 265 System.out.println("end: " + finalLocks.size() + " locks found"); 266 delete(writableDir); 267 } 268 } 269 270 271 private static void testFileHandlerCreate(File writableDir, boolean first) 272 throws IOException { 273 List<File> before = listLocks(writableDir, true); 274 System.out.println("before: " +before.size() + " locks found"); 275 try { 276 if (first && !before.isEmpty()) { 277 throw new RuntimeException("Expected no lock file! Found: "+before); 278 } else if (!first && before.size() != 1) { 279 throw new RuntimeException("Expected a single lock file! Found: "+before); 280 } 281 } finally { 282 before.stream().forEach(CheckZombieLockTest::delete); 283 } 284 FileHandler handler = createFileHandler(writableDir); 285 System.out.println("handler created: "+handler); 286 List<File> after = listLocks(writableDir, true); 287 System.out.println("after creating handler: " + after.size() + " locks found"); 288 if (after.size() != 1) { 289 throw new RuntimeException("Unexpected number of lock files found for "+handler+": "+after); 290 } 291 } 292 293 294 /** 295 * Setup all the files and directories needed for the tests 296 * 297 * @return writable directory created that needs to be deleted when done 298 * @throws RuntimeException 299 */ 300 private static File setup() throws RuntimeException { 301 // First do some setup in the temporary directory (using same logic as 302 // FileHandler for %t pattern) 303 String tmpDir = System.getProperty("java.io.tmpdir"); // i.e. %t 304 if (tmpDir == null) { 305 tmpDir = System.getProperty("user.home"); 306 } 307 File tmpOrHomeDir = new File(tmpDir); 308 // Create a writable directory here (%t/writable-dir) 309 File writableDir = new File(tmpOrHomeDir, WRITABLE_DIR); 310 if (!createFile(writableDir, true)) { 311 throw new RuntimeException("Test setup failed: unable to create" 312 + " writable working directory " 313 + writableDir.getAbsolutePath() ); 314 } 315 316 // try to determine whether file locking is supported 317 try { 318 FileChannel fc = FileChannel.open(Paths.get(writableDir.getAbsolutePath(), 319 UUID.randomUUID().toString()+".lck"), 320 StandardOpenOption.CREATE_NEW, StandardOpenOption.APPEND, 321 StandardOpenOption.DELETE_ON_CLOSE); 322 try { 323 fc.tryLock(); 324 } catch(IOException x) { 325 supportsLocking = false; 326 } finally { 327 fc.close(); 328 } 329 } catch(Throwable t) { 330 // should not happen 331 t.printStackTrace(); 332 } 333 return writableDir; 334 } 335 336 /** 337 * @param newFile 338 * @return true if file already exists or creation succeeded 339 */ 340 private static boolean createFile(File newFile, boolean makeDirectory) { 341 if (newFile.exists()) { 342 return true; 343 } 344 if (makeDirectory) { 345 return newFile.mkdir(); 346 } else { 347 try { 348 return newFile.createNewFile(); 349 } catch (IOException ioex) { 350 ioex.printStackTrace(); 351 return false; 352 } 353 } 354 } 355 356 /* 357 * Recursively delete all files starting at specified file 358 */ 359 private static void delete(File f) { 360 if (f != null && f.isDirectory()) { 361 for (File c : f.listFiles()) 362 delete(c); 363 } 364 if (!f.delete()) 365 System.err.println( 366 "WARNING: unable to delete/cleanup writable test directory: " 367 + f ); 368 } 369 }