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 static org.junit.Assert.assertEquals;
013    import static org.junit.Assert.assertNotNull;
014    import static org.junit.Assert.assertNull;
015    import static org.junit.Assert.assertSame;
016    import static org.junit.Assert.assertTrue;
017    
018    import java.util.ArrayList;
019    import java.util.Collections;
020    import java.util.Date;
021    import java.util.List;
022    
023    import org.junit.Test;
024    import org.picocontainer.BehaviorFactory;
025    import org.picocontainer.ComponentAdapter;
026    import org.picocontainer.DefaultPicoContainer;
027    import org.picocontainer.PicoContainer;
028    import org.picocontainer.injectors.ConstructorInjection;
029    import org.picocontainer.injectors.ConstructorInjector;
030    import org.picocontainer.lifecycle.NullLifecycleStrategy;
031    import org.picocontainer.monitors.NullComponentMonitor;
032    
033    /**
034     * @author Thomas Heller
035     * @author Aslak Hellesøy
036     * @author Jörg Schaible
037     */
038    public class SynchronizedTestCase {
039        private final Runner[] runner = new Runner[3];
040        private int blockerCounter = 0;
041    
042        final class Runner implements Runnable {
043            public RuntimeException exception;
044            public Blocker blocker;
045            private final PicoContainer pico;
046    
047            public Runner(PicoContainer pico) {
048                this.pico = pico;
049            }
050    
051            public void run() {
052                try {
053                    blocker = (Blocker) pico.getComponent("key");
054                } catch (RuntimeException e) {
055                    exception = e;
056                }
057            }
058        }
059    
060        public class Blocker {
061            public Blocker() throws InterruptedException {
062                final Thread thread = Thread.currentThread();
063                synchronized (thread) {
064                    SynchronizedTestCase.this.blockerCounter++;
065                    thread.wait();
066                }
067            }
068        }
069    
070        private void initTest(ComponentAdapter componentAdapter) throws InterruptedException {
071            DefaultPicoContainer pico = new DefaultPicoContainer();
072            pico.addComponent(this);
073            pico.addAdapter(componentAdapter);
074            blockerCounter = 0;
075    
076            for(int i = 0; i < runner.length; ++i) {
077                runner[i] = new Runner(pico);
078            }
079            
080            Thread racer[] = new Thread[runner.length];
081            for(int i = 0; i < racer.length; ++i) {
082                racer[i] =  new Thread(runner[i]);
083            }
084    
085            for (Thread aRacer2 : racer) {
086                aRacer2.start();
087                Thread.sleep(250);
088            }
089    
090            for (Thread aRacer : racer) {
091                synchronized (aRacer) {
092                    aRacer.notify();
093                }
094            }
095    
096            for (Thread aRacer1 : racer) {
097                aRacer1.join();
098            }
099        }
100    
101        @Test public void testRaceConditionIsHandledBySynchronizedComponentAdapter() throws InterruptedException {
102            ComponentAdapter componentAdapter = new Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), false));
103            ComponentAdapter synchronizedComponentAdapter = makeComponentAdapter(componentAdapter);
104            initTest(synchronizedComponentAdapter);
105    
106            assertEquals(1, blockerCounter);
107            for (Runner aRunner1 : runner) {
108                assertNull(aRunner1.exception);
109            }
110            for (Runner aRunner : runner) {
111                assertNotNull(aRunner.blocker);
112            }
113            for(int i = 1; i < runner.length; ++i) {
114                assertSame(runner[0].blocker, runner[i].blocker);
115            }
116        }
117    
118        protected ComponentAdapter makeComponentAdapter(ComponentAdapter componentAdapter) {
119            return new Synchronized(componentAdapter);
120        }
121    
122        @Test public void testRaceConditionIsNotHandledWithoutSynchronizedComponentAdapter() throws InterruptedException {
123            ComponentAdapter componentAdapter = new Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), false));
124            initTest(componentAdapter);
125    
126            assertNull(runner[0].exception);
127            assertEquals(3, blockerCounter);
128            for(int i = 1; i < runner.length; ++i) {
129                assertNull(runner[i].exception);
130            }
131        }
132    
133        public void THIS_NATURALLY_FAILS_testSingletonCreationRace() throws InterruptedException {
134            DefaultPicoContainer pico = new DefaultPicoContainer();
135            pico.addComponent("slow", SlowCtor.class);
136            runConcurrencyTest(pico);
137        }
138    
139        public void THIS_NATURALLY_FAILS_testSingletonCreationWithSynchronizedAdapter() throws InterruptedException {
140            DefaultPicoContainer pico = new DefaultPicoContainer();
141            pico.addAdapter(new Cached(makeComponentAdapter(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), false))));
142            runConcurrencyTest(pico);
143        }
144    
145        // This is overkill - an outer sync adapter is enough
146        @Test public void testSingletonCreationWithSynchronizedAdapterAndDoubleLocking() throws InterruptedException {
147            DefaultPicoContainer pico = new DefaultPicoContainer();
148            pico.addAdapter(makeComponentAdapter(new Cached(new Synchronized(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), false)))));
149            runConcurrencyTest(pico);
150        }
151    
152        @Test public void testSingletonCreationWithSynchronizedAdapterOutside() throws InterruptedException {
153            DefaultPicoContainer pico = new DefaultPicoContainer();
154            pico.addAdapter(makeComponentAdapter(new Cached(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), false))));
155            runConcurrencyTest(pico);
156        }
157    
158        @Test public void testSingletonCreationWithSynchronizedAdapterOutsideUsingFactory() throws InterruptedException {
159            DefaultPicoContainer pico = new DefaultPicoContainer(
160                    makeBehaviorFactory().wrap(new Caching().wrap(new ConstructorInjection()))
161            );
162            pico.addComponent("slow", SlowCtor.class);
163            runConcurrencyTest(pico);
164        }
165    
166        protected BehaviorFactory makeBehaviorFactory() {
167            return new Synchronizing();
168        }
169    
170        private void runConcurrencyTest(final DefaultPicoContainer pico) throws InterruptedException {
171            int size = 10;
172    
173            Thread[] threads = new Thread[size];
174    
175            final List out = Collections.synchronizedList(new ArrayList());
176    
177            for (int i = 0; i < size; i++) {
178    
179                threads[i] = new Thread(new Runnable() {
180                    public void run() {
181                        try {
182                            out.add(pico.getComponent("slow"));
183                        } catch (Exception e) {
184                            // add ex? is e.equals(anotherEOfTheSameType) == true?
185                            out.add(new Date()); // add something else to indicate miss
186                        }
187                    }
188                });
189            }
190    
191            for (Thread thread1 : threads) {
192                thread1.start();
193            }
194            for (Thread thread : threads) {
195                thread.join();
196            }
197    
198            List differentInstances = new ArrayList();
199    
200            for (Object anOut : out) {
201    
202                if (!differentInstances.contains(anOut)) {
203                    differentInstances.add(anOut);
204                }
205            }
206    
207            assertTrue("Only one singleton instance was created [we have " + differentInstances.size() + "]", differentInstances.size() == 1);
208        }
209    
210        public static class SlowCtor {
211            public SlowCtor() throws InterruptedException {
212                Thread.sleep(50);
213            }
214        }
215    }