blob: f0b0a930c68f65b6b1daa71c8e1463c729f78c0d [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
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 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 "+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}