--- /dev/null 2015-10-09 21:42:01.000000000 +0200 +++ new/jdk/src/java.base/share/classes/sun/util/logger/BootstrapLogger.java 2015-10-09 21:42:01.000000000 +0200 @@ -0,0 +1,1028 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.util.logger; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.ref.WeakReference; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import sun.misc.InnocuousThread; +import sun.misc.VM; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLoggerBridge; +import sun.util.logger.LazyLoggers.LazyLoggerAccessor; +import sun.util.logging.ConfigurableLoggerBridge; + +/** + * The BootstrapLogger class handles all the logic needed by Lazy Loggers + * to delay the creation of System.Logger instances until the VM is booted. + * By extension - it also contains the logic that will delay the creation + * of JUL Loggers until the LogManager is initialized by the application, in + * the common case where JUL is the default and there is no custom JUL + * configuration. + * + * A BootstrapLogger instance is both a Logger and a + * PlatformLoggerBridge instance, which will put all Log messages in a queue + * until the VM is booted. + * Once the VM is booted, it obtain the real System.Logger instance from the + * LoggerFinder and flushes the message to the queue. + * + * There are a few caveat: + * - the queue may not be flush until the next message is logged after + * the VM is booted + * - while the BootstrapLogger is active, the default implementation + * for all convenience methods is used + * - PlatformLogger.setLevel calls are ignored + * + * @since 1.9 + * + */ +public final class BootstrapLogger implements Logger, PlatformLoggerBridge, + ConfigurableLoggerBridge { + + // We use the BootstrapExecutors class to submit delayed messages + // to an indepedent InnocuousThread which will ensure that + // delayed log events will be clearly identified as messages that have + // been delayed during the boot sequence. + private static class BootstrapExecutors implements ThreadFactory { + + // Maybe that should be made configurable with system properties. + static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30; + + // The BootstrapMessageLoggerTask is a Runnable which keeps + // a hard ref to the ExecutorService that owns it. + // This ensure that the ExecutorService is not gc'ed until the thread + // has stopped running. + private static class BootstrapMessageLoggerTask implements Runnable { + ExecutorService owner; + Runnable run; + public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) { + this.owner = owner; + this.run = r; + } + @Override + public void run() { + try { + run.run(); + } finally { + owner = null; // allow the ExecutorService to be gced. + } + } + } + + private static volatile WeakReference executorRef; + private static ExecutorService getExecutor() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return executor; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + if (executor == null) { + executor = new ThreadPoolExecutor(0, 1, + KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new BootstrapExecutors()); + } + // The executor service will be elligible for gc + // KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s) + // after the execution of its last pending task. + executorRef = new WeakReference<>(executor); + return executorRef.get(); + } + } + + @Override + public Thread newThread(Runnable r) { + ExecutorService owner = getExecutor(); + Thread thread = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Thread run() { + Thread t = new InnocuousThread(new BootstrapMessageLoggerTask(owner, r)); + t.setName("BootstrapMessageLoggerTask-"+t.getName()); + return t; + } + }, null, new RuntimePermission("enableContextClassLoaderOverride")); + thread.setDaemon(true); + return thread; + } + + static void submit(Runnable r) { + getExecutor().execute(r); + } + + static void join(Runnable r) { + try { + getExecutor().submit(r).get(); + } catch (InterruptedException | ExecutionException ex) { + // should not happen + ex.printStackTrace(System.err); + } + } + + // This is used by tests. + static void awaitPendingTasks() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (ref == null) { + synchronized(BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + } + } + if (executor != null) { + // since our executor uses a FIFO and has a single thread + // then awaiting the execution of its pending tasks can be done + // simply by registering a new task and waiting until it + // completes. This of course would not work if we were using + // several threads, but we don't. + join(()->{}); + } + } + + // This is used by tests. + static boolean isAlive() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return true; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + return executor != null; + } + } + } + + // The accessor in which this logger is temporarilly set. + final LazyLoggerAccessor holder; + + // The pending log event queue. The first event is the head, and + // new events are added at the tail + LogEvent head, tail; + + BootstrapLogger(LazyLoggerAccessor holder) { + this.holder = holder; + } + + // Temporary data object storing log events + // It would be nice to use a Consumer instead of a LogEvent. + // This way we could simply do things like: + // push((logger) -> logger.log(level, msg)); + // Unfortunately, if we come to here it means we are in the bootsraping + // phase where using lambdas is not safe yet - so we have to use a + // a data object instead... + // + static final class LogEvent { + // only one of these two levels should be non null + final Level level; + final PlatformLogger.Level platformLevel; + + final ResourceBundle bundle; + final String msg; + final Throwable thrown; + final Object[] params; + final Supplier msgSupplier; + final String sourceClass; + final String sourceMethod; + final long timeMillis; + final long nanoAdjustment; + + // because logging a message may entail calling toString() on + // the parameters etc... we need to store the context of the + // caller who logged the message - so that we can reuse it when + // we finally log the message. + final AccessControlContext acc; + + // The next event in the queue + LogEvent next; + + private LogEvent(Level level, ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + } + + private LogEvent(Level level, Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + } + + private LogEvent(PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + } + + private LogEvent(PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.log(LogEvent, logger) instead. + private void log(Logger logger) { + assert platformLevel == null && level != null; + //new Exception("logging delayed message").printStackTrace(); + if (msgSupplier != null) { + if (thrown != null) { + logger.log(level, msgSupplier, thrown); + } else { + logger.log(level, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.log(level, bundle, msg, thrown); + } else { + logger.log(level, bundle, msg, params); + } + } + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.doLog(LogEvent, logger) instead. + private void log(PlatformLoggerBridge logger) { + assert platformLevel != null && level == null; + if (sourceClass == null) { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, thrown, msgSupplier); + } else { + logger.log(platformLevel, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, bundle, msg, params); + } + } + } else { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier); + } else { + logger.log(platformLevel,sourceClass, sourceMethod, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params); + } + } + } + } + + // non default methods from Logger interface + static LogEvent valueOf(Level level, ResourceBundle bundle, String key, Throwable thrown) { + return new LogEvent(level, bundle, key, thrown, null); + } + static LogEvent valueOf(Level level, ResourceBundle bundle, String format, Object[] params) { + return new LogEvent(level, bundle, format, null, params); + } + static LogEvent valueOf(Level level, Supplier msgSupplier, Throwable thrown) { + return new LogEvent(level, msgSupplier, thrown, null); + } + static LogEvent valueOf(Level level, Supplier msgSupplier) { + return new LogEvent(level, msgSupplier, null, null); + } + static void log(LogEvent log, Logger logger) { + final SecurityManager sm = System.getSecurityManager(); + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + if (sm == null || log.acc == null) { + BootstrapExecutors.submit(() -> log.log(logger)); + } else { + BootstrapExecutors.submit(() -> + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc)); + } + } + + // non default methods from PlatformLoggerBridge interface + static LogEvent valueOf(PlatformLogger.Level level, String msg) { + return new LogEvent(level, null, null, null, msg, null, null); + } + static LogEvent valueOf(PlatformLogger.Level level, String msg, Throwable thrown) { + return new LogEvent(level, null, null, null, msg, thrown, null); + } + static LogEvent valueOf(PlatformLogger.Level level, String msg, Object[] params) { + return new LogEvent(level, null, null, null, msg, null, params); + } + static LogEvent valueOf(PlatformLogger.Level level, Supplier msgSupplier) { + return new LogEvent(level, null, null, msgSupplier, null, null); + } + static LogEvent vaueOf(PlatformLogger.Level level, Supplier msgSupplier, + Throwable thrown) { + return new LogEvent(level, null, null, msgSupplier, thrown, null); + } + static LogEvent valueOf(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Object[] params) { + return new LogEvent(level, sourceClass, sourceMethod, bundle, msg, null, params); + } + static LogEvent valueOf(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) { + return new LogEvent(level, sourceClass, sourceMethod, bundle, msg, thrown, null); + } + static LogEvent valueOf(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier, Throwable thrown) { + return new LogEvent(level, sourceClass, sourceMethod, msgSupplier, thrown, null); + } + static void log(LogEvent log, PlatformLoggerBridge logger) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null || log.acc == null) { + log.log(logger); + } else { + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc); + } + } + + } + + // Push a log event at the end of the pending LogEvent queue. + void push(LogEvent log) { + synchronized(this) { + if (head == null) { + head = tail = log; + } else { + tail.next = log; + if (log != null) tail=log; + } + } + // if the queue has been flushed just before we entered + // the synchronized block we need to flush it again. + checkBootstrapping(); + } + + // Flushes the queue of pending LogEvents to the logger. + void flush(Logger logger) { + LogEvent first; + synchronized (this) { + first = head; + head = tail = null; + } + PlatformLoggerBridge platform = null; + while (first != null) { + LogEvent next = first.next; + first.next = null; + if (first.platformLevel != null) { + if (platform == null) { + platform = PlatformLoggerBridge.convert(logger); + } + LogEvent.log(first, platform); + } else { + LogEvent.log(first, logger); + } + first = next; + } + } + + /** + * The name of this logger. This is the name of the actual logger for which + * this logger acts as a temporary proxy. + * @return The logger name. + */ + @Override + public String getName() { + return holder.name; + } + + /** + * Check whether the VM is still bootstrapping, and if not, arranges + * for this logger's holder to create the real logger and flush the + * pending event queue. + * @return true if the VM is still bootstrapping. + */ + boolean checkBootstrapping() { + if (isBooted()) { + holder.flush(this); + return false; + } + return true; + } + + // ---------------------------------- + // Methods from Logger + // ---------------------------------- + + @Override + public boolean isLoggable(Level level) { + if (checkBootstrapping()) { + return level.getSeverity() >= Level.INFO.getSeverity(); + } else { + final Logger spi = holder.wrapped(); + return spi.isLoggable(level); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, bundle, key, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, key, thrown); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, bundle, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, format, params); + } + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, null, msg, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(Level level, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, null, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, format, params); + } + } + + @Override + public void log(Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, msgSupplier)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(Level level, Object obj) { + if (checkBootstrapping()) { + Logger.super.log(level, obj); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, obj); + } + } + + @Override + public void log(Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, null, msg, (Object[])null)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg); + } + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, msgSupplier, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier, thrown); + } + } + + // ---------------------------------- + // Methods from PlatformLoggerBridge + // ---------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + if (checkBootstrapping()) { + return level.intValue() >= PlatformLogger.Level.INFO.intValue(); + } else { + final PlatformLoggerBridge spi = holder.platform(); + return spi.isLoggable(level); + } + } + + @Override + public boolean isEnabled() { + if (checkBootstrapping()) { + return true; + } else { + final PlatformLoggerBridge spi = holder.platform(); + return spi.isEnabled(); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, msg)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, msg, thrown)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, msg, params)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, msgSupplier)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.vaueOf(level, msgSupplier, thrown)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, null, + msg, (Object[])null)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, msgSupplier, null)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, null, msg, params)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, null, msg, thrown)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, msgSupplier, thrown)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, bundle, msg, params)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, sourceClass, sourceMethod, bundle, msg, thrown)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, null, null, bundle, msg, params)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logrb(level, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(level, null, null, bundle, msg, thrown)); + } else { + final PlatformLoggerBridge spi = holder.platform(); + spi.logrb(level, bundle, msg, thrown); + } + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + if (checkBootstrapping()) { + // This practically means that PlatformLogger.setLevel() + // calls will be ignored if the VM is still bootstrapping. We could + // attempt to fix that but is it worth it? + return ConfigurableLoggerBridge.super.getLoggerConfiguration(); + } else { + final PlatformLoggerBridge spi = holder.platform(); + return ConfigurableLoggerBridge.getLoggerConfiguration(spi); + } + } + + // This BooleanSupplier is a hook for tests - so that we can simulate + // what would happen before the VM is booted. + private static volatile BooleanSupplier isBooted; + public static boolean isBooted() { + if (isBooted != null) return isBooted.getAsBoolean(); + else return VM.isBooted(); + } + + // A bit of black magic. We try to find out the nature of the logging + // backend without actually loading it. + private static enum LoggingBackend { + // There is no LoggerFinder and JUL is not present + NONE(true), + + // There is no LoggerFinder, but we have found a + // JdkLoggerFinder installed (which means JUL is present), + // and we haven't found any custom configuration for JUL. + // Until LogManager is initialized we can use a simple console + // logger. + JUL_DEFAULT(false), + + // Same as above, except that we have found a custom configuration + // for JUL. We cannot use the simple console logger in this case. + JUL_WITH_CONFIG(true), + + // We have found a custom LoggerFinder. + CUSTOM(true); + + final boolean useLoggerFinder; + private LoggingBackend(boolean useLoggerFinder) { + this.useLoggerFinder = useLoggerFinder; + } + }; + + // The purpose of this class is to delay the initialization of + // the detectedBackend field until it is actually read. + // We do not want this field to get initialized if VM.isBooted() is false. + private static final class DetectBackend { + static final LoggingBackend detectedBackend; + static { + detectedBackend = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public LoggingBackend run() { + final Iterator iterator = + ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader()) + .iterator(); + if (iterator.hasNext()) { + return LoggingBackend.CUSTOM; // Custom Logger Provider is registered + } + // No custom logger provider: we will be using the default + // backend. + final Iterator iterator2 = + ServiceLoader.loadInstalled(JdkLoggerProvider.class) + .iterator(); + if (iterator2.hasNext()) { + // JdkLoggingProvider is registered. The default + // implementation is java.util.logging + String cname = System.getProperty("java.util.logging.config.class"); + String fname = System.getProperty("java.util.logging.config.file"); + return (cname != null || fname != null) + ? LoggingBackend.JUL_WITH_CONFIG + : LoggingBackend.JUL_DEFAULT; + } else { + // SimpleLogger is used + return LoggingBackend.NONE; + } + } + }); + // System.out.println("Detected backend: "+detectedBackend); + + // Not sure whether this is needed: it was in the original + // PlatformLogger proxy code: + // + // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations + // less probable. Don't initialize JavaLoggerProxy class since + // java.util.logging may not be enabled. + try { + Class.forName("sun.util.logger.LazyLoggers$JdkLazyLogger", + false, + BootstrapLogger.class.getClassLoader()); + } catch (ClassNotFoundException ex) { + throw new InternalError(ex); + } + } + } + + // We will use temporary SimpleConsoleLoggers if + // the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + private static boolean useTemporaryLoggers() { + // being paranoid: this should already have been checked + if (!isBooted()) return true; + return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT + && !logManagerConfigured; + } + + // We will use lazy loggers if: + // - the VM is not yet booted + // - the logging backend is a custom backend + // - the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + public static synchronized boolean useLazyLoggers() { + return !BootstrapLogger.isBooted() + || DetectBackend.detectedBackend == LoggingBackend.CUSTOM + || useTemporaryLoggers(); + } + + // Called by LazyLoggerAccessor. This method will determine whether + // to create a BootstrapLogger (if the VM is not yet booted), + // a SimpleConsoleLogger (if JUL is the default backend and there + // is no custom JUL configuration and LogManager is not yet initialized), + // or a logger returned by the loaded LoggerFinder (all other cases). + static Logger getLogger(LazyLoggerAccessor accessor) { + if (!BootstrapLogger.isBooted()) { + return new BootstrapLogger(accessor); + } else { + boolean temporary = useTemporaryLoggers(); + if (temporary) { + // JUL is the default backend, there is no custom configuration, + // LogManager has not been used. + synchronized(BootstrapLogger.class) { + if (useTemporaryLoggers()) { + return makeTemporaryLogger(accessor); + } + } + } + // Already booted. Return the real logger. + return accessor.createLogger(); + } + } + + + // If the backend is JUL, and there is no custom configuration, and + // nobody has attempted to call LogManager.getLogManager() yet, then + // we can temporarily substitute JUL Logger with SimpleConsoleLoggers, + // which avoids the cost of actually loading up the LogManager... + // The TemporaryLoggers class has the logic to create such temporary + // loggers, and to possibly replace them with real JUL loggers if + // someone calls LogManager.getLogManager(). + static final class TemporaryLoggers implements + Function { + + // all accesses must be synchronized on the outer BootstrapLogger.class + final Map temporaryLoggers = + new HashMap<>(); + + // all accesses must be synchronized on the outer BootstrapLogger.class + // The temporaryLoggers map will be cleared when LogManager is initialized. + boolean cleared; + + @Override + // all accesses must be synchronized on the outer BootstrapLogger.class + public SimpleConsoleLogger apply(LazyLoggerAccessor t) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + SimpleConsoleLogger get(LazyLoggerAccessor a) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return temporaryLoggers.computeIfAbsent(a, this); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + Map drainTemporaryLoggers() { + if (temporaryLoggers.isEmpty()) return null; + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + final Map accessors = new HashMap<>(temporaryLoggers); + temporaryLoggers.clear(); + cleared = true; + return accessors; + } + + static void resetTemporaryLoggers(Map accessors) { + // When the backend is JUL we want to force the creation of + // JUL loggers here: some tests are expecting that the + // PlatformLogger will create JUL loggers as soon as the + // LogManager is initialized. + // + // If the backend is not JUL then we can delay the re-creation + // of the wrapped logger until they are next accessed. + // + final LoggingBackend detectedBackend = DetectBackend.detectedBackend; + final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT + && detectedBackend != LoggingBackend.JUL_WITH_CONFIG; + for (Map.Entry a : accessors.entrySet()) { + a.getKey().release(a.getValue(), !lazy); + } + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + static final TemporaryLoggers INSTANCE = new TemporaryLoggers(); + } + + static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) { + // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class + return TemporaryLoggers.INSTANCE.get(a); + } + + private static volatile boolean logManagerConfigured; + + private static synchronized Map + releaseTemporaryLoggers() { + // first check whether there's a chance that we have used + // temporary loggers; Will be false if logManagerConfigured is already + // true. + final boolean clearTemporaryLoggers = useTemporaryLoggers(); + + // then sets the flag that tells that the log manager is configured + logManagerConfigured = true; + + // finally replace all temporary loggers by real JUL loggers + if (clearTemporaryLoggers) { + // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class + return TemporaryLoggers.INSTANCE.drainTemporaryLoggers(); + } else { + return null; + } + } + + public static void redirectTemporaryLoggers() { + // This call is synchronized on BootstrapLogger.class. + final Map accessors = + releaseTemporaryLoggers(); + + // We will now reset the logger accessors, triggering the + // (possibly lazy) replacement of any temporary logger by the + // real logger returned from the loaded LoggerFinder. + if (accessors != null) { + TemporaryLoggers.resetTemporaryLoggers(accessors); + } + } + + // Hook for tests which need to wait until pending messages + // are processed. + static void awaitPendingTasks() { + BootstrapExecutors.awaitPendingTasks(); + } + static boolean isAlive() { + return BootstrapExecutors.isAlive(); + } + +}