diff --git a/common/src/main/java/io/netty/util/internal/logging/InternalLoggerFactory.java b/common/src/main/java/io/netty/util/internal/logging/InternalLoggerFactory.java index 9f85e3646b..2b19d5c026 100644 --- a/common/src/main/java/io/netty/util/internal/logging/InternalLoggerFactory.java +++ b/common/src/main/java/io/netty/util/internal/logging/InternalLoggerFactory.java @@ -16,24 +16,64 @@ package io.netty.util.internal.logging; +import java.util.concurrent.atomic.AtomicReference; + /** - * Creates an {@link InternalLogger} or changes the default factory - * implementation. This factory allows you to choose what logging framework - * Netty should use. The default factory is {@link Slf4JLoggerFactory}. If SLF4J - * is not available, {@link Log4JLoggerFactory} is used. If Log4J is not available, - * {@link JdkLoggerFactory} is used. You can change it to your preferred - * logging framework before other Netty classes are loaded: - *
- * {@link InternalLoggerFactory}.setDefaultFactory({@link Log4JLoggerFactory}.INSTANCE);
- * 
- * Please note that the new default factory is effective only for the classes - * which were loaded after the default factory is changed. Therefore, - * {@link #setDefaultFactory(InternalLoggerFactory)} should be called as early - * as possible and shouldn't be called more than once. + * Creates {@link InternalLogger}s. This factory allows you to choose what logging framework Netty should use. The + * default factory is {@link Slf4JLoggerFactory}. If SLF4J is not available, {@link Log4JLoggerFactory} is used. If + * Log4J is not available, {@link JdkLoggerFactory} is used. You can change it to your preferred logging framework + * before other Netty classes are loaded via {@link #setDefaultFactory(InternalLoggerFactory)}. If you want to change + * the logger factory, {@link #setDefaultFactory(InternalLoggerFactory)} must be invoked before any other Netty classes + * are loaded. Note that {@link #setDefaultFactory(InternalLoggerFactory)}} can not be invoked more than once. */ public abstract class InternalLoggerFactory { - private static volatile InternalLoggerFactory defaultFactory; + private static final InternalLoggerFactoryHolder HOLDER = new InternalLoggerFactoryHolder(); + + /** + * This class holds a reference to the {@link InternalLoggerFactory}. The raison d'ĂȘtre for this class is primarily + * to aid in testing. + */ + static final class InternalLoggerFactoryHolder { + private final AtomicReference reference; + + InternalLoggerFactoryHolder() { + this(null); + } + + InternalLoggerFactoryHolder(final InternalLoggerFactory holder) { + this.reference = new AtomicReference(holder); + } + + InternalLoggerFactory getFactory() { + if (reference.get() == null) { + reference.compareAndSet(null, newDefaultFactory(InternalLoggerFactory.class.getName())); + } + return reference.get(); + } + + void setFactory(final InternalLoggerFactory factory) { + if (factory == null) { + throw new NullPointerException("factory"); + } + if (!reference.compareAndSet(null, factory)) { + throw new IllegalStateException( + "factory is already set to [" + reference.get() + "], rejecting [" + factory + "]"); + } + } + + InternalLogger getInstance(final Class clazz) { + return getInstance(clazz.getName()); + } + + InternalLogger getInstance(final String name) { + return newInstance(name); + } + + InternalLogger newInstance(String name) { + return getFactory().newInstance(name); + } + } @SuppressWarnings("UnusedCatchParameter") private static InternalLoggerFactory newDefaultFactory(String name) { @@ -54,38 +94,35 @@ public abstract class InternalLoggerFactory { } /** - * Returns the default factory. The initial default factory is - * {@link JdkLoggerFactory}. + * Get the default factory that was either initialized automatically based on logging implementations on the + * classpath, or set explicitly via {@link #setDefaultFactory(InternalLoggerFactory)}. */ public static InternalLoggerFactory getDefaultFactory() { - if (defaultFactory == null) { - defaultFactory = newDefaultFactory(InternalLoggerFactory.class.getName()); - } - return defaultFactory; + return HOLDER.getFactory(); } /** - * Changes the default factory. + * Set the default factory. This method must be invoked before the default factory is initialized via + * {@link #getDefaultFactory()}, and can not be invoked multiple times. + * + * @param defaultFactory a non-null implementation of {@link InternalLoggerFactory} */ public static void setDefaultFactory(InternalLoggerFactory defaultFactory) { - if (defaultFactory == null) { - throw new NullPointerException("defaultFactory"); - } - InternalLoggerFactory.defaultFactory = defaultFactory; + HOLDER.setFactory(defaultFactory); } /** * Creates a new logger instance with the name of the specified class. */ public static InternalLogger getInstance(Class clazz) { - return getInstance(clazz.getName()); + return HOLDER.getInstance(clazz); } /** * Creates a new logger instance with the specified name. */ public static InternalLogger getInstance(String name) { - return getDefaultFactory().newInstance(name); + return HOLDER.getInstance(name); } /** diff --git a/common/src/test/java/io/netty/util/internal/logging/InternalLoggerFactoryTest.java b/common/src/test/java/io/netty/util/internal/logging/InternalLoggerFactoryTest.java index 0a9f85a3f7..d040943ccf 100644 --- a/common/src/test/java/io/netty/util/internal/logging/InternalLoggerFactoryTest.java +++ b/common/src/test/java/io/netty/util/internal/logging/InternalLoggerFactoryTest.java @@ -13,49 +13,63 @@ * License for the specific language governing permissions and limitations * under the License. */ + package io.netty.util.internal.logging; import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.easymock.EasyMock.*; -import static org.junit.Assert.*; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class InternalLoggerFactoryTest { private static final Exception e = new Exception(); - private InternalLoggerFactory oldLoggerFactory; + private InternalLoggerFactory.InternalLoggerFactoryHolder holder; private InternalLogger mock; @Before public void init() { - oldLoggerFactory = InternalLoggerFactory.getDefaultFactory(); - InternalLoggerFactory mockFactory = createMock(InternalLoggerFactory.class); + final InternalLoggerFactory mockFactory = createMock(InternalLoggerFactory.class); mock = createStrictMock(InternalLogger.class); expect(mockFactory.newInstance("mock")).andReturn(mock).anyTimes(); replay(mockFactory); - InternalLoggerFactory.setDefaultFactory(mockFactory); + holder = new InternalLoggerFactory.InternalLoggerFactoryHolder(mockFactory); } @After public void destroy() { reset(mock); - InternalLoggerFactory.setDefaultFactory(oldLoggerFactory); } @Test(expected = NullPointerException.class) public void shouldNotAllowNullDefaultFactory() { - InternalLoggerFactory.setDefaultFactory(null); + holder.setFactory(null); } @Test public void shouldGetInstance() { - InternalLoggerFactory.setDefaultFactory(oldLoggerFactory); + final String helloWorld = "Hello, world!"; - String helloWorld = "Hello, world!"; - - InternalLogger one = InternalLoggerFactory.getInstance("helloWorld"); - InternalLogger two = InternalLoggerFactory.getInstance(helloWorld.getClass()); + final InternalLogger one = InternalLoggerFactory.getInstance("helloWorld"); + final InternalLogger two = InternalLoggerFactory.getInstance(helloWorld.getClass()); assertNotNull(one); assertNotNull(two); @@ -67,7 +81,7 @@ public class InternalLoggerFactoryTest { expect(mock.isTraceEnabled()).andReturn(true); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); assertTrue(logger.isTraceEnabled()); verify(mock); } @@ -77,7 +91,7 @@ public class InternalLoggerFactoryTest { expect(mock.isDebugEnabled()).andReturn(true); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); assertTrue(logger.isDebugEnabled()); verify(mock); } @@ -87,7 +101,7 @@ public class InternalLoggerFactoryTest { expect(mock.isInfoEnabled()).andReturn(true); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); assertTrue(logger.isInfoEnabled()); verify(mock); } @@ -97,7 +111,7 @@ public class InternalLoggerFactoryTest { expect(mock.isWarnEnabled()).andReturn(true); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); assertTrue(logger.isWarnEnabled()); verify(mock); } @@ -107,7 +121,7 @@ public class InternalLoggerFactoryTest { expect(mock.isErrorEnabled()).andReturn(true); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); assertTrue(logger.isErrorEnabled()); verify(mock); } @@ -117,7 +131,7 @@ public class InternalLoggerFactoryTest { mock.trace("a"); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.trace("a"); verify(mock); } @@ -127,7 +141,7 @@ public class InternalLoggerFactoryTest { mock.trace("a", e); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.trace("a", e); verify(mock); } @@ -137,7 +151,7 @@ public class InternalLoggerFactoryTest { mock.debug("a"); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.debug("a"); verify(mock); } @@ -147,7 +161,7 @@ public class InternalLoggerFactoryTest { mock.debug("a", e); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.debug("a", e); verify(mock); } @@ -157,7 +171,7 @@ public class InternalLoggerFactoryTest { mock.info("a"); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.info("a"); verify(mock); } @@ -167,7 +181,7 @@ public class InternalLoggerFactoryTest { mock.info("a", e); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.info("a", e); verify(mock); } @@ -177,7 +191,7 @@ public class InternalLoggerFactoryTest { mock.warn("a"); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.warn("a"); verify(mock); } @@ -187,7 +201,7 @@ public class InternalLoggerFactoryTest { mock.warn("a", e); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.warn("a", e); verify(mock); } @@ -197,7 +211,7 @@ public class InternalLoggerFactoryTest { mock.error("a"); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.error("a"); verify(mock); } @@ -207,8 +221,167 @@ public class InternalLoggerFactoryTest { mock.error("a", e); replay(mock); - InternalLogger logger = InternalLoggerFactory.getInstance("mock"); + final InternalLogger logger = holder.getInstance("mock"); logger.error("a", e); verify(mock); } + + @Test + public void shouldNotAllowToSetFactoryTwice() { + try { + holder.setFactory(createMock(InternalLoggerFactory.class)); + fail("should have thrown IllegalStateException"); + } catch (final IllegalStateException e) { + assertThat(e.getMessage(), containsString("factory is already set")); + } + + try { + final InternalLoggerFactory.InternalLoggerFactoryHolder implicit = + new InternalLoggerFactory.InternalLoggerFactoryHolder(); + implicit.getFactory(); // force initialization + implicit.setFactory(createMock(InternalLoggerFactory.class)); + fail("should have thrown IllegalStateException"); + } catch (final IllegalStateException e) { + assertThat(e.getMessage(), containsString("factory is already set")); + } + } + + @Test + public void raceGetAndGet() throws BrokenBarrierException, InterruptedException { + final CyclicBarrier barrier = new CyclicBarrier(3); + final InternalLoggerFactory.InternalLoggerFactoryHolder holder = + new InternalLoggerFactory.InternalLoggerFactoryHolder(); + final AtomicReference firstReference = new AtomicReference(); + final AtomicReference secondReference = new AtomicReference(); + + final Thread firstGet = getThread(firstReference, holder, barrier); + final Thread secondGet = getThread(secondReference, holder, barrier); + + firstGet.start(); + secondGet.start(); + // start the two get threads + barrier.await(); + + // wait for the two get threads to complete + barrier.await(); + + if (holder.getFactory() == firstReference.get()) { + assertSame(holder.getFactory(), secondReference.get()); + } else if (holder.getFactory() == secondReference.get()) { + assertSame(holder.getFactory(), firstReference.get()); + } else { + fail("holder should have been set by one of the get threads"); + } + } + + @Test + public void raceGetAndSet() throws BrokenBarrierException, InterruptedException { + final CyclicBarrier barrier = new CyclicBarrier(3); + final InternalLoggerFactory.InternalLoggerFactoryHolder holder = + new InternalLoggerFactory.InternalLoggerFactoryHolder(); + final InternalLoggerFactory internalLoggerFactory = createMock(InternalLoggerFactory.class); + final AtomicReference reference = new AtomicReference(); + + final Thread get = getThread(reference, holder, barrier); + + final AtomicBoolean setSuccess = new AtomicBoolean(); + final Thread set = setThread(internalLoggerFactory, holder, setSuccess, barrier); + + get.start(); + set.start(); + // start the get and set threads + barrier.await(); + + // wait for the get and set threads to complete + barrier.await(); + + if (setSuccess.get()) { + assertSame(internalLoggerFactory, reference.get()); + assertSame(internalLoggerFactory, holder.getFactory()); + } else { + assertNotSame(internalLoggerFactory, reference.get()); + assertNotSame(internalLoggerFactory, holder.getFactory()); + assertSame(holder.getFactory(), reference.get()); + } + } + + @Test + public void raceSetAndSet() throws BrokenBarrierException, InterruptedException { + final CyclicBarrier barrier = new CyclicBarrier(3); + final InternalLoggerFactory.InternalLoggerFactoryHolder holder = + new InternalLoggerFactory.InternalLoggerFactoryHolder(); + final InternalLoggerFactory first = createMock(InternalLoggerFactory.class); + final InternalLoggerFactory second = createMock(InternalLoggerFactory.class); + + final AtomicBoolean firstSetSuccess = new AtomicBoolean(); + final Thread firstSet = setThread(first, holder, firstSetSuccess, barrier); + + final AtomicBoolean secondSetSuccess = new AtomicBoolean(); + final Thread secondSet = setThread(second, holder, secondSetSuccess, barrier); + + firstSet.start(); + secondSet.start(); + // start the two set threads + barrier.await(); + + // wait for the two set threads to complete + barrier.await(); + + assertTrue(firstSetSuccess.get() || secondSetSuccess.get()); + if (firstSetSuccess.get()) { + assertFalse(secondSetSuccess.get()); + assertSame(first, holder.getFactory()); + } else { + assertFalse(firstSetSuccess.get()); + assertSame(second, holder.getFactory()); + } + } + + private static Thread getThread( + final AtomicReference reference, + final InternalLoggerFactory.InternalLoggerFactoryHolder holder, + final CyclicBarrier barrier) { + return new Thread(new Runnable() { + @Override + public void run() { + awaitUnchecked(barrier); + reference.set(holder.getFactory()); + awaitUnchecked(barrier); + } + }); + } + + private static Thread setThread( + final InternalLoggerFactory internalLoggerFactory, + final InternalLoggerFactory.InternalLoggerFactoryHolder holder, + final AtomicBoolean setSuccess, + final CyclicBarrier barrier) { + return new Thread(new Runnable() { + @Override + public void run() { + awaitUnchecked(barrier); + boolean success = true; + try { + holder.setFactory(internalLoggerFactory); + } catch (final IllegalStateException e) { + success = false; + assertThat(e.getMessage(), containsString("factory is already set")); + } finally { + setSuccess.set(success); + awaitUnchecked(barrier); + } + } + }); + } + + private static void awaitUnchecked(final CyclicBarrier barrier) { + try { + barrier.await(); + } catch (final InterruptedException exception) { + throw new IllegalStateException(exception); + } catch (final BrokenBarrierException exception) { + throw new IllegalStateException(exception); + } + } + }