2015-03-01 22:20 GMT+01:00 Oliver Heger <oliver.he...@oliver-heger.de>:
> > > Am 01.03.2015 um 17:52 schrieb Benedikt Ritter: > > 2015-02-26 21:29 GMT+01:00 Oliver Heger <oliver.he...@oliver-heger.de>: > > > >> Hi Arthur, > >> > >> I don't have any principle objections against your implementation. > >> > >> I just don't want it as a replacement for the AtomicSafeInitializer > >> class. The latter was designed and announces itself to use only atomic > >> variables for its synchronization and no other types of locks. This > >> premise no longer holds. If the implementation is broken and does not > >> work as expected, we should deprecate and later remove it. > >> > > > > Just deprecating the class if it has a bug (hanging threads when an > > exception is thrown) doesn't seem right to me. > > > > How about we apply the patch and make it very clear in the release notes > > and JavaDoc, that it's behavior has been changed? > > But then the name of the class is completely wrong. It is a bit like > > public class ClassWithoutSynchronization { > public void doSomething() { > *synchronized*(this) { > ... > } > } > } > > For me the name AtomicSafeInitializer indicates that only atomic > variables are used to ensure thread-safety. Everything else would be > surprising, even if it was mentioned in JavaDoc. > We have several issues here: 1. Find a name for the proposed initializer implementation. How about BlockingInitizalizer? 2. Fix the bug in AtomicSafeInitializer (hanging threads on exception). My opinion is, that BusyWaitInitializer would be a much better name for this class :o) 3. Compare the new implementation and LazyInitializer. Is the BlockingInitializer a replacement for LazyInitializer? Benedikt > > Oliver > > > > Benedikt > > > > > >> > >> What I would be interested in is a comparison between your > >> implementation and LazyInitializer. How do these classes behave in > >> typical scenarios and which one is the preferred option under which > >> conditions? > >> > >> If we add your proposal as an additional initializer implementation, we > >> will also have to update the user's guide and give our users appropriate > >> recommendations when they should use which class. > >> > >> And, we will have to pick a meaningful name for your class. > >> > >> Oliver > >> > >> Am 26.02.2015 um 20:51 schrieb Arthur Naseef: > >>> A few questions coming to mind here: > >>> > >>> - First off, how do we move this forward? > >>> - What do we do with the new implementation? > >>> - What is a good way to address the busy-wait in the original > code? > >>> - What is a good way to address the fact that all threads will > >>> busy-wait in the original code if the initialize() method throws > an > >>> exception? > >>> - What is a good way to address the original comments which > >> indicate > >>> this class will perform better when, in fact, the busy waiting > may > >> have a > >>> significant impact on performance? > >>> - How can we address the fears? Honestly, I think the comment is > >> less a > >>> "fear" than a belief, so how do we prove/disprove? > >>> - What is this other initializer implementation? > >>> - What are the differences in operation between the two? > >>> > >>> Oliver - I'm reading a lot of concerns here, but not seeing how you > would > >>> like to address those concerns. Please help me on that front. > >>> > >>> Note, by the way, that the new implementation operates the same as the > >>> original code, to the application using it, except in the case of an > >>> exception thrown on the initialize() call, which is problematic now. > >> That > >>> is, this new implementation guarantees initialize() will only ever be > >>> called one time, and it ensures all callers receive the result of that > >>> initialize() method. > >>> > >>> Art > >>> > >>> > >>> > >>> On Mon, Feb 23, 2015 at 1:47 PM, Oliver Heger < > >> oliver.he...@oliver-heger.de> > >>> wrote: > >>> > >>>> > >>>> > >>>> Am 23.02.2015 um 21:35 schrieb Benedikt Ritter: > >>>>> Oliver Heger has raised concerns about this commit in JIRA [1]: > >>>>> > >>>>>> This is a strong change in the behavior of this class. The main > >> property > >>>>> of atomic initializers was that they are non > >>>>>> blocking. Now a blocking wait has been introduced. When there is so > >> much > >>>>> contention that the busy wait is > >>>>>> actually a problem, wouln't it then be better to directly use a > >> blocking > >>>>> variant like lazy initializer? > >>>>> > >>>>> I've looked through the JavaDoc of AtomicInitializer once more. It > >> says: > >>>>> "Because {@code AtomicSafeInitializer} does not use synchronization > at > >>>> all > >>>>> it probably outruns {@link LazyInitializer}, at least under low or > >>>> moderate > >>>>> concurrent access." > >>>>> > >>>>> This is the only thing I can find regarding concurrency properties of > >>>>> AtomicInitializer. I think this still holds, doesn't it? > >>>> > >>>> No, the CountDownLatch is synchronized. > >>>> > >>>> There are multiple initializer implementations with different > properties > >>>> which are suitable for different use cases and scenarios. The atomic > >>>> initializers are useful if you want to avoid blocking calls, but they > >>>> might be an inferior solution under high contention. > >>>> > >>>> My fear is that this commit optimizes the class for a use case which > can > >>>> be served better by another initializer implementation which is > already > >>>> blocking; and this optimization has negative impact on the original > >>>> intention of AtomicSafeInitializer. > >>>> > >>>> Oliver > >>>> > >>>>> > >>>>> Benedikt > >>>>> > >>>>> [1] https://issues.apache.org/jira/browse/LANG-1086 > >>>>> > >>>>> 2015-02-23 21:15 GMT+01:00 <brit...@apache.org>: > >>>>> > >>>>>> Author: britter > >>>>>> Date: Mon Feb 23 20:15:49 2015 > >>>>>> New Revision: 1661762 > >>>>>> > >>>>>> URL: http://svn.apache.org/r1661762 > >>>>>> Log: > >>>>>> LANG-1086: Remove busy wait from AtomicSafeInitializer.get(). This > >> also > >>>>>> fixes #46 from github. Thanks to github user artnaseef. > >>>>>> > >>>>>> Modified: > >>>>>> commons/proper/lang/trunk/src/changes/changes.xml > >>>>>> > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java > >>>>>> > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java > >>>>>> > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java > >>>>>> > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java > >>>>>> > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java > >>>>>> > >>>>>> Modified: commons/proper/lang/trunk/src/changes/changes.xml > >>>>>> URL: > >>>>>> > >>>> > >> > http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/changes/changes.xml?rev=1661762&r1=1661761&r2=1661762&view=diff > >>>>>> > >>>>>> > >>>> > >> > ============================================================================== > >>>>>> --- commons/proper/lang/trunk/src/changes/changes.xml [utf-8] > >> (original) > >>>>>> +++ commons/proper/lang/trunk/src/changes/changes.xml [utf-8] Mon > Feb > >> 23 > >>>>>> 20:15:49 2015 > >>>>>> @@ -22,6 +22,7 @@ > >>>>>> <body> > >>>>>> > >>>>>> <release version="3.4" date="tba" description="tba"> > >>>>>> + <action issue="LANG-1086" type="update" dev="britter">Remove > busy > >>>>>> wait from AtomicSafeInitializer.get()</action> > >>>>>> <action issue="LANG-1081" type="fix" dev="britter" > >> due-to="Jonathan > >>>>>> Baker">DiffBuilder.append(String, Object left, Object right) does > not > >>>> do a > >>>>>> left.equals(right) check</action> > >>>>>> <action issue="LANG-1055" type="fix" dev="britter" > >> due-to="Jonathan > >>>>>> Baker">StrSubstitutor.replaceSystemProperties does not work > >>>>>> consistently</action> > >>>>>> <action issue="LANG-1082" type="add" dev="britter" > >> due-to="Jonathan > >>>>>> Baker">Add option to disable the "objectsTriviallyEqual" test in > >>>>>> DiffBuilder</action> > >>>>>> > >>>>>> Modified: > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java > >>>>>> URL: > >>>>>> > >>>> > >> > http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java?rev=1661762&r1=1661761&r2=1661762&view=diff > >>>>>> > >>>>>> > >>>> > >> > ============================================================================== > >>>>>> --- > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java > >>>>>> (original) > >>>>>> +++ > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java > >>>>>> Mon Feb 23 20:15:49 2015 > >>>>>> @@ -16,6 +16,7 @@ > >>>>>> */ > >>>>>> package org.apache.commons.lang3.concurrent; > >>>>>> > >>>>>> +import java.util.concurrent.CountDownLatch; > >>>>>> import java.util.concurrent.atomic.AtomicReference; > >>>>>> > >>>>>> /** > >>>>>> @@ -62,20 +63,44 @@ public abstract class AtomicSafeInitiali > >>>>>> /** Holds the reference to the managed object. */ > >>>>>> private final AtomicReference<T> reference = new > >>>> AtomicReference<T>(); > >>>>>> > >>>>>> + /** Holds the exception that terminated the initialize() > method, > >> if > >>>>>> an exception was thrown */ > >>>>>> + private final AtomicReference<ConcurrentException> > referenceExc = > >>>> new > >>>>>> AtomicReference<ConcurrentException>(); > >>>>>> + > >>>>>> + /** Latch for those threads waiting for initialization to > >>>> complete. */ > >>>>>> + private final CountDownLatch latch = new CountDownLatch(1); > >>>>>> + > >>>>>> /** > >>>>>> * Get (and initialize, if not initialized yet) the required > >> object > >>>>>> * > >>>>>> * @return lazily initialized object > >>>>>> * @throws ConcurrentException if the initialization of the > >> object > >>>>>> causes an > >>>>>> - * exception > >>>>>> + * exception or the thread is interrupted waiting for another > >>>> thread > >>>>>> to > >>>>>> + * complete the initialization > >>>>>> */ > >>>>>> @Override > >>>>>> public final T get() throws ConcurrentException { > >>>>>> T result; > >>>>>> > >>>>>> - while ((result = reference.get()) == null) { > >>>>>> + if ((result = reference.get()) == null) { > >>>>>> if (factory.compareAndSet(null, this)) { > >>>>>> - reference.set(initialize()); > >>>>>> + try { > >>>>>> + reference.set(result = initialize()); > >>>>>> + } catch ( ConcurrentException exc ) { > >>>>>> + referenceExc.set(exc); > >>>>>> + throw exc; > >>>>>> + } finally { > >>>>>> + latch.countDown(); > >>>>>> + } > >>>>>> + } else { > >>>>>> + try { > >>>>>> + latch.await(); > >>>>>> + if ( referenceExc.get() != null ) { > >>>>>> + throw new > >>>>>> ConcurrentException(referenceExc.get().getMessage(), > >>>>>> referenceExc.get().getCause()); > >>>>>> + } > >>>>>> + result = reference.get(); > >>>>>> + } catch (InterruptedException intExc) { > >>>>>> + throw new ConcurrentException("interrupted > >> waiting > >>>>>> for initialization to complete", intExc); > >>>>>> + } > >>>>>> } > >>>>>> } > >>>>>> > >>>>>> > >>>>>> Modified: > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java > >>>>>> URL: > >>>>>> > >>>> > >> > http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java?rev=1661762&r1=1661761&r2=1661762&view=diff > >>>>>> > >>>>>> > >>>> > >> > ============================================================================== > >>>>>> --- > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java > >>>>>> (original) > >>>>>> +++ > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java > >>>>>> Mon Feb 23 20:15:49 2015 > >>>>>> @@ -18,6 +18,8 @@ package org.apache.commons.lang3.concurr > >>>>>> > >>>>>> import static org.junit.Assert.assertEquals; > >>>>>> import static org.junit.Assert.assertNotNull; > >>>>>> +import static org.junit.Assert.assertSame; > >>>>>> +import static org.junit.Assert.assertTrue; > >>>>>> > >>>>>> import java.util.concurrent.CountDownLatch; > >>>>>> > >>>>>> @@ -72,7 +74,41 @@ public abstract class AbstractConcurrent > >>>>>> @Test > >>>>>> public void testGetConcurrent() throws ConcurrentException, > >>>>>> InterruptedException { > >>>>>> - final ConcurrentInitializer<Object> initializer = > >>>>>> createInitializer(); > >>>>>> + > >>>>>> + this.testGetConcurrentOptionallyWithException(false, null, > >>>> null); > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Tests the handling of exceptions thrown on the initialized > >> when > >>>>>> multiple threads execute concurrently. > >>>>>> + * Always an exception with the same message and cause should > be > >>>>>> thrown. > >>>>>> + * > >>>>>> + * @throws > >> org.apache.commons.lang3.concurrent.ConcurrentException > >>>>>> because the object under test may throw it > >>>>>> + * @throws java.lang.InterruptedException because the threading > >> API > >>>>>> my throw it > >>>>>> + */ > >>>>>> + public void testGetConcurrentWithException(String > >> expectedMessage, > >>>>>> + Exception > >> expectedCause) > >>>>>> + throws ConcurrentException, InterruptedException { > >>>>>> + > >>>>>> + this.testGetConcurrentOptionallyWithException(true, > >>>>>> expectedMessage, expectedCause); > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Tests whether get() can be invoked from multiple threads > >>>>>> concurrently. Supports the exception-handling case > >>>>>> + * and the normal, non-exception case. > >>>>>> + * > >>>>>> + * Always the same object should be returned, or an exception > >> with > >>>>>> the same message and cause should be thrown. > >>>>>> + * > >>>>>> + * @throws > >> org.apache.commons.lang3.concurrent.ConcurrentException > >>>>>> because the object under test may throw it > >>>>>> + * @throws java.lang.InterruptedException because the threading > >> API > >>>>>> my throw it > >>>>>> + */ > >>>>>> + protected void testGetConcurrentOptionallyWithException(boolean > >>>>>> expectExceptions, String expectedMessage, > >>>>>> + > Exception > >>>>>> expectedCause) > >>>>>> + throws ConcurrentException, InterruptedException { > >>>>>> + > >>>>>> + final ConcurrentInitializer<Object> initializer = > >>>>>> expectExceptions ? > >>>>>> + createExceptionThrowingInitializer() : > >>>>>> + createInitializer(); > >>>>>> + > >>>>>> final int threadCount = 20; > >>>>>> final CountDownLatch startLatch = new CountDownLatch(1); > >>>>>> class GetThread extends Thread { > >>>>>> @@ -106,9 +142,18 @@ public abstract class AbstractConcurrent > >>>>>> } > >>>>>> > >>>>>> // check results > >>>>>> - final Object managedObject = initializer.get(); > >>>>>> - for (final GetThread t : threads) { > >>>>>> - assertEquals("Wrong object", managedObject, t.object); > >>>>>> + if ( expectExceptions ) { > >>>>>> + for (GetThread t : threads) { > >>>>>> + assertTrue(t.object instanceof Exception); > >>>>>> + Exception exc = (Exception) t.object; > >>>>>> + assertEquals(expectedMessage, exc.getMessage()); > >>>>>> + assertSame(expectedCause, exc.getCause()); > >>>>>> + } > >>>>>> + } else { > >>>>>> + final Object managedObject = initializer.get(); > >>>>>> + for (final GetThread t : threads) { > >>>>>> + assertEquals("Wrong object", managedObject, > >> t.object); > >>>>>> + } > >>>>>> } > >>>>>> } > >>>>>> > >>>>>> @@ -119,4 +164,12 @@ public abstract class AbstractConcurrent > >>>>>> * @return the initializer object to be tested > >>>>>> */ > >>>>>> protected abstract ConcurrentInitializer<Object> > >>>> createInitializer(); > >>>>>> + > >>>>>> + /** > >>>>>> + * Creates a {@link ConcurrentInitializer} object that always > >>>> throws > >>>>>> + * exceptions. > >>>>>> + * > >>>>>> + * @return > >>>>>> + */ > >>>>>> + protected abstract ConcurrentInitializer<Object> > >>>>>> createExceptionThrowingInitializer(); > >>>>>> } > >>>>>> > >>>>>> Modified: > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java > >>>>>> URL: > >>>>>> > >>>> > >> > http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java?rev=1661762&r1=1661761&r2=1661762&view=diff > >>>>>> > >>>>>> > >>>> > >> > ============================================================================== > >>>>>> --- > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java > >>>>>> (original) > >>>>>> +++ > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java > >>>>>> Mon Feb 23 20:15:49 2015 > >>>>>> @@ -16,12 +16,29 @@ > >>>>>> */ > >>>>>> package org.apache.commons.lang3.concurrent; > >>>>>> > >>>>>> +import org.junit.Test; > >>>>>> + > >>>>>> /** > >>>>>> * Test class for {@code AtomicInitializer}. > >>>>>> * > >>>>>> * @version $Id$ > >>>>>> */ > >>>>>> public class AtomicInitializerTest extends > >>>>>> AbstractConcurrentInitializerTest { > >>>>>> + private Exception testCauseException; > >>>>>> + private String testExceptionMessage; > >>>>>> + > >>>>>> + public AtomicInitializerTest() { > >>>>>> + testExceptionMessage = "x-test-exception-message-x"; > >>>>>> + testCauseException = new Exception(testExceptionMessage); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testGetConcurrentWithException () > >>>>>> + throws ConcurrentException, InterruptedException { > >>>>>> + > >>>>>> + super.testGetConcurrentWithException(testExceptionMessage, > >>>>>> testCauseException); > >>>>>> + } > >>>>>> + > >>>>>> /** > >>>>>> * Returns the initializer to be tested. > >>>>>> * > >>>>>> @@ -36,4 +53,20 @@ public class AtomicInitializerTest exten > >>>>>> } > >>>>>> }; > >>>>>> } > >>>>>> + > >>>>>> + @Override > >>>>>> + protected ConcurrentInitializer<Object> > >>>>>> createExceptionThrowingInitializer() { > >>>>>> + return new > ExceptionThrowingAtomicSafeInitializerTestImpl(); > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * A concrete test implementation of {@code > >> AtomicSafeInitializer}. > >>>>>> This > >>>>>> + * implementation always throws an exception. > >>>>>> + */ > >>>>>> + private class ExceptionThrowingAtomicSafeInitializerTestImpl > >>>> extends > >>>>>> AtomicSafeInitializer<Object> { > >>>>>> + @Override > >>>>>> + protected Object initialize() throws ConcurrentException { > >>>>>> + throw new ConcurrentException(testExceptionMessage, > >>>>>> testCauseException); > >>>>>> + } > >>>>>> + } > >>>>>> } > >>>>>> > >>>>>> Modified: > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java > >>>>>> URL: > >>>>>> > >>>> > >> > http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java?rev=1661762&r1=1661761&r2=1661762&view=diff > >>>>>> > >>>>>> > >>>> > >> > ============================================================================== > >>>>>> --- > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java > >>>>>> (original) > >>>>>> +++ > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java > >>>>>> Mon Feb 23 20:15:49 2015 > >>>>>> @@ -17,7 +17,11 @@ > >>>>>> package org.apache.commons.lang3.concurrent; > >>>>>> > >>>>>> import static org.junit.Assert.assertEquals; > >>>>>> +import static org.junit.Assert.assertFalse; > >>>>>> +import static org.junit.Assert.assertSame; > >>>>>> +import static org.junit.Assert.assertTrue; > >>>>>> > >>>>>> +import java.util.concurrent.CountDownLatch; > >>>>>> import java.util.concurrent.atomic.AtomicInteger; > >>>>>> > >>>>>> import org.junit.Before; > >>>>>> @@ -30,12 +34,19 @@ import org.junit.Test; > >>>>>> */ > >>>>>> public class AtomicSafeInitializerTest extends > >>>>>> AbstractConcurrentInitializerTest { > >>>>>> + > >>>>>> /** The instance to be tested. */ > >>>>>> private AtomicSafeInitializerTestImpl initializer; > >>>>>> + private ExceptionThrowingAtomicSafeInitializerTestImpl > >>>>>> exceptionThrowingInitializer; > >>>>>> + private Exception testCauseException; > >>>>>> + private String testExceptionMessage; > >>>>>> > >>>>>> @Before > >>>>>> public void setUp() throws Exception { > >>>>>> initializer = new AtomicSafeInitializerTestImpl(); > >>>>>> + exceptionThrowingInitializer = new > >>>>>> ExceptionThrowingAtomicSafeInitializerTestImpl(); > >>>>>> + testExceptionMessage = "x-test-exception-message-x"; > >>>>>> + testCauseException = new Exception(testExceptionMessage); > >>>>>> } > >>>>>> > >>>>>> /** > >>>>>> @@ -49,6 +60,17 @@ public class AtomicSafeInitializerTest e > >>>>>> } > >>>>>> > >>>>>> /** > >>>>>> + * Returns the exception-throwing initializer to be tested. > >>>>>> + * > >>>>>> + * @return the {@code AtomicSafeInitializer} under test when > >>>>>> validating > >>>>>> + * exception handling > >>>>>> + */ > >>>>>> + @Override > >>>>>> + protected ConcurrentInitializer<Object> > >>>>>> createExceptionThrowingInitializer() { > >>>>>> + return exceptionThrowingInitializer; > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> * Tests that initialize() is called only once. > >>>>>> * > >>>>>> * @throws > >> org.apache.commons.lang3.concurrent.ConcurrentException > >>>>>> because {@link #testGetConcurrent()} may throw it > >>>>>> @@ -62,6 +84,92 @@ public class AtomicSafeInitializerTest e > >>>>>> initializer.initCounter.get()); > >>>>>> } > >>>>>> > >>>>>> + @Test > >>>>>> + public void testExceptionOnInitialize() throws > >> ConcurrentException, > >>>>>> + InterruptedException { > >>>>>> + > >>>>>> + testGetConcurrentWithException(testExceptionMessage, > >>>>>> testCauseException); > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Validate the handling of an interrupted exception on a > thread > >>>>>> waiting for another thread to finish calling the > >>>>>> + * initialize() method. > >>>>>> + * > >>>>>> + * @throws Exception > >>>>>> + */ > >>>>>> + @Test(timeout = 3000) > >>>>>> + public void testInterruptedWaitingOnInitialize() throws > >> Exception { > >>>>>> + this.execTestWithWaitingOnInitialize(true); > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Test the success path of two threads reaching the > >> initialization > >>>>>> point at the same time. > >>>>>> + */ > >>>>>> + @Test(timeout = 3000) > >>>>>> + public void testOneThreadWaitingForAnotherToInitialize () > throws > >>>>>> Exception { > >>>>>> + execTestWithWaitingOnInitialize(false); > >>>>>> + } > >>>>>> + > >>>>>> + > >>>>>> + /** > >>>>>> + * Execute a test that requires one thread to be waiting on the > >>>>>> initialize() method of another thread. This test > >>>>>> + * uses latches to guarantee the code path being tested. > >>>>>> + * > >>>>>> + * @throws Exception > >>>>>> + */ > >>>>>> + protected void execTestWithWaitingOnInitialize(boolean > >>>> interruptInd) > >>>>>> throws Exception { > >>>>>> + final CountDownLatch startLatch = new CountDownLatch(1); > >>>>>> + final CountDownLatch finishLatch = new CountDownLatch(1); > >>>>>> + final WaitingInitializerTestImpl initializer = new > >>>>>> WaitingInitializerTestImpl(startLatch, finishLatch); > >>>>>> + > >>>>>> + InitializerTestThread execThread1 = new > >>>>>> InitializerTestThread(initializer); > >>>>>> + InitializerTestThread execThread2 = new > >>>>>> InitializerTestThread(initializer); > >>>>>> + > >>>>>> + // Start the first thread and wait for it to get into the > >>>>>> initialize method so we are sure it is the thread > >>>>>> + // executing initialize(). > >>>>>> + execThread1.start(); > >>>>>> + startLatch.await(); > >>>>>> + > >>>>>> + // Start the second thread and interrupt it to force the > >>>>>> InterruptedException. There is no need to make sure > >>>>>> + // the thread reaches the await() call before interrupting > >> it. > >>>>>> + execThread2.start(); > >>>>>> + > >>>>>> + if ( interruptInd ) { > >>>>>> + // Interrupt the second thread now and wait for it to > >>>>>> complete to ensure it reaches the wait inside the > >>>>>> + // get() method. > >>>>>> + execThread2.interrupt(); > >>>>>> + execThread2.join(); > >>>>>> + } > >>>>>> + > >>>>>> + // Signal the completion of the initialize method now. > >>>>>> + finishLatch.countDown(); > >>>>>> + > >>>>>> + // Wait for the initialize() to finish. > >>>>>> + execThread1.join(); > >>>>>> + > >>>>>> + // Wait for thread2 to finish, if it was not already done > >>>>>> + if ( ! interruptInd ) { > >>>>>> + execThread2.join(); > >>>>>> + } > >>>>>> + > >>>>>> + // > >>>>>> + // Validate: thread1 should have the valid result; thread2 > >>>> should > >>>>>> have caught an interrupted exception, if > >>>>>> + // interrupted, or should have the same result otherwise. > >>>>>> + // > >>>>>> + assertFalse(execThread1.isCaughtException()); > >>>>>> + assertSame(initializer.getAnswer(), > execThread1.getResult()); > >>>>>> + > >>>>>> + if ( interruptInd ) { > >>>>>> + assertTrue(execThread2.isCaughtException()); > >>>>>> + Exception exc = (Exception) execThread2.getResult(); > >>>>>> + assertTrue(exc.getCause() instanceof > >> InterruptedException); > >>>>>> + assertEquals("interrupted waiting for initialization to > >>>>>> complete", exc.getMessage()); > >>>>>> + } else { > >>>>>> + assertFalse(execThread2.isCaughtException()); > >>>>>> + assertSame(initializer.getAnswer(), > >>>> execThread2.getResult()); > >>>>>> + } > >>>>>> + } > >>>>>> + > >>>>>> /** > >>>>>> * A concrete test implementation of {@code > >> AtomicSafeInitializer}. > >>>>>> This > >>>>>> * implementation also counts the number of invocations of the > >>>>>> initialize() > >>>>>> @@ -78,4 +186,90 @@ public class AtomicSafeInitializerTest e > >>>>>> return new Object(); > >>>>>> } > >>>>>> } > >>>>>> + > >>>>>> + /** > >>>>>> + * A concrete test implementation of {@code > >> AtomicSafeInitializer}. > >>>>>> This > >>>>>> + * implementation always throws an exception. > >>>>>> + */ > >>>>>> + private class ExceptionThrowingAtomicSafeInitializerTestImpl > >>>> extends > >>>>>> AtomicSafeInitializer<Object> { > >>>>>> + @Override > >>>>>> + protected Object initialize() throws ConcurrentException { > >>>>>> + throw new ConcurrentException(testExceptionMessage, > >>>>>> testCauseException); > >>>>>> + } > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Initializer that signals it has started and waits to > complete > >>>>>> until signalled in order to enable a guaranteed > >>>>>> + * order-of-operations. This allows the test code to peg one > >>>> thread > >>>>>> to the initialize method for a period of time > >>>>>> + * that the test can dictate. > >>>>>> + */ > >>>>>> + private class WaitingInitializerTestImpl extends > >>>>>> AtomicSafeInitializer<Object> { > >>>>>> + private final CountDownLatch startedLatch; > >>>>>> + private final CountDownLatch finishLatch; > >>>>>> + private final Object answer = new Object(); > >>>>>> + > >>>>>> + public WaitingInitializerTestImpl(CountDownLatch > >> startedLatch, > >>>>>> CountDownLatch finishLatch) { > >>>>>> + this.startedLatch = startedLatch; > >>>>>> + this.finishLatch = finishLatch; > >>>>>> + } > >>>>>> + > >>>>>> + @Override > >>>>>> + protected Object initialize() throws ConcurrentException { > >>>>>> + this.startedLatch.countDown(); > >>>>>> + try { > >>>>>> + this.finishLatch.await(); > >>>>>> + } catch (InterruptedException intExc) { > >>>>>> + throw new ConcurrentException(intExc); > >>>>>> + } > >>>>>> + > >>>>>> + return answer; > >>>>>> + } > >>>>>> + > >>>>>> + public Object getAnswer () { > >>>>>> + return answer; > >>>>>> + } > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Test executor of the initializer get() operation that > captures > >>>> the > >>>>>> result. > >>>>>> + */ > >>>>>> + private class InitializerTestThread extends Thread { > >>>>>> + private AtomicSafeInitializer<Object> initializer; > >>>>>> + private Object result; > >>>>>> + private boolean caughtException; > >>>>>> + > >>>>>> + public InitializerTestThread(AtomicSafeInitializer<Object> > >>>>>> initializer) { > >>>>>> + super("AtomicSafeInitializer test thread"); > >>>>>> + this.initializer = initializer; > >>>>>> + } > >>>>>> + > >>>>>> + @Override > >>>>>> + public void run() { > >>>>>> + try { > >>>>>> + this.result = initializer.get(); > >>>>>> + } catch ( ConcurrentException concurrentExc ) { > >>>>>> + this.caughtException = true; > >>>>>> + this.result = concurrentExc; > >>>>>> + } > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Resulting object, if the get() method returned > >> successfully, > >>>>>> or exception if an exception was thrown. > >>>>>> + * > >>>>>> + * @return resulting object or exception from the get() > >> method > >>>>>> call. > >>>>>> + */ > >>>>>> + public Object getResult () { > >>>>>> + return this.result; > >>>>>> + } > >>>>>> + > >>>>>> + /** > >>>>>> + * Determine whether an exception was caught on the get() > >> call. > >>>>>> Does not guarantee that the get() method was > >>>>>> + * called or completed. > >>>>>> + * > >>>>>> + * @return true => exception was caught; false => exception > >> was > >>>>>> not caught. > >>>>>> + */ > >>>>>> + public boolean isCaughtException () { > >>>>>> + return this.caughtException; > >>>>>> + } > >>>>>> + } > >>>>>> } > >>>>>> > >>>>>> Modified: > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java > >>>>>> URL: > >>>>>> > >>>> > >> > http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java?rev=1661762&r1=1661761&r2=1661762&view=diff > >>>>>> > >>>>>> > >>>> > >> > ============================================================================== > >>>>>> --- > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java > >>>>>> (original) > >>>>>> +++ > >>>>>> > >>>> > >> > commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/concurrent/LazyInitializerTest.java > >>>>>> Mon Feb 23 20:15:49 2015 > >>>>>> @@ -17,6 +17,7 @@ > >>>>>> package org.apache.commons.lang3.concurrent; > >>>>>> > >>>>>> import org.junit.Before; > >>>>>> +import org.junit.Test; > >>>>>> > >>>>>> /** > >>>>>> * Test class for {@code LazyInitializer}. > >>>>>> @@ -26,10 +27,16 @@ import org.junit.Before; > >>>>>> public class LazyInitializerTest extends > >>>>>> AbstractConcurrentInitializerTest { > >>>>>> /** The initializer to be tested. */ > >>>>>> private LazyInitializerTestImpl initializer; > >>>>>> + private ExceptionThrowingLazyInitializerTestImpl > >>>>>> exceptionThrowingInitializer; > >>>>>> + private Exception testCauseException; > >>>>>> + private String testExceptionMessage; > >>>>>> > >>>>>> @Before > >>>>>> public void setUp() throws Exception { > >>>>>> initializer = new LazyInitializerTestImpl(); > >>>>>> + exceptionThrowingInitializer = new > >>>>>> ExceptionThrowingLazyInitializerTestImpl(); > >>>>>> + testExceptionMessage = "x-test-exception-message-x"; > >>>>>> + testCauseException = new Exception(testExceptionMessage); > >>>>>> } > >>>>>> > >>>>>> /** > >>>>>> @@ -43,6 +50,18 @@ public class LazyInitializerTest extends > >>>>>> return initializer; > >>>>>> } > >>>>>> > >>>>>> + @Override > >>>>>> + protected ConcurrentInitializer<Object> > >>>>>> createExceptionThrowingInitializer() { > >>>>>> + return exceptionThrowingInitializer; > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testGetConcurrentWithException () > >>>>>> + throws ConcurrentException, InterruptedException { > >>>>>> + > >>>>>> + super.testGetConcurrentWithException(testExceptionMessage, > >>>>>> testCauseException); > >>>>>> + } > >>>>>> + > >>>>>> /** > >>>>>> * A test implementation of LazyInitializer. This class > creates a > >>>>>> plain > >>>>>> * Object. As Object does not provide a specific equals() > method, > >>>> it > >>>>>> is easy > >>>>>> @@ -55,4 +74,16 @@ public class LazyInitializerTest extends > >>>>>> return new Object(); > >>>>>> } > >>>>>> } > >>>>>> + > >>>>>> + > >>>>>> + /** > >>>>>> + * A concrete test implementation of {@code > >> AtomicSafeInitializer}. > >>>>>> This > >>>>>> + * implementation always throws an exception. > >>>>>> + */ > >>>>>> + private class ExceptionThrowingLazyInitializerTestImpl extends > >>>>>> LazyInitializer<Object> { > >>>>>> + @Override > >>>>>> + protected Object initialize() throws ConcurrentException { > >>>>>> + throw new ConcurrentException(testExceptionMessage, > >>>>>> testCauseException); > >>>>>> + } > >>>>>> + } > >>>>>> } > >>>>>> > >>>>>> > >>>>>> > >>>>> > >>>>> > >>>> > >>>> --------------------------------------------------------------------- > >>>> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org > >>>> For additional commands, e-mail: dev-h...@commons.apache.org > >>>> > >>>> > >>> > >> > >> --------------------------------------------------------------------- > >> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org > >> For additional commands, e-mail: dev-h...@commons.apache.org > >> > >> > > > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org > For additional commands, e-mail: dev-h...@commons.apache.org > > -- http://people.apache.org/~britter/ http://www.systemoutprintln.de/ http://twitter.com/BenediktRitter http://github.com/britter