blob: ab65a814f0b4c3b87396626a2b29fc355205b3f9 [file] [log] [blame]
dfuchsa0941c22014-07-07 15:31:07 +02001/*
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 */
41import java.io.File;
42import java.io.IOException;
43import java.nio.channels.FileChannel;
44import java.nio.file.Paths;
45import java.nio.file.StandardOpenOption;
46import java.util.ArrayList;
47import java.util.List;
48import java.util.UUID;
49import java.util.logging.FileHandler;
50import java.util.logging.Level;
51import java.util.logging.LogRecord;
52public class CheckZombieLockTest {
53
dfuchs7741a3a2014-08-19 17:11:28 +020054 private static final String WRITABLE_DIR = "writable-lockfile-dir";
dfuchsa0941c22014-07-07 15:31:07 +020055 private static volatile boolean supportsLocking = true;
56
57 static enum TestCase {
dfuchs7741a3a2014-08-19 17:11:28 +020058 WRITABLE, // just verifies that we can create a file in our 'writable-lockfile-dir'
dfuchsa0941c22014-07-07 15:31:07 +020059 CLOSE, // checks that closing a FileHandler removes its lock file
dfuchs7741a3a2014-08-19 17:11:28 +020060 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
dfuchsa0941c22014-07-07 15:31:07 +020062 REUSE, // verifies that zombie lock files can be reused
dfuchs7741a3a2014-08-19 17:11:28 +020063 CLEANUP // removes "writable-lockfile-dir"
dfuchsa0941c22014-07-07 15:31:07 +020064 };
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();
dfuchs7741a3a2014-08-19 17:11:28 +020070 System.out.println("Writable dir is: " + writableDir.getAbsolutePath());
dfuchsa0941c22014-07-07 15:31:07 +020071 // 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;
dfuchs7741a3a2014-08-19 17:11:28 +0200107 default: throw new RuntimeException("No such test case: " + arg);
dfuchsa0941c22014-07-07 15:31:07 +0200108 }
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)) {
dfuchs7741a3a2014-08-19 17:11:28 +0200123 throw new IOException("Can't create " + file + "\n\tUnable to run test");
dfuchsa0941c22014-07-07 15:31:07 +0200124 } 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)) {
dfuchs7741a3a2014-08-19 17:11:28 +0200162 throw new IOException("Can't create fake lock file: " + fakeLock);
dfuchsa0941c22014-07-07 15:31:07 +0200163 }
164 try {
165 List<File> before = listLocks(writableDir, true);
dfuchs7741a3a2014-08-19 17:11:28 +0200166 System.out.println("before: " + before.size() + " locks found");
dfuchsa0941c22014-07-07 15:31:07 +0200167 FileHandler handler = createFileHandler(writableDir);
dfuchs7741a3a2014-08-19 17:11:28 +0200168 System.out.println("handler created: " + handler);
dfuchsa0941c22014-07-07 15:31:07 +0200169 List<File> after = listLocks(writableDir, true);
170 System.out.println("after creating handler: " + after.size() + " locks found");
171 handler.close();
dfuchs7741a3a2014-08-19 17:11:28 +0200172 System.out.println("handler closed: " + handler);
dfuchsa0941c22014-07-07 15:31:07 +0200173 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()) {
dfuchs7741a3a2014-08-19 17:11:28 +0200177 throw new RuntimeException("Zombie lock file detected: " + afterClose);
dfuchsa0941c22014-07-07 15:31:07 +0200178 }
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);
dfuchs7741a3a2014-08-19 17:11:28 +0200189 System.out.println("before: " + before.size() + " locks found");
dfuchsa0941c22014-07-07 15:31:07 +0200190 try {
191 if (!before.isEmpty()) {
dfuchs7741a3a2014-08-19 17:11:28 +0200192 throw new RuntimeException("Expected no lock file! Found: " + before);
dfuchsa0941c22014-07-07 15:31:07 +0200193 }
194 } finally {
195 before.stream().forEach(CheckZombieLockTest::delete);
196 }
197
198 FileHandler handler1 = createFileHandler(writableDir);
dfuchs7741a3a2014-08-19 17:11:28 +0200199 System.out.println("handler created: " + handler1);
dfuchsa0941c22014-07-07 15:31:07 +0200200 List<File> after = listLocks(writableDir, true);
201 System.out.println("after creating handler: " + after.size() + " locks found");
202 if (after.size() != 1) {
dfuchs7741a3a2014-08-19 17:11:28 +0200203 throw new RuntimeException("Unexpected number of lock files found for "
204 + handler1 + ": " + after);
dfuchsa0941c22014-07-07 15:31:07 +0200205 }
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()) {
dfuchs7741a3a2014-08-19 17:11:28 +0200212 throw new RuntimeException("Unexpected number of lock files found for "
213 + handler1 + ": " + after);
dfuchsa0941c22014-07-07 15:31:07 +0200214 }
215 if (!createFile(lock, false)) {
dfuchs7741a3a2014-08-19 17:11:28 +0200216 throw new IOException("Can't create fake lock file: " + lock);
dfuchsa0941c22014-07-07 15:31:07 +0200217 }
218 try {
219 before = listLocks(writableDir, true);
dfuchs7741a3a2014-08-19 17:11:28 +0200220 System.out.println("before: " + before.size() + " locks found");
dfuchsa0941c22014-07-07 15:31:07 +0200221 if (before.size() != 1) {
dfuchs7741a3a2014-08-19 17:11:28 +0200222 throw new RuntimeException("Unexpected number of lock files found: "
223 + before + " expected [" + lock + "].");
dfuchsa0941c22014-07-07 15:31:07 +0200224 }
225 FileHandler handler2 = createFileHandler(writableDir);
dfuchs7741a3a2014-08-19 17:11:28 +0200226 System.out.println("handler created: " + handler2);
dfuchsa0941c22014-07-07 15:31:07 +0200227 after = listLocks(writableDir, true);
228 System.out.println("after creating handler: " + after.size() + " locks found");
229 after.removeAll(before);
230 if (!after.isEmpty()) {
dfuchs7741a3a2014-08-19 17:11:28 +0200231 throw new RuntimeException("Unexpected lock file found: " + after
dfuchsa0941c22014-07-07 15:31:07 +0200232 + "\n\t" + lock + " should have been reused");
233 }
234 handler2.close();
dfuchs7741a3a2014-08-19 17:11:28 +0200235 System.out.println("handler closed: " + handler2);
dfuchsa0941c22014-07-07 15:31:07 +0200236 List<File> afterClose = listLocks(writableDir, true);
237 System.out.println("after closing handler: " + afterClose.size() + " locks found");
238 if (!afterClose.isEmpty()) {
dfuchs7741a3a2014-08-19 17:11:28 +0200239 throw new RuntimeException("Zombie lock file detected: " + afterClose);
dfuchsa0941c22014-07-07 15:31:07 +0200240 }
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) {
dfuchs7741a3a2014-08-19 17:11:28 +0200248 System.out.println("locked: " + lock);
dfuchsa0941c22014-07-07 15:31:07 +0200249 handler2 = createFileHandler(writableDir);
dfuchs7741a3a2014-08-19 17:11:28 +0200250 System.out.println("handler created: " + handler2);
dfuchsa0941c22014-07-07 15:31:07 +0200251 after = listLocks(writableDir, true);
dfuchs7741a3a2014-08-19 17:11:28 +0200252 System.out.println("after creating handler: " + after.size()
253 + " locks found");
dfuchsa0941c22014-07-07 15:31:07 +0200254 after.removeAll(before);
255 if (after.size() != 1) {
dfuchs7741a3a2014-08-19 17:11:28 +0200256 throw new RuntimeException("Unexpected lock files found: " + after
dfuchsa0941c22014-07-07 15:31:07 +0200257 + "\n\t" + lock + " should not have been reused");
258 }
259 } else {
dfuchs7741a3a2014-08-19 17:11:28 +0200260 throw new RuntimeException("Failed to lock: " + lock);
dfuchsa0941c22014-07-07 15:31:07 +0200261 }
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);
dfuchs7741a3a2014-08-19 17:11:28 +0200277 System.out.println("before: " + before.size() + " locks found");
dfuchsa0941c22014-07-07 15:31:07 +0200278 try {
279 if (first && !before.isEmpty()) {
dfuchs7741a3a2014-08-19 17:11:28 +0200280 throw new RuntimeException("Expected no lock file! Found: " + before);
dfuchsa0941c22014-07-07 15:31:07 +0200281 } else if (!first && before.size() != 1) {
dfuchs7741a3a2014-08-19 17:11:28 +0200282 throw new RuntimeException("Expected a single lock file! Found: " + before);
dfuchsa0941c22014-07-07 15:31:07 +0200283 }
284 } finally {
285 before.stream().forEach(CheckZombieLockTest::delete);
286 }
287 FileHandler handler = createFileHandler(writableDir);
dfuchs7741a3a2014-08-19 17:11:28 +0200288 System.out.println("handler created: " + handler);
dfuchsa0941c22014-07-07 15:31:07 +0200289 List<File> after = listLocks(writableDir, true);
290 System.out.println("after creating handler: " + after.size() + " locks found");
291 if (after.size() != 1) {
dfuchs7741a3a2014-08-19 17:11:28 +0200292 throw new RuntimeException("Unexpected number of lock files found for "
293 + handler + ": " + after);
dfuchsa0941c22014-07-07 15:31:07 +0200294 }
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);
dfuchs7741a3a2014-08-19 17:11:28 +0200312 // Create a writable directory here (%t/writable-lockfile-dir)
dfuchsa0941c22014-07-07 15:31:07 +0200313 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
dfuchs7741a3a2014-08-19 17:11:28 +0200321 final String uniqueFileName = UUID.randomUUID().toString()+".lck";
dfuchsa0941c22014-07-07 15:31:07 +0200322 try {
323 FileChannel fc = FileChannel.open(Paths.get(writableDir.getAbsolutePath(),
dfuchs7741a3a2014-08-19 17:11:28 +0200324 uniqueFileName),
dfuchsa0941c22014-07-07 15:31:07 +0200325 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 }
dfuchs7741a3a2014-08-19 17:11:28 +0200334 } catch (IOException t) {
dfuchsa0941c22014-07-07 15:31:07 +0200335 // should not happen
dfuchs7741a3a2014-08-19 17:11:28 +0200336 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);
dfuchsa0941c22014-07-07 15:31:07 +0200339 }
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}