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 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.FilePermission;
26 import java.io.IOException;
27 import java.lang.ref.Reference;
28 import java.lang.ref.ReferenceQueue;
29 import java.lang.ref.WeakReference;
30 import java.nio.file.Files;
31 import java.nio.file.Paths;
32 import java.security.CodeSource;
33 import java.security.Permission;
34 import java.security.PermissionCollection;
35 import java.security.Permissions;
36 import java.security.Policy;
37 import java.security.ProtectionDomain;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.Enumeration;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Properties;
44 import java.util.Set;
45 import java.util.UUID;
46 import java.util.concurrent.Callable;
47 import java.util.concurrent.atomic.AtomicBoolean;
48 import java.util.logging.FileHandler;
49 import java.util.logging.Handler;
50 import java.util.logging.LogManager;
51 import java.util.logging.Logger;
52 import java.util.logging.LoggingPermission;
53 
54 /**
55  * @test
56  * @bug 8060132
57  * @summary tests that FileHandlers configured on abstract nodes in logging.properties
58  *          will be closed by reset().
59  * @run main/othervm ParentLoggerWithHandlerGC UNSECURE
60  * @run main/othervm ParentLoggerWithHandlerGC SECURE
61  * @author danielfuchs
62  * @key randomness
63  */
64 public class ParentLoggerWithHandlerGC {
65 
66     /**
67      * We will test the handling of abstract logger nodes with file handlers in
68      * two configurations:
69      * UNSECURE: No security manager.
70      * SECURE: With the security manager present - and the required
71      *         permissions granted.
72      */
73     public static enum TestCase {
74         UNSECURE, SECURE;
run(Properties propertyFile)75         public void run(Properties propertyFile) throws Exception {
76             System.out.println("Running test case: " + name());
77             Configure.setUp(this, propertyFile);
78             test(this.name() + " " + propertyFile.getProperty("test.name"), propertyFile);
79         }
80     }
81 
82 
83     private static final String PREFIX =
84             "FileHandler-" + UUID.randomUUID() + ".log";
85     private static final String userDir = System.getProperty("user.dir", ".");
86     private static final boolean userDirWritable = Files.isWritable(Paths.get(userDir));
87 
88     static enum ConfigMode { DEFAULT, ENSURE_CLOSE_ON_RESET_TRUE, ENSURE_CLOSE_ON_RESET_FALSE }
89 
90     private static final List<Properties> properties;
91     static {
92         Properties props1 = new Properties();
93         props1.setProperty("test.name", "parent logger with handler");
94         props1.setProperty("test.config.mode", ConfigMode.DEFAULT.name());
FileHandler.class.getName()95         props1.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
String.valueOf(Integer.MAX_VALUE)96         props1.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
FileHandler.class.getName()97         props1.setProperty(FileHandler.class.getName() + ".level", "ALL");
FileHandler.class.getName()98         props1.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
99         props1.setProperty("com.foo.handlers", FileHandler.class.getName());
100         props1.setProperty("com.bar.level", "FINEST");
101 
102         Properties props2 = new Properties();
103         props2.setProperty("test.name", "parent logger with handler");
104         props2.setProperty("test.config.mode", ConfigMode.ENSURE_CLOSE_ON_RESET_TRUE.name());
FileHandler.class.getName()105         props2.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
String.valueOf(Integer.MAX_VALUE)106         props2.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
FileHandler.class.getName()107         props2.setProperty(FileHandler.class.getName() + ".level", "ALL");
FileHandler.class.getName()108         props2.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
109         props2.setProperty("com.foo.handlers", FileHandler.class.getName());
110         props2.setProperty("com.foo.handlers.ensureCloseOnReset", "true");
111         props2.setProperty("com.bar.level", "FINEST");
112 
113         Properties props3 = new Properties();
114         props3.setProperty("test.name", "parent logger with handler");
115         props3.setProperty("test.config.mode", ConfigMode.ENSURE_CLOSE_ON_RESET_FALSE.name());
FileHandler.class.getName()116         props3.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
String.valueOf(Integer.MAX_VALUE)117         props3.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
FileHandler.class.getName()118         props3.setProperty(FileHandler.class.getName() + ".level", "ALL");
FileHandler.class.getName()119         props3.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
120         props3.setProperty("com.foo.handlers", FileHandler.class.getName());
121         props3.setProperty("com.foo.handlers.ensureCloseOnReset", "false");
122         props3.setProperty("com.bar.level", "FINEST");
123 
124         properties = Collections.unmodifiableList(Arrays.asList(
125                     props1, props2, props3));
126     }
127 
main(String... args)128     public static void main(String... args) throws Exception {
129 
130 
131         if (args == null || args.length == 0) {
132             args = new String[] {
133                 TestCase.UNSECURE.name(),
134                 TestCase.SECURE.name(),
135             };
136         }
137 
138         try {
139             for (String testName : args) {
140                 for (Properties propertyFile : properties) {
141                     TestCase test = TestCase.valueOf(testName);
142                     test.run(propertyFile);
143                 }
144             }
145         } finally {
146             if (userDirWritable) {
147                 Configure.doPrivileged(() -> {
148                     // cleanup - delete files that have been created
149                     try {
150                         Files.list(Paths.get(userDir))
151                             .filter((f) -> f.toString().contains(PREFIX))
152                             .forEach((f) -> {
153                                 try {
154                                     System.out.println("deleting " + f);
155                                     Files.delete(f);
156                                 } catch(Throwable t) {
157                                     System.err.println("Failed to delete " + f + ": " + t);
158                                 }
159                             });
160                     } catch(Throwable t) {
161                         System.err.println("Cleanup failed to list files: " + t);
162                         t.printStackTrace();
163                     }
164                 });
165             }
166         }
167     }
168 
169     static class Configure {
170         static Policy policy = null;
171         static final AtomicBoolean allowAll = new AtomicBoolean(false);
setUp(TestCase test, Properties propertyFile)172         static void setUp(TestCase test, Properties propertyFile) {
173             switch (test) {
174                 case SECURE:
175                     if (policy == null && System.getSecurityManager() != null) {
176                         throw new IllegalStateException("SecurityManager already set");
177                     } else if (policy == null) {
178                         policy = new SimplePolicy(TestCase.SECURE, allowAll);
179                         Policy.setPolicy(policy);
180                         System.setSecurityManager(new SecurityManager());
181                     }
182                     if (System.getSecurityManager() == null) {
183                         throw new IllegalStateException("No SecurityManager.");
184                     }
185                     if (policy == null) {
186                         throw new IllegalStateException("policy not configured");
187                     }
188                     break;
189                 case UNSECURE:
190                     if (System.getSecurityManager() != null) {
191                         throw new IllegalStateException("SecurityManager already set");
192                     }
193                     break;
194                 default:
195                     new InternalError("No such testcase: " + test);
196             }
197             doPrivileged(() -> {
198                 try {
199                     ByteArrayOutputStream bytes = new ByteArrayOutputStream();
200                     propertyFile.store(bytes, propertyFile.getProperty("test.name"));
201                     ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray());
202                     LogManager.getLogManager().readConfiguration(bais);
203                 } catch (IOException ex) {
204                     throw new RuntimeException(ex);
205                 }
206             });
207         }
doPrivileged(Runnable run)208         static void doPrivileged(Runnable run) {
209             allowAll.set(true);
210             try {
211                 run.run();
212             } finally {
213                 allowAll.set(false);
214             }
215         }
callPrivileged(Callable<T> call)216         static <T> T callPrivileged(Callable<T> call) throws Exception {
217             allowAll.set(true);
218             try {
219                 return call.call();
220             } finally {
221                 allowAll.set(false);
222             }
223         }
224     }
225 
226     @FunctionalInterface
227     public static interface FileHandlerSupplier {
test()228         public FileHandler test() throws Exception;
229     }
230 
231     static final class TestAssertException extends RuntimeException {
TestAssertException(String msg)232         TestAssertException(String msg) {
233             super(msg);
234         }
235     }
236 
assertEquals(long expected, long received, String msg)237     private static void assertEquals(long expected, long received, String msg) {
238         if (expected != received) {
239             throw new TestAssertException("Unexpected result for " + msg
240                     + ".\n\texpected: " + expected
241                     +  "\n\tactual:   " + received);
242         } else {
243             System.out.println("Got expected " + msg + ": " + received);
244         }
245     }
246 
247 
test(String name, Properties props)248     public static void test(String name, Properties props) throws Exception {
249         ConfigMode configMode = ConfigMode.valueOf(props.getProperty("test.config.mode"));
250         System.out.println("\nTesting: " + name + " mode=" + configMode);
251         if (!userDirWritable) {
252             throw new RuntimeException("Not writable: "+userDir);
253         }
254         switch(configMode) {
255             case DEFAULT:
256             case ENSURE_CLOSE_ON_RESET_TRUE:
257                 testCloseOnResetTrue(name, props); break;
258             case ENSURE_CLOSE_ON_RESET_FALSE:
259                 testCloseOnResetFalse(name, props); break;
260             default:
261                 throw new RuntimeException("Unknwown mode: " + configMode);
262         }
263     }
264 
265 
266     // Test a configuration which has either
267     // com.foo.handlers.ensureCloseOnReset=true, or where
268     // com.foo.handlers.ensureCloseOnReset is not specified.
testCloseOnResetTrue(String name, Properties props)269     public static void testCloseOnResetTrue(String name, Properties props)
270             throws Exception {
271         Logger fooChild = Logger.getLogger("com.foo.child");
272         fooChild.info("hello world");
273         Logger barChild = Logger.getLogger("com.bar.child");
274         barChild.info("hello world");
275 
276         ReferenceQueue<Logger> queue = new ReferenceQueue();
277         WeakReference<Logger> fooRef = new WeakReference<>(Logger.getLogger("com.foo"), queue);
278         if (fooRef.get() != fooChild.getParent()) {
279             throw new RuntimeException("Unexpected parent logger: "
280                     + fooChild.getParent() +"\n\texpected: " + fooRef.get());
281         }
282         WeakReference<Logger> barRef = new WeakReference<>(Logger.getLogger("com.bar"), queue);
283         if (barRef.get() != barChild.getParent()) {
284             throw new RuntimeException("Unexpected parent logger: "
285                     + barChild.getParent() +"\n\texpected: " + barRef.get());
286         }
287         fooChild = barChild = null;
288         Reference<? extends Logger> ref2 = null;
289         while ((ref2 = queue.poll()) == null) {
290             System.gc();
291             Thread.sleep(1000);
292         }
293         Throwable failed = null;
294         try {
295             do {
296                 if (ref2 != barRef) {
297                     throw new RuntimeException("Unexpected reference: "
298                             + ref2 +"\n\texpected: " + barRef);
299                 }
300                 if (ref2.get() != null) {
301                     throw new RuntimeException("Referent not cleared: "
302                             + ref2.get());
303                 }
304                 System.out.println("Got barRef");
305                 System.gc();
306                 Thread.sleep(1000);
307             } while( (ref2 = queue.poll()) != null);
308             System.out.println("Parent logger GCed");
309         } catch(Throwable t) {
310             failed = t;
311         } finally {
312             final Throwable suppressed = failed;
313             Configure.doPrivileged(() -> LogManager.getLogManager().reset());
314             Configure.doPrivileged(() -> {
315                 try {
316                     StringBuilder builder = new StringBuilder();
317                     Files.list(Paths.get(userDir))
318                         .filter((f) -> f.toString().contains(PREFIX))
319                         .filter((f) -> f.toString().endsWith(".lck"))
320                         .forEach((f) -> {
321                                 builder.append(f.toString()).append('\n');
322                         });
323                     if (!builder.toString().isEmpty()) {
324                         throw new RuntimeException("Lock files not cleaned:\n"
325                                 + builder.toString());
326                     }
327                 } catch(RuntimeException | Error x) {
328                     if (suppressed != null) x.addSuppressed(suppressed);
329                     throw x;
330                 } catch(Exception x) {
331                     if (suppressed != null) x.addSuppressed(suppressed);
332                     throw new RuntimeException(x);
333                 }
334             });
335             while ((ref2 = queue.poll()) == null) {
336                 System.gc();
337                 Thread.sleep(1000);
338             }
339             if (ref2 != fooRef) {
340                 throw new RuntimeException("Unexpected reference: "
341                         + ref2 +"\n\texpected: " + fooRef);
342             }
343             if (ref2.get() != null) {
344                 throw new RuntimeException("Referent not cleared: " + ref2.get());
345             }
346             System.out.println("Got fooRef after reset()");
347 
348         }
349         if (failed != null) {
350             // should rarely happen...
351             throw new RuntimeException(failed);
352         }
353 
354     }
355 
getHandlerToClose()356     private static Handler getHandlerToClose() throws Exception {
357         return Configure.callPrivileged(
358                 () -> Logger.getLogger("com.foo.child").getParent().getHandlers()[0]);
359     }
360 
361     // Test a configuration which has com.foo.handlers.ensureCloseOnReset=false
testCloseOnResetFalse(String name, Properties props)362     public static void testCloseOnResetFalse(String name, Properties props)
363             throws Exception {
364         Logger fooChild = Logger.getLogger("com.foo.child");
365         fooChild.info("hello world");
366         Logger barChild = Logger.getLogger("com.bar.child");
367         barChild.info("hello world");
368 
369         Handler toClose = getHandlerToClose();
370 
371         ReferenceQueue<Logger> queue = new ReferenceQueue();
372         WeakReference<Logger> fooRef = new WeakReference<>(Logger.getLogger("com.foo"), queue);
373         if (fooRef.get() != fooChild.getParent()) {
374             throw new RuntimeException("Unexpected parent logger: "
375                     + fooChild.getParent() +"\n\texpected: " + fooRef.get());
376         }
377         WeakReference<Logger> barRef = new WeakReference<>(Logger.getLogger("com.bar"), queue);
378         if (barRef.get() != barChild.getParent()) {
379             throw new RuntimeException("Unexpected parent logger: "
380                     + barChild.getParent() +"\n\texpected: " + barRef.get());
381         }
382         fooChild = barChild = null;
383         Reference<? extends Logger> ref2 = null;
384         Set<WeakReference<Logger>> expectedRefs = new HashSet<>(Arrays.asList(fooRef, barRef));
385         Throwable failed = null;
386         try {
387             int l=0;
388             while (failed == null && !expectedRefs.isEmpty()) {
389                 int max = 60;
390                 while ((ref2 = queue.poll()) == null) {
391                     if (l > 0 && max-- <= 0) {
392                         throw new RuntimeException("Logger #2 not GC'ed!"
393                                 + " max too short (max=60) or "
394                                 + "com.foo.handlers.ensureCloseOnReset=false"
395                                 + " does not work");
396                     }
397                     System.gc();
398                     Thread.sleep(1000);
399                 }
400                 do {
401                     if (!expectedRefs.contains(ref2)) {
402                         throw new RuntimeException("Unexpected reference: "
403                                 + ref2 +"\n\texpected: " + expectedRefs);
404                     }
405                     if (ref2.get() != null) {
406                         throw new RuntimeException("Referent not cleared: "
407                                 + ref2.get());
408                     }
409                     expectedRefs.remove(ref2);
410                     System.out.println("Got "+
411                             (ref2 == barRef ? "barRef"
412                                     : (ref2 == fooRef ? "fooRef"
413                                             : ref2.toString())));
414                     System.gc();
415                     Thread.sleep(1000);
416                     System.out.println("Logger #" + (++l) + " GCed");
417                 } while( (ref2 = queue.poll()) != null);
418             }
419         } catch(Throwable t) {
420             failed = t;
421         } finally {
422             final Throwable suppressed = failed;
423             Configure.doPrivileged(() -> LogManager.getLogManager().reset());
424             Configure.doPrivileged(() -> {
425                 try {
426                     toClose.close();
427                     StringBuilder builder = new StringBuilder();
428                     Files.list(Paths.get(userDir))
429                         .filter((f) -> f.toString().contains(PREFIX))
430                         .filter((f) -> f.toString().endsWith(".lck"))
431                         .forEach((f) -> {
432                                 builder.append(f.toString()).append('\n');
433                         });
434                     if (!builder.toString().isEmpty()) {
435                         throw new RuntimeException("Lock files not cleaned:\n" + builder.toString());
436                     }
437                 } catch(RuntimeException | Error x) {
438                     if (suppressed != null) x.addSuppressed(suppressed);
439                     throw x;
440                 } catch(Exception x) {
441                     if (suppressed != null) x.addSuppressed(suppressed);
442                     throw new RuntimeException(x);
443                 }
444             });
445         }
446         if (failed != null) {
447             // should rarely happen...
448             throw new RuntimeException(failed);
449         }
450 
451     }
452 
453 
454     static final class PermissionsBuilder {
455         final Permissions perms;
PermissionsBuilder()456         public PermissionsBuilder() {
457             this(new Permissions());
458         }
PermissionsBuilder(Permissions perms)459         public PermissionsBuilder(Permissions perms) {
460             this.perms = perms;
461         }
add(Permission p)462         public PermissionsBuilder add(Permission p) {
463             perms.add(p);
464             return this;
465         }
addAll(PermissionCollection col)466         public PermissionsBuilder addAll(PermissionCollection col) {
467             if (col != null) {
468                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
469                     perms.add(e.nextElement());
470                 }
471             }
472             return this;
473         }
toPermissions()474         public Permissions toPermissions() {
475             final PermissionsBuilder builder = new PermissionsBuilder();
476             builder.addAll(perms);
477             return builder.perms;
478         }
479     }
480 
481     public static class SimplePolicy extends Policy {
482 
483         final Permissions permissions;
484         final Permissions allPermissions;
485         final AtomicBoolean allowAll;
SimplePolicy(TestCase test, AtomicBoolean allowAll)486         public SimplePolicy(TestCase test, AtomicBoolean allowAll) {
487             this.allowAll = allowAll;
488             permissions = new Permissions();
489             permissions.add(new LoggingPermission("control", null));
490             permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete"));
491             permissions.add(new FilePermission(PREFIX, "read,write"));
492 
493             // these are used for configuring the test itself...
494             allPermissions = new Permissions();
495             allPermissions.add(new java.security.AllPermission());
496 
497         }
498 
499         @Override
implies(ProtectionDomain domain, Permission permission)500         public boolean implies(ProtectionDomain domain, Permission permission) {
501             if (allowAll.get()) return allPermissions.implies(permission);
502             return permissions.implies(permission);
503         }
504 
505         @Override
getPermissions(CodeSource codesource)506         public PermissionCollection getPermissions(CodeSource codesource) {
507             return new PermissionsBuilder().addAll(allowAll.get()
508                     ? allPermissions : permissions).toPermissions();
509         }
510 
511         @Override
getPermissions(ProtectionDomain domain)512         public PermissionCollection getPermissions(ProtectionDomain domain) {
513             return new PermissionsBuilder().addAll(allowAll.get()
514                     ? allPermissions : permissions).toPermissions();
515         }
516     }
517 
518 }
519