001    /*****************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.            *
003     * ------------------------------------------------------------------------- *
004     * The software in this package is published under the terms of the BSD      *
005     * style license a copy of which has been included with this distribution in *
006     * the LICENSE.txt file.                                                     *
007     *                                                                           *
008     * Original code by                                                          *
009     *****************************************************************************/
010    package org.picocontainer.behaviors;
011    
012    import java.util.ArrayList;
013    import java.util.Collections;
014    import java.util.Date;
015    import java.util.List;
016    
017    import junit.framework.TestCase;
018    
019    import org.picocontainer.ComponentAdapter;
020    import org.picocontainer.PicoContainer;
021    import org.picocontainer.DefaultPicoContainer;
022    import org.picocontainer.lifecycle.NullLifecycleStrategy;
023    import org.picocontainer.monitors.NullComponentMonitor;
024    import org.picocontainer.behaviors.CachingBehavior;
025    import org.picocontainer.behaviors.CachingBehaviorFactory;
026    import org.picocontainer.behaviors.SynchronizedBehavior;
027    import org.picocontainer.injectors.ConstructorInjector;
028    import org.picocontainer.injectors.ConstructorInjectionFactory;
029    import org.picocontainer.behaviors.SynchronizedBehaviorFactory;
030    
031    /**
032     * @author Thomas Heller
033     * @author Aslak Hellesøy
034     * @author Jörg Schaible
035     * @version $Revision: 3714 $
036     */
037    public final class SynchronizedBehaviorTestCase extends TestCase {
038        private final Runner[] runner = new Runner[3];
039        private int blockerCounter = 0;
040    
041        final class Runner implements Runnable {
042            public RuntimeException exception;
043            public Blocker blocker;
044            private final PicoContainer pico;
045    
046            public Runner(PicoContainer pico) {
047                this.pico = pico;
048            }
049    
050            public void run() {
051                try {
052                    blocker = (Blocker) pico.getComponent("key");
053                } catch (RuntimeException e) {
054                    exception = e;
055                }
056            }
057        }
058    
059        public class Blocker {
060            public Blocker() throws InterruptedException {
061                final Thread thread = Thread.currentThread();
062                synchronized (thread) {
063                    SynchronizedBehaviorTestCase.this.blockerCounter++;
064                    thread.wait();
065                }
066            }
067        }
068    
069        private void initTest(ComponentAdapter componentAdapter) throws InterruptedException {
070            DefaultPicoContainer pico = new DefaultPicoContainer();
071            pico.addComponent(this);
072            pico.addAdapter(componentAdapter);
073            blockerCounter = 0;
074    
075            for(int i = 0; i < runner.length; ++i) {
076                runner[i] = new Runner(pico);
077            }
078            
079            Thread racer[] = new Thread[runner.length];
080            for(int i = 0; i < racer.length; ++i) {
081                racer[i] =  new Thread(runner[i]);
082            }
083    
084            for (Thread aRacer2 : racer) {
085                aRacer2.start();
086                Thread.sleep(250);
087            }
088    
089            for (Thread aRacer : racer) {
090                synchronized (aRacer) {
091                    aRacer.notify();
092                }
093            }
094    
095            for (Thread aRacer1 : racer) {
096                aRacer1.join();
097            }
098        }
099    
100        public void testRaceConditionIsHandledBySynchronizedComponentAdapter() throws InterruptedException {
101            ComponentAdapter componentAdapter = new CachingBehavior(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()));
102            SynchronizedBehavior synchronizedComponentAdapter = new SynchronizedBehavior(componentAdapter);
103            initTest(synchronizedComponentAdapter);
104    
105            assertEquals(1, blockerCounter);
106            for (Runner aRunner1 : runner) {
107                assertNull(aRunner1.exception);
108            }
109            for (Runner aRunner : runner) {
110                assertNotNull(aRunner.blocker);
111            }
112            for(int i = 1; i < runner.length; ++i) {
113                assertSame(runner[0].blocker, runner[i].blocker);
114            }
115        }
116    
117        public void testRaceConditionIsNotHandledWithoutSynchronizedComponentAdapter() throws InterruptedException {
118            ComponentAdapter componentAdapter = new CachingBehavior(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()));
119            initTest(componentAdapter);
120    
121            assertNull(runner[0].exception);
122            assertEquals(3, blockerCounter);
123            for(int i = 1; i < runner.length; ++i) {
124                assertNull(runner[i].exception);
125            }
126        }
127    
128        public void THIS_NATURALLY_FAILS_testSingletonCreationRace() throws InterruptedException {
129            DefaultPicoContainer pico = new DefaultPicoContainer();
130            pico.addComponent("slow", SlowCtor.class);
131            runConcurrencyTest(pico);
132        }
133    
134        public void THIS_NATURALLY_FAILS_testSingletonCreationWithSynchronizedAdapter() throws InterruptedException {
135            DefaultPicoContainer pico = new DefaultPicoContainer();
136            pico.addAdapter(new CachingBehavior(new SynchronizedBehavior(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()))));
137            runConcurrencyTest(pico);
138        }
139    
140        // This is overkill - an outer sync adapter is enough
141        public void testSingletonCreationWithSynchronizedAdapterAndDoubleLocking() throws InterruptedException {
142            DefaultPicoContainer pico = new DefaultPicoContainer();
143            pico.addAdapter(new SynchronizedBehavior(new CachingBehavior(new SynchronizedBehavior(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy())))));
144            runConcurrencyTest(pico);
145        }
146    
147        public void testSingletonCreationWithSynchronizedAdapterOutside() throws InterruptedException {
148            DefaultPicoContainer pico = new DefaultPicoContainer();
149            pico.addAdapter(new SynchronizedBehavior(new CachingBehavior(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()))));
150            runConcurrencyTest(pico);
151        }
152    
153        public void testSingletonCreationWithSynchronizedAdapterOutsideUsingFactory() throws InterruptedException {
154            DefaultPicoContainer pico = new DefaultPicoContainer(
155                    new SynchronizedBehaviorFactory().forThis(new CachingBehaviorFactory().forThis(new ConstructorInjectionFactory()))
156            );
157            pico.addComponent("slow", SlowCtor.class);
158            runConcurrencyTest(pico);
159        }
160    
161        private void runConcurrencyTest(final DefaultPicoContainer pico) throws InterruptedException {
162            int size = 10;
163    
164            Thread[] threads = new Thread[size];
165    
166            final List out = Collections.synchronizedList(new ArrayList());
167    
168            for (int i = 0; i < size; i++) {
169    
170                threads[i] = new Thread(new Runnable() {
171                    public void run() {
172                        try {
173                            out.add(pico.getComponent("slow"));
174                        } catch (Exception e) {
175                            // add ex? is e.equals(anotherEOfTheSameType) == true?
176                            out.add(new Date()); // add something else to indicate miss
177                        }
178                    }
179                });
180            }
181    
182            for (Thread thread1 : threads) {
183                thread1.start();
184            }
185            for (Thread thread : threads) {
186                thread.join();
187            }
188    
189            List differentInstances = new ArrayList();
190    
191            for (Object anOut : out) {
192    
193                if (!differentInstances.contains(anOut)) {
194                    differentInstances.add(anOut);
195                }
196            }
197    
198            assertTrue("Only one singleton instance was created [we have " + differentInstances.size() + "]", differentInstances.size() == 1);
199        }
200    
201        public static class SlowCtor {
202            public SlowCtor() throws InterruptedException {
203                Thread.sleep(50);
204            }
205        }
206    }