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.BehaviorFactory;
023    import org.picocontainer.lifecycle.NullLifecycleStrategy;
024    import org.picocontainer.monitors.NullComponentMonitor;
025    import org.picocontainer.behaviors.Cached;
026    import org.picocontainer.behaviors.Caching;
027    import org.picocontainer.behaviors.Synchronized;
028    import org.picocontainer.injectors.ConstructorInjector;
029    import org.picocontainer.injectors.ConstructorInjection;
030    import org.picocontainer.behaviors.Synchronizing;
031    
032    /**
033     * @author Thomas Heller
034     * @author Aslak Hellesøy
035     * @author Jörg Schaible
036     */
037    public class SynchronizedTestCase 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                    SynchronizedTestCase.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 Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()));
102            ComponentAdapter synchronizedComponentAdapter = makeComponentAdapter(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        protected ComponentAdapter makeComponentAdapter(ComponentAdapter componentAdapter) {
118            return new Synchronized(componentAdapter);
119        }
120    
121        public void testRaceConditionIsNotHandledWithoutSynchronizedComponentAdapter() throws InterruptedException {
122            ComponentAdapter componentAdapter = new Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()));
123            initTest(componentAdapter);
124    
125            assertNull(runner[0].exception);
126            assertEquals(3, blockerCounter);
127            for(int i = 1; i < runner.length; ++i) {
128                assertNull(runner[i].exception);
129            }
130        }
131    
132        public void THIS_NATURALLY_FAILS_testSingletonCreationRace() throws InterruptedException {
133            DefaultPicoContainer pico = new DefaultPicoContainer();
134            pico.addComponent("slow", SlowCtor.class);
135            runConcurrencyTest(pico);
136        }
137    
138        public void THIS_NATURALLY_FAILS_testSingletonCreationWithSynchronizedAdapter() throws InterruptedException {
139            DefaultPicoContainer pico = new DefaultPicoContainer();
140            pico.addAdapter(new Cached(makeComponentAdapter(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()))));
141            runConcurrencyTest(pico);
142        }
143    
144        // This is overkill - an outer sync adapter is enough
145        public void testSingletonCreationWithSynchronizedAdapterAndDoubleLocking() throws InterruptedException {
146            DefaultPicoContainer pico = new DefaultPicoContainer();
147            pico.addAdapter(makeComponentAdapter(new Cached(new Synchronized(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy())))));
148            runConcurrencyTest(pico);
149        }
150    
151        public void testSingletonCreationWithSynchronizedAdapterOutside() throws InterruptedException {
152            DefaultPicoContainer pico = new DefaultPicoContainer();
153            pico.addAdapter(makeComponentAdapter(new Cached(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()))));
154            runConcurrencyTest(pico);
155        }
156    
157        public void testSingletonCreationWithSynchronizedAdapterOutsideUsingFactory() throws InterruptedException {
158            DefaultPicoContainer pico = new DefaultPicoContainer(
159                    makeBehaviorFactory().wrap(new Caching().wrap(new ConstructorInjection()))
160            );
161            pico.addComponent("slow", SlowCtor.class);
162            runConcurrencyTest(pico);
163        }
164    
165        protected BehaviorFactory makeBehaviorFactory() {
166            return new Synchronizing();
167        }
168    
169        private void runConcurrencyTest(final DefaultPicoContainer pico) throws InterruptedException {
170            int size = 10;
171    
172            Thread[] threads = new Thread[size];
173    
174            final List out = Collections.synchronizedList(new ArrayList());
175    
176            for (int i = 0; i < size; i++) {
177    
178                threads[i] = new Thread(new Runnable() {
179                    public void run() {
180                        try {
181                            out.add(pico.getComponent("slow"));
182                        } catch (Exception e) {
183                            // add ex? is e.equals(anotherEOfTheSameType) == true?
184                            out.add(new Date()); // add something else to indicate miss
185                        }
186                    }
187                });
188            }
189    
190            for (Thread thread1 : threads) {
191                thread1.start();
192            }
193            for (Thread thread : threads) {
194                thread.join();
195            }
196    
197            List differentInstances = new ArrayList();
198    
199            for (Object anOut : out) {
200    
201                if (!differentInstances.contains(anOut)) {
202                    differentInstances.add(anOut);
203                }
204            }
205    
206            assertTrue("Only one singleton instance was created [we have " + differentInstances.size() + "]", differentInstances.size() == 1);
207        }
208    
209        public static class SlowCtor {
210            public SlowCtor() throws InterruptedException {
211                Thread.sleep(50);
212            }
213        }
214    }